I am trying to implement basic command execution in a shell program for the unix-like xv6 OS. The part of the shell code that I am editing is the runcmd function where I am using the execvp command to execute the commands used in the terminal. The program compiles without errors when I compile it but nothing happens when I try to type a command on the command line. I've read the man pages for the exec command, but I still don't really understand the proper way in which these arguments need to passed in the exec() command or when to use which version of exec() as I'm still very new to OS programming.
What haven't I implemented here that needs to be added in order for commands to be executed? I have the code for the runcmd function below:
EDIT:
I just added more exec statements with the paths to the binary for each command; however, only the first exec command works (which is cd in this case). When I use any other command, the command line executes it as if it is CD. How do I get it to work for multiple commands?
struct cmd {
int type; // ' ' (exec), | (pipe), '<' or '>' for redirection
};
struct
execcmd {
int type; // ' '
char *argv[MAXARGS]; // arguments to the command to be exec-ed
};
// Execute cmd. Never returns.
void
runcmd(struct cmd *cmd)
{
int p[2], r;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;
if(cmd == 0)
exit(0);
switch(cmd->type){
default:
fprintf(stderr, "unknown runcmd\n");
exit(-1);
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
//fprintf(stderr, "exec not implemented\n");
execvp("/bin/cd" , ecmd->argv );
execvp("/bin/grep" , ecmd->argv );
execvp("/bin/echo" , ecmd->argv );
execvp("/bin/cat" , ecmd->argv );
execvp("/bin/ls" , ecmd->argv );
break;
case '>':
case '<':
rcmd = (struct redircmd*)cmd;
//fprintf(stderr, "redir not implemented\n");
execvp("/bin" , ecmd->argv );
runcmd(rcmd->cmd);
break;
case '|':
pcmd = (struct pipecmd*)cmd;
fprintf(stderr, "pipe not implemented\n");
int execl(const char *path, const char *arg, ...);
break;
}
exit(0);
}
It looks like you are trying to execute the "/bin" directory.
You should make the first argument to the exec call be the binary the user wants to run.
Using the perror function would also give you useful output when the command fails.
I think this is what you actually need:
case ' ':
ecmd = (struct execcmd*)cmd;
if (ecmd->argv[0] == 0)
_exit(0);
execvp(ecmd->argv[0], ecmd->argv);
/* if control reaches this point, execvp failed */
perror(ecmd->argv[0]);
_exit(127);
You may be wondering why execvp takes the executable to load as a separate argument from the argument vector, when you're just going to hand it argv[0]. This is legacy functionality; if I were designing this API from scratch today, I don't think I would include it. However, the idea is that a program might behave differently depending on what its argv[0] is. For instance, back in the day ex and vi were the same executable (two hard links to the same inode -- symlinks had not been invented yet), which decided what mode to start up in based on its argv[0]. And, since the dawn of Unix, login(1) has invoked shells with the first character of argv[0] set to '-'; there is no /bin/-sh, so it actually needs the ability to specify the program-to-invoke separately from the argument vector.
I can't presently think of a situation where a shell would use anything other than argv[0] as the first argument to execvp.
Notes on other changes to your code:
Never call exit on the child side of a fork, only _exit.
When a system call fails, always print out both the name of the offending file and strerror(errno), to stderr, not stdout. perror is a convenient shorthand for this operation.
For the perror call to be safe, you must have called fflush(0) in the parent, immediately before the fork, and your C library must implement line-buffering of stderr, correctly. I mention this because XV6 appears to be a teaching OS where you have to implement bits of it yourself, and I don't know whether stdio is your responsibility or not.
Related
I have issues creating simple C program which takes arguments from command line, the last argument is path to the file. Program runs cat command on given file, and then runs tr on the result of cat. Tr gets arguments from command line(other than the last argument). I am getting errors:
Missing operand.
write error: Broken Pipe.
I am not sure where the mistake is...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WRITE_END 1
#define READ_END 0
int main(int argc, char* argv[]){
if(argc < 2){
printf("\nPROVIDE AN ARGUMENT\n");
return 1;
}
const char * file = argv[argc - 1];
char ** args = calloc(argc - 2, sizeof(char*));
for( int i = 1; i<argc-2; i++){
args[i - 1 ] = argv[i];
}
int fd[2];
pipe(fd);
pid_t child;
if((child = fork()) == -1)return 2;
if(child == 0){
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
close(fd[WRITE_END]);
execlp("cat", "cat", file, (char*)NULL);
exit(1);
}
else{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
close(fd[READ_END]);
execlp("tr", "tr", *args, (char*)NULL);
exit(1);
}
close(fd[0]);
close(fd[1]);
wait(0);
wait(0);
return 0;
}
There are a few problems here that are keeping you from getting this to work. First, as mentioned by Nate Eldredge in a comment, there are problems with the allocation and copying of the all-but-last arguments to variable args. Second, your use of execlp has a slight problem in that the arguments should include an extra argument corresponding to the name of the program run (not the same as the file opened as the executable, lots of people get confused about this point). Third, as also mentioned by Nate, you need to call execvp in the branch of the if-else corresponding to the parent process (the "else" branch). Its second argument will need to be an array of pointers to character, the last of which is NULL.
So taking these one at a time. First, you need to allocate argc slots for args to use it in something like the way you intend:
char ** args = calloc(argc, sizeof(char*));
memcpy(args, argv, sizeof(char*)*(argc -1));
The first line allocates an array of character pointers the same size as the arg. list. The second line copies all but the last pointer in argv to the corresponding location in args and leaves the last one as NULL (calloc initialized the storage for it to be zero, and you need the last pointer in args to be a null pointer if you're going to pass it to execvp, which you will). Note that you're not duplicating all of the storage under argv, just the pointers in the first dimension (remember: argv[0] is a pointer and argv[0][0] is the first character in the program name).
Note that your use of close and dup was fine. I don't know why anyone objected to that unless they forgot that allocating a file descriptor always takes the lowest-numbered descriptor that is unused. That's about the most important thing about descriptor tables as originally used in UNIX.
Next, the call to execlp that overlays the child process created by fork with "cat" is missing an argument. It should be:
execlp("cat", "cat", file, (char*)NULL);
That extra "cat" in there is the value cat will receive when it enters main() as argv[0]. You're probably noticing that this looks like you could lie about the name of the program you're running with the exec__ functions, and you can (but you can't completely hide having done it).
Finally, that second execlp call. You can't pass arguments through as if they were typed on the command line, in one big string: exec in any form doesn't use a shell to invoke the other program and it's not going to parse the command line for you. In addition, the way you were (apparently, if I've read your intent correctly) trying to concatenate the argument strings was also not right (see above comments about args allocation and the memcpy call). You have to break out individual arguments and pass them to it. So if you have an array of pointer to character and the last one is NULL, like you'll have in args after the changes I indicated for allocating and copying data, then you can just pass args to execvp:
execvp("tr", args);
These aren't huge errors and a lot of people make these kinds of mistakes when starting out with manipulating the argument list and using the fork and exec functions. A lot of people make mistakes trying to use a pipe between parent and child processes but you seem to have gotten that part right.
One last thing: the lines downstream in execution from the exec__ calls only get executed if there's an error performing the actual replacement of the running program with the new one. Errors on the command line of "cat" or "tr", for example, won't cause exec__ to fail. Errors like lack of permission to execute the file given as the first argument or absence of the file will cause the exec__ functions to fail. Unless exec returns an error, nothing downstream of the exec call is executed in the process in which it is executed (a successful exec never returns).
I try to run multiple commands (or using simple output redirection) via execve().
When I put this (of course before I pass this string to function I split into spaces and put each each separate to char* []):
"bash -c ' /usr/bin/cat /root/script.sh > /root/script1.sh ' "
to execve() function, I've got an error:
/usr/bin/cat: -c: line 0: unexpected EOF while looking for matching `''
/usr/bin/cat: -c: line 1: syntax error: unexpected end of file
This is my proposal to run multiple linux commands (applications located into PATH) using exactly execve() function (because of security reasons)
But this solution don't work as I expect.
Any idea to fix my solution? Maybe I can use execve() otherwise, but I don't know how..
EDIT: Added simplified (sorry, I can't paste in the original form, because of company restriction) source code:
int foo(const char *cmdline)
{
char d[] = "bash -c ' /usr/bin/cat /root/script.sh > /root/script1.sh ' ";
args = strtok(d, " ");
counter = 0;
while (args != NULL)
{
cmdline_args[counter++] = args;
args = strtok(NULL, " ");
}
cmdline_args[counter] = '\0';
switch (pid = fork()) {
case -1:
ret = -1;
case 0: // for execve
status = execve(cmdline_args[0], cmdline_args, env);
exit(status);
default: // for parent pid
if (waitpid(pid, &status, 0) < 0) {
// in case when waitpid failed
}
}
return ret;
}
As your code is written just now, it will I think execute the executable bash with arguments:
[ "bash", "-c", "'", "/usr/bin/cat", "/root/script.sh", ">", "/root/script1.sh", "'", 0]
I'm guessing that you instead want to be aiming for something like:
[ "/bin/bash", "-c", "/usr/bin/cat /root/script.sh >/root/script1.sh", NULL]
The bash binary is unlikely to respond well to an argument '. When bash processes a command that you type, it does quite a lot of intricate work to process the contents of quoted strings, and pull out the actual intended arguments from them. It looks like you may have to duplicate some of that work, if you really have to process near-arbitrary commands in cmdline (in which case, I would step back and think βis this really the right way to be doing X?β).
Also, execve requires a full path to the binary as its first argument; it doesn't search the PATH.
Also^2: your title mentions a backquote `, but your example code mentions single right-quotes ' β you're aware those are very different, yes?
Works for me if done like this:
cmdline_args[0] = "bash";
cmdline_args[1] = "-c";
cmdline_args[2] = "/usr/bin/cat /root/script.sh > /root/script1.sh";
cmdline_args[3] = NULL;
So the problem is that you're crunching the whole command line string in strtok. Because of that bash gets multiple parameters (not just one as it's supposed to - the whole command string). bash is probably only interpreting the first command parameter so you'll end up executing the ' command ...
There are probably better ways to do that though ...
I am writing a program using execl to execute my exe file which is testing and it's work very well and display the output in the Linux CLI. But I have not idea how to change the execl to execv, although I know both of the system call will give the same value. I am confused with the array argument for execv system call
This is my execl sample program
int main(void)
{
int childpid;
if((childpid = fork()) == -1 )
{
perror("can't fork");
exit(1);
}
else if(childpid == 0)
{
execl("./testing","","",(char *)0);
exit(0);
}
else
{
printf("finish");
exit(0);
}
}
can I know how to change the execl to execv. What I read from online, we must set the file path for my exe file and the argument of array . What type of argument need to set for the array in order to ask the program to execute the testing exe file ?
https://support.sas.com/documentation/onlinedoc/sasc/doc/lr2/execv.htmIs it the link consist of the thing I want ? But what I read from it ,the command is request the list the file,not execute the file. Correct me I make any mistake
In order to see the difference, here is a line of code executing a ls -l -R -a
with execl(3):
execl("/bin/ls", "ls", "-l", "-R", "-a", NULL);
with execv(3):
char* arr[] = {"ls", "-l", "-R", "-a", NULL};
execv("/bin/ls", arr);
The char(*)[] sent to execv will be passed to /bin/ls as argv (in int main(int argc, char **argv))
According to the man page the use of execv is quite simple. The first argument is the path as a string to the program you want to execute. The second is an array of string that will be used as the arguments of the program you want to execute. It is the kind of array you get if you get the argv array in your main function.
So the array you will pass as a parameter will be the array received in the main function of the program you execute with execv.
By convention, the first argument should be the program name (the one you try to execute) but it is not mandatory (but strongly recommended since it is the behaviour a lot of programs are expecting). Each other string in the array should be an individual argument.
And of course, the array should be terminated with a NULL pointer to mark the end.
Array example: ["prog_name", "arg1", "arg2", "arg3", NULL]
[] is your array, each string separated with a coma is a frame of your array and at the end you have the null frame.
I hope I am clear enough!
pid_t childPid = fork ();
if (childPid == (pid_t) 0)//zero success
{
const char *path = "/usr/local/mysql/bin/mysql";
//doesn't work
//char * const parmList[] = {"--user=root", "test_db", NULL};
//does work
char * const parmList[] = {"", "--user=root", "test_db", NULL};
execv(path, parmList);
printf("ERROR:\tFork failed.\n");
}
else if (childPid < (pid_t) 0)// -1 failure
{
/* The fork failed. */
printf("ERROR:\tFork failed.\n");
return EXIT_FAILURE;
}
else
{
while (true) {
//stay alive
sleep(1);
}
}
printf("done");
exit(0);
I am having trouble importing a sql dump by using execv. You can see I wasn't able to login using the first paramList but the second one worked just fine. Anyways, if I add to the param list:
char * const parmList[] = {"", "--user=root", "test_db", "<", "/Users/joelsaltzman/Desktop/dump.sql", NULL};
The output shows the mysql help for the command line args like I typed something wrong.
Does anybody know how to get this to work?
The first paramList is incorrect, because the first element should be the filename of the program you are going to execute:
The argument argv is an array of character pointers to null-terminated strings. The application shall ensure that the last member of this array is a null pointer. These strings shall constitute the
argument list available to the new process image. The value in argv[0] should point to a filename that is associated with the process being started by one of the exec functions.
The input redirection with < does not work because this is not a feature of the kernel (which you invoke using execv), but of usual Unix shells. The system library call is what you are looking for. (It also just uses a call from the exec-family, but calls a shell with your command, which will then support <.)
Be sure to read the manpage system(3) and think about input validation if you are going to pass it a string that could be influenced by a malicious user.
The second one works better, because the first parameter should be the command name. Therefore, MySQL starts reading from the second parameter. You should use the command name (the path), not an empty string, but it normally doesn't matter.
You can't use redirection with execv, because this is a shell feature, and execv doesn't run the shell. You can execute /bin/sh, with parameters that tell it to run mysql, or you can use dup2 to change stdin to whatever you want.
Use popen() instead to start mysql, and then write the contents of the sql file into the process yourself.
I'm writing my own unix terminal and I'm running into a problem executing commands:
First I take the user input and store it into a buffer, then I separate the words and store them into my argv[] array.
i.e
command is "firefox" to launch firefox which is stored in argv[0]
How do I launch the command? This is what I'm trying to do, but I'm getting errors:
void launchProcess(char *command[], char *file){
pid_t pid;
pid = fork();
if (pid == -1) {
perror("Error when forking");
exit(EXIT_FAILURE);
}
if (pid == 0){
fprintf(stderr, "Child pid = %d\n", getpid());
execv(file, command);
}
When I call:
launchProcess(commandArgv, "STANDARD");
I get the following errors:
error: conflicting types for launchProcess
If you have a conflicting type error, you should make sure that the function you listed has the same type as its declaration.
Also, you probably already know, but execv requires a fully qualified path to the executable, so a value like "STANDARD" isn't going to work; use execvp if you want it to use the PATH variable to determine the location of the binary. You should also make sure the last value in the argv array is NULL. Finally, make sure to check the return value of execv; there is a definite possibility it can fail, e.g., if the user tries to execute a program that doesn't exist.
You need to prototype the function: add "void launchProcess(char *command[], char *file);" above your main function.
Your data types look correct, but based on the parameter names: "file" and "command", It looks like you might be using the function in the wrong way.
Here's an example of executing the ls function with no arguments.
char *args[] = { NULL };
execv("/bin/ls", args);
These notes on exec and wait might help some.