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 ...
Related
Im programming a shell. This is a small piece of it that handles redirects. I only included the snippet that is causing the error. This portion of the program is supposed to take a command/argument line and execute the line accordingly. The wc < junk > junk2 is an example of a command line input and for some reason when i execute the line its producing that error junk: >: open: No such file or directory. The rest of the program functions fine but its breaking here.
: wc < junk > junk2
junk: >: open: No such file or directory
1 4 31 junk2
1 4 31 total
I briefly had it working as intended but for some reason I must have lost a piece of the puzzle because it's broken once again.
Ive looked at my array values and they are as expected. For some clarification I'm using a copy of the arguments array so the original is unchanged by setting the argument copy value to NULL. Im setting the argument copy value to null so the loop doesn't repeatedly use a redirection.
If you have any advise or see the problem that I apparently can't it would be appreciated.
while (arguments[i] != NULL)
{
if ((strcmp(argumentsCopy[i], "<") == 0))
{
if ((access(argumentsCopy[i + 1], R_OK) == -1))
{
printf("Cannot open %s for input\n", argumentsCopy[i + 1]);
fflush(stdout);
redirect = 1;
}
else
{
int fd = open(argumentsCopy[i + 1], O_RDONLY);
dup2(fd, STDIN_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1;
execvp(command, &arguments[i + 1]);
}
}
if ((strcmp(argumentsCopy[i], ">") == 0))
{
int fd = open(argumentsCopy[ i + 1], O_RDWR);
dup2(fd, STDOUT_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1; // avoid repeat use of redirection symbol in arguments array
execvp(command, &arguments[i + 1]);
}
i++;
}
if (redirect == 0)
{
execvp(command, execArgs); //execArgs is entire command w/ arguments
}
exit 0; //default if error occurred.
First, note that you're not correctly using the execvp function. Once a call to execvp (or any call in the exec family) succeeds, the execution of the current program terminates and is replaced by the program you are execing. Thus, control flow should never proceed past an execvp call unless that call failed. Therefore, you cannot call execvp until you are sure both the stdin and stdout file descriptors have been properly redirected (in the case that you have both input and output redirection). From your code sample, it appears you call execvp once either one is detected, which means your shell can only redirect input or output, not both.
However, that is not the source of the error you're receiving. Consider the input wc < junk > junk2. Based on the information you provided, the command and arguments array will be populated as follows:
command = "wc"
arguments[0] = ">"
arguments[1] = "junk"
arguments[2] = "<"
arguments[3] = "junk2"
arguments[4] = NULL
When i = 0, the first if-statement will be taken, opening the file descriptor and performing execvp with the parameters command and &arguments[i+1]. These correspond to the file to be executed and the argv array passed to that function's main method respectively. In this case, the command is wc and the args are { "junk", "<", "junk2", NULL } (as this is the value of the null terminated array beginning at &arguments[i+1]). On Unix systems, it is conventional for the first argument (argv[0]) to be the name of the current program. Thus the actual command line arguments begin at argv[1] (see Program Arguments for documentation). From wc's perspective, the command line arguments it should process are { "<", "junk2", NULL } after it processes what it believes to be its program name, argv[0], or junk.
wc takes as arguments the list of files it will process. In this case, it believes that list to be < and junk2, as "<" is an operator recognized by bash and other shells, not the executing program. Thus, wc (which believes its name is junk because of argv[0]) attempts to open < as an input file and fails, printing the message:
junk: >: open: No such file or directory
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.
Assume, I have one line bash script that executes everything it has in arguments
#!/bin/bash
$1
So, the command ./one_line_script.sh "ls -lh" works fine.
Also I have C code, that accepts arguments and send them to my bash script
int main (int argc, char* argv[])
{
char command[1000];
sprintf(command, "/path/to/one_line_script.sh %s", argv[1]);
system(command);
return 0;
}
And here I've got a problem, because ./c_program "ls -lh" returns only ls output. It doesn't understand few arguments. How do I need to modify my C code, so it could accept few arguments?
I would recommend to use fork and exec directly to avoid quoting issues altogether. Consider for example what happens if a ' is contained within an argument - when doing sprintf cmd-line butchering this leads to broken command lines.
int pid = fork();
if(pid == 0) {
execl("/bin/sh", "sh", "-c", arg1, arg2, arg3, 0);
} else {
int status=0;
waitpid(pid, &status, 0);
}
You need to quote it in your sprintf too, or bash will only receive one argument :)
sprintf(command, "/path/to/one_line_script.sh '%s'", argv[1]);
I've added quotes (') around the %s.
You can also use $# instead of $1 in your bash script, so it will take all arguments passed to it.
Try:
int i;
command[0] = '\0';
for (i = 1; i < argc; i++)
strcat (command, argv[i]);
system (command);
This should work, but please be aware that it has a lot of security hazards: first of all executing any possible command you get on the command line might allow users to do things they normally aren't allowed to (don't setuid your program!). Then the buffer might easily overflow and allow all kinds of stack smashing. So I'd say: only use this program as a learning tool, to learn manipulation of argc/argv and to begin thinking about security. Don't even compile it!
I've separated a given command from the user into substrings , here's the code :
int i;
char *line = malloc(BUFFER);
char *origLine = line;
fgets(line, 128, stdin); // get a line from stdin
// get complete diagnostics on the given string
lineData info = runDiagnostics(line);
char command[20];
sscanf(line, "%20s ", command);
line = strchr(line, ' ');
printf("The Command is: %s\n", command);
int currentCount = 0; // number of elements in the line
int *argumentsCount = ¤tCount; // pointer to that
// get the elements separated
char** arguments = separateLineGetElements(line,argumentsCount);
// here we call a method that would execute the commands
if (execvp(*arguments,*argumentsCount) < 0) // execute the command
{
printf("ERROR: exec failed\n");
exit(1);
}
When I execute the command in execvp(*arguments,*argumentsCount) , it fails .
What's wrong ?
Thanks .
EDIT :
The input from the user is : ls > a.out , hence I have 3 strings , which are :
ls , > , a.out , and it fails .
Shell redirection won't work if you aren't invoking a shell. You also won't have path searching to find the ls program. Some options
use system() instead, and exit when it returns
exec a shell and have it run your command
setup redirection as a shell would, then fork and execute each required child program.
Also your command doesn't make a lot of sense, you probably want ¦ instead of > and may need to specify the directory of a.out if it is not in your path. Consider giving it a meaningful name as well.
From man page of execvp command:
int execvp(const char *file, char *const argv[]);
The second argument is a list of null-terminated C-strings as arguments to the command to be executed by execvp. But in your code, you pass an int as the second argument which is wrong.
If you have list of arguments in the variable arguments then call execvp as:
execvp(arguments[0],arguments);
When you run ls > a.out at the command-line, > and a.out are not arguments passed to the application; they're interpreted by the shell to redirect stdout.
So in short, it is not possible to do what you want to do.1
1. Well, it is, but not this way. Your application would need to interpret the arguments, create the file, and set up a stream redirect.
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.