I am trying to create a simple c program that takes user input, passes it to underlying shell and return the ouput of the user command. For eg: pwd will give the present working directory.
I want to do this using fork() and exec() in an infinite loop but I am facing two problems:
My loop terminates after first run
It only takes first argument. 'ls -ltr' will give me the output of 'ls' and not 'ls -ltr'
int runit(char*);
void main() {
int pid=0;
char command[50];
while(1)
{
int d=0;
printf("Please enter your command!\n");
scanf("%s", &command);
switch (pid = fork())
{
case 0: // a fork returns 0 to the child
printf("Child process \n");
d=runit(command);
if(d==-1){
printf("command not found \n");}
break;
default:
wait(5); // a fork returns a pid to the parent
printf("Parent process \n");
break;
case -1: //if something went wrong
perror("fork");
exit(1);
}
}
}
int runit(char* command) { //executing the command
char path[50]="/bin/";
int d = execl(strcat(path,command),command,NULL,NULL);
return(d);
}
Can somebody tell me what I am doing wrong or guide me how to correct this.
This input reading
scanf("%s", &command);
would stop at the first whitespace (unless input failure occurs -- you should check the return value of scanf() in any case). So, when you input ls -ltr, command will have just ls. You need to read lines. For example, use fgets() to read input (and make sure to handle the trailing newline):
fgets(command, sizeof command, stdin);
command[strcspn(command, "\n")] = 0; /* to remove \n if present */
And the next problem is that execl() takes multiple arguments. That means
this usage:
int d = execl(strcat(path,command),command,NULL,NULL);
is broken. You need to split the input command into multiple strings and pass each arguments to execl(). A better option to store the individual strings in an array and use execv() instead.
Another thing you need to be aware of is exec*() family functions do not return on success. So, the return code check isn't very useful - instead you can just use perror() to know why it failed if it failed.
Related
Line number #15 { printf("This goes to the terminal\n"); } is not getting printed anywhere not in the terminal nor in the file.
//inputs argc = 3 :- ./executable_file output_file command
int main(int argc, char **argv)
{
if(argc < 3)
{
return 0;
}
int stdout_copy = dup(1);
int fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd < 0)
{
printf("ERROR\n");
return 0;
}
printf("This goes to the standard output(terminal).\n");
printf("Now the standard output will go to \"%s\" file .\n", argv[1]);
dup2(fd, 1);
printf("This output goes to \"%s\"\n",argv[1]);
close(fd);
execvp(argv[2],argv+2);
dup2(stdout_copy,1);
printf("This goes to the terminal\n");
return 0;
}
Apologies for the Previous Question :
I'm really sorry, it was my mistake in analysing it.
And special thanks for all answers and hints.
problem in writing to terminal after using execvp and dup2 syscalls
Neither:
execvp(argc[2],argc+2);
dup2(stdout_copy,1);
printf("This goes to the terminal\n");
Or:
dup2(stdout_copy,1);
execvp(argc[2],argc+2);
printf("This goes to the terminal\n");
...will output to stdout if the call to execvp(argc[2],argc+2); succeeds.
However, both will output to stdout if it fails.
(Unless command line arguments are incorrect, dup2() likely has nothing to do with failure to output to stdout. See additional content below for how to check this.)
Read all about it here: execvp.
In a nutshell, execvp() replaces the current process with a new process. If it is successful the current process is no longer what you are viewing on the terminal. Only when it is not successful will the commands following it be executed.
The following suggestions are not precisely on-topic, but important nonetheless...
Change:
int main(int argv, char **argc)
To:
int main(int argc, char **argv) //or int main(int argc, char *argv[]), either are fine.
This will be the foundation of seeing normal behavior. Anything else is very confusing to future maintainers of your code, and to people trying to understand what you are doing here.
These names are easily remembered by keeping in mind that argc is used for the count of command line arguments, and argv is the vector that is use to store them.
Also, your code shows no indications that you are checking/validating these arguments, but given the nature of your program, they should be validated before going on. For example:
//verify required number of command line arguments was entered
if(argc <!= 3)//requires at least one additional command line argument
{
printf("Usage: prog.exe [path_filename]\nInclude path_filename and try again.\nProgram will exit.");
return 0;
}
//check if file exists before going on
if( access( argv[1], F_OK ) != -1 )
{
// file exists
} else {
// file doesn't exist
}
//do same for argv[2]
(2nd example to check file in Linux environment is from here)
BTW, Knowing the command line arguments that were passed into the program, would help to provide a more definitive answer here. Their syntax and content, and whether or not the files that they reference exist, determine how the call to execvp will behave.
Suggestions
It is generally always look at the return values of functions that have them. But because of the unique behavior of execvp If is successful it does not return, and if it fails it will always return -1. So in this case pay special attention to the value of errno for error indications, again all of which are covered in the link above.
As mentioned in comments (in two places.) it is a good idea to use fflush(stdout) to empty buffers when interpreting standard I/O and file descriptor I/O, and before using any of the exec*() family of calls.
Take time to read the man pages for the functions - shell commands that are used. It will save time, and guide you during debugging sessions.
I have a program x, which I want to cleanly terminate.
You can run it by simply doing ./x and use the terminal to write lines to stdin directly and terminate it by writing exit.
However, if you use: cat file.txt | ./x, the stdin is now piped from the file and therefore you can never type exit.
The best way to end this program would be for it to automatically terminate once the last line was read from the file.
Alternatively, I'd like to re-route stdin back to the terminal if that is at all possible, to further allow manual input as before.
Here is some sample code:
int main() {
// ...
while (ongoing) {
size_t n = 0;
char* ln = NULL;
getline(&ln, &n, stdin);
strtok(ln, "\n");
strtok(ln, "\r");
if (strcmp("exit", ln) == 0) {
break;
}
//...
}
}
you're using getline to read your input. getline returns -1 on EOF. This makes it easy.
if (-1==getline(...))
break;
When you have read all the input from a pipe, EOF will be raised up to indicate that the full input has been reached.
In your example, this will be rougly equivalent with exit, so you can also check the return value of getline to see if the EOF has reached (in which case -1 will be returned).
In Linux, I am finding pid of process by opening pipe with "pidof process_name" command and then reading it's output using fgets function. But it fails to find pid once in a while. Below is my code for finding pid of my process.
int FindPidByProcessName(char *pName)
{
int pid = -1;
char line[30] = { 0 };
char buf[64] = { 0 };
sprintf(buf, "pidof %s", pName);
//pipe stream to process
FILE *cmd = popen(buf, "r");
if (NULL != cmd)
{
//get line from pipe stream
fgets(line, 30, cmd);
//close pipe
pclose(cmd); cmd = NULL;
//convert string to unsigned LONG integer
pid = strtoul(line, NULL, 10);
}
return pid;
}
In output sometimes pid=0 comes even though process is available in "ps" command output.
So, I try to find root cause behind this issue and i found something like input/output buffer mechanism is may creating issue in my scenario.
So I try to use sync() function before opening popen() and strangely my function starts working with 100% accuracy.
Now sync() function is taking too much time(approximately 2min sometime) to complete its execution which is not desirable. So i try to use fflush(), fsync() and fdatasync() but these all are not working appropriately.
So please anyone tell me what was the exact root cause behind this issue And how to solve this issue appropriately?
Ok, the root cause of the error is stored in the errno variable (which btw you do not need to initialize). You can get an informative message using the fucntion
perror("Error: ");
If u use perror the variable errno is interpreted and you get a descriptive message.
Another way (the right way!) of finding the root cause is compiling your program with the -g flag and running the binary with gdb.
Edit: I strongly suggest the use of the gdb debugger so that you can look exactly what path does your code follow, so that you can explain the strange behaviour you described.
Second Edit: Errno stores the last error (return value). Instead of calling the functions as you do, you should write, and check errno immediately:
if ((<function>) <0) {
perror("<function>: ");
exit(1);
}
For a homework assignment I have to write a basic shell including redirection. The program uses readline to prompt for input, parses the input string, and breaks it down into the executable name, the arguments, and the input/output file(s), if applicable. After parsing the string, it forks and the child execv()'s to the executable that was passed in. I'm using dup2() to change the file descriptors after the fork and before the execv, but am having a problem once the program has execv'd to the new executable. If in my shell I run ls > foo.out, I get: ls: cannot access H��y�A�
$ L��H)�I��$�: No such file or directory
Construction of c->argv:
char *args[6];
int i;
for(i=0;i<=4;i++){
char *_arg=strsep(&_str_cmd," ");
printf("Found _arg: %s\n",_arg);
// If there is an argument and it is not blank
if(_arg && strcmp(_arg,"")!=0){
if(strcmp(_arg,"<")==0){
_cmd.infile=strsep(&_str_cmd," ");
i--;
continue;
}
else if(strcmp(_arg,">")==0){
_cmd.outfile=strsep(&_str_cmd," ");
i--;
continue;
}
}
else{break;}
}
args[i]=(char*)0;
_cmd.binary=args[0];
memcpy(_cmd.argv,args,sizeof _cmd.argv);
How are you constructing c->argv? It must be a NULL-terminated array of char *. You are likely missing the terminator.
In your code handling <... and >..., you skip over an entry in argv, leaving it uninitialized.
The aim of the program is to fork a new child process and execute a process which also has command line arguments. If I enter /bin/ls --help, I get the error:
shadyabhi#shadyabhi-desktop:~/lab/200801076_lab3$ ./a.out
Enter the name of the executable(with full path)/bin/ls --help
Starting the executable as a new child process...
Binary file to be executed: /bin/ls
/bin/ls: unrecognized option '--help
'
Try `/bin/ls --help' for more information.
Status returned by Child process: 2
shadyabhi#shadyabhi-desktop:~/lab/200801076_lab3$
What would be the right argument to execve()?
#include<stdio.h>
#include<string.h> //strcpy() used
#include<malloc.h> //malloc() used
#include<unistd.h> //fork() used
#include<stdlib.h> //exit() function used
#include<sys/wait.h> //waitpid() used
int main(int argc, char **argv)
{
char command[256];
char **args=NULL;
char *arg;
int count=0;
char *binary;
pid_t pid;
printf("Enter the name of the executable(with full path)");
fgets(command,256,stdin);
binary=strtok(command," ");
args=malloc(sizeof(char*)*10);
args[0]=malloc(strlen(binary)+1);
strcpy(args[0],binary);
while ((arg=strtok(NULL," "))!=NULL)
{
if ( count%10 == 0) args=realloc(args,sizeof(char*)*10);
count++;
args[count]=malloc(strlen(arg));
strcpy(args[count],arg);
}
args[++count]=NULL;
if ((pid = fork()) == -1)
{
perror("Error forking...\n");
exit(1);
}
if (pid == 0)
{
printf("Starting the executable as a new child process...\n");
printf("Binary file to be executed: %s\n",binary);
execve(args[0],args,NULL);
}
else
{
int status;
waitpid(-1, &status, 0);
printf("Status returned by Child process: %d\n",WEXITSTATUS(status));
}
return 0;
}
The first entry in the args array should be the program name again. Your code calls /bin/ls with --help as the process name.
Please check to make sure args is not getting clobbered by the realloc call. See here on SO regarding realloc
Edit:
Also the loop looks funny....
You called strtok like this:
binary=strtok(command," ");
Change the loop construct to use binary instead as shown...
char *tmpPtr;
while (binary != NULL){
if ( count%10 == 0) tmpPtr=realloc(args,sizeof(char)*10);
if (tmpPtr != NULL) args = tmpPtr;
count++;
args[count-1]=malloc(strlen(binary)+1);
strcpy(args[count-1],binary);
binary = strtok(command, " ");
}
And use the binary for copying the string....
Hope this helps,
Best regards,
Tom.
Your program has some obvious errors. For instance, declaring char **args=NULL; and then args=realloc(args,sizeof(char)*10); (since it's char**, you should be alloc-ing to char*, no?..).
Since sizeof(char*) is usually 4 while sizeof(char) is usually 1, you end up with some serious memory management problems around there (you alloc less than you use, and you end up writing where you shouldn't). From there on, all hell breaks loose and you can't expect your program's behavior to make any sense.
I'd suggest that you run your program through an util such as Valgrind to figure out memory leaks and correct the program appropriately. Probably your execve problems will disappear as soon as the memory problems are corrected.