using exec with only some parameters of argv (using C) - c

I need to have an input that looks like
./a.out <exe> <arg1> ... <argn> <others_stuff>
where <exe> <arg1> ... <argn> is the input that I must execute as a separate process (the objective is to save the exe's output into a txt).
Saving output into a txt file isn't a problem, I just have to redirect stdout (using dup2, freopen, or something similar).
The problem is to execute just a portion of argv! Because exec's family functions (they are so many!) let to give as input whole argv, or specifying each arg.
I'm writing over here because I can't solve the problem, so I hope you're going to help me (I googled everywhere with no success).
EDIT: I forgot to say that i cannot use system for execute the command!

If you want to use a contiguous portion of argv, you have two options, you can (as you have tried) create a new arg array, properly filling it as so:
char *params[argc-2];
memcpy(params, argv+1, sizeof params);
params[argc-3] = NULL;
execvp(*params, params);
You could just smash argv
argv[argc-3] = NULL;
execvp(argv[1], argv+1);
Or if you don't have too many args, you can use execlp:
execlp(argv[0], argv[0], argv[3], argv[2], argv[4], NULL);

Since exec accepts an argv argument as a char* array terminated by a NULL pointer, you can just use the existing argv and set the member after the last one you want to pass to NULL.
This does destroy argv - if that's a problem, you can copy it first (you'll have to allocate some memory for the new copy...)

Related

Issue passing optional parameters into C execlp()

I'm writing a C program that uses execlp() to run the linux command-line tool, convert. This command takes optional arguments. However, when using it with execlp(), my C program doesn't recognize the flags I pass in and thus doesn't do the command properly.
For example, if I were to run this command in terminal convert -resize 10% src.jpg dst.jpg it will resize the src image by 10%, saving it to dst. However when I run my C program with this code
rc = execlp("convert", "-resize 10%", src, dst, NULL);
my computer doesn't recognize the resize -10% flag and doesn't do anything to my source image. Why is that?
By convention, the first parameter to a process (accessible as argv[0]) is the name of the process. You haven't included that, so "-resize 10%" is read as the process name instead of an option.
Also, "-resize 10%" is actually two parameters separated by a space, so you need to split them up.
rc = execlp("convert", "convert", "-resize", "10%", src, dst, NULL);
Most likely, the -resize should be one option and 10% should be another:
rc = execlp("convert", "convert", "-resize", "10%", src, dst, NULL);
Using execlp() is a bad idea if you have variable numbers of arguments — use execvp() instead, building an array of arguments terminated by a null pointer. Use execlp() only when the argument list is fixed.
char *args[6];
int i = 0;
args[i++] = "convert";
args[i++] = "-resize";
args[i++] = "10%";
args[i++] = src;
args[i++] = dst;
args[i++] = NULL;
rc = execvp(args[0], args);
Note that this formula ensures that the program name is passed correctly — once as a string that is searched for on $PATH, and once as the argv[0] of the executed program.
With execlp(), as dbush notes, you have to repeat the command name — once to specify the executable and once to specify the value for argv[0].
Note too that there is nothing to stop you from telling the program via argv[0] that it has a wholly different name from the name that you execute. This rarely happens (shells don't do it) but when you write the code yourself, it is possible.

write Error: Broken Pipe in C using execlp

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).

C: execvp() and command line arguments

So I'm writing a program where the arguments are as follows:
program start emacs file.c
or even
program wait
In essence, the first argument (argv[0]) is the program name, followed by user inputs.
Inside my code, I invoke execvp. Thing is, I'm not entirely sure I'm invoking the right arguments.
if (pid == 0) {
execvp(argv[1], argv); //Line of interest
exit(1);
}
are argv[1] and argv the correct arguments to pass for the functionality described above? I looked at the man page and they make sense but might not be correct for this case.
Thank you!
In your main, argv will be like this in the first example:
argv[0] = "program";
argv[1] = "start";
argv[2] = "emacs";
argv[3] = "file.c";
argv[4] = NULL;
In execv you want to execute the program "start" with args "emacs file.c", right?. Then the first parameter should be argv[1] - "start" and the second one an array with this strings: {"start", "emacs", "file.c", NULL}. If you use argv, you include the "program" string in argv[0].
You can create a new array and copy these parameters or use the address of argv[1] like this:
execvp(argv[1], &argv[1]); //Line of interest
The only thing that might be an issue is that argv[0] in argv passed to execvp won't match argv[1] (the first argument). Otherwise, it looks okay.
Imagine calling program cat file.txt. In your program, argv will be {"program", "cat", "file.txt", NULL}. Then, in cat, even though the binary called will be cat, argv will still be {"program", "cat", "file.txt", NULL}.
Since cat tries to open and read each argument as a file, the first file it'll try to open is cat (argv[1]), which isn't the desired behavior.
The simple solution is to use execvp(argv[1], argv+1) - this essentially shifts the argument array to the left by one element.
My understanding is that you want to take a specific action based on the second command-line argument (argv[1]). If the second argument is 'start', your program should start the executable named argv[2] with the arguments provided thereafter (right?). In this case, you should provide execvp with the executable name (argv[2]) [1] and a list of arguments, which by convention starts with the name of the executable (argv[2]).
execvp(argv[2], &argv[2]) would implement what we have described in the last paragraph (assuming this is what you intended to do).
[1] execvp expects 2 arguments as you know. The first is a filename; if the specified filename does not contain a slash character (/), execvp will do a lookup in the PATH environment variable (which contains a list of directories where executable files reside) to find the executable's fully-qualified name. The second argument is a list of command-line arguments that will be available to the program when it starts.

malloc on (char**)

Well, I'm trying to write a shell for linux using C. Using the functions fork() and execl(), I can execute each command, but now I'm stuck trying to read the arguments:
char * command;
char ** c_args = NULL;
bytes_read = getline (&command, &nbytes, stdin);
command = strtok(command, "\n ");
int arg = 0;
c_arg = strtok(NULL, "\n ");
while( c_arg != NULL ) {
if( c_args == NULL ) {
c_args = (char**) malloc(sizeof(char*));
}
else {
c_args = (char**) realloc( c_args, sizeof(char*) * (arg + 1) );
}
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
c_arg = strtok(NULL, "\n ");
arg++;
}
...
pid_t pid = fork()
...
...
execl( <path>, command, c_args, NULL)
...
...
That way I get errors from the command when I try to pass arguments, for example:
ls -l
Gives me:
ls: cannot access p��: No such file or directory
I know that the problem is the c_args allocation. What's wrong with it?
Cheers.
You can't use execl() for a variable list of arguments; you need to use execv() or one of its variants (execve(), execvp(), etc). You can only use execl() when you know all the arguments that will be present at compile time. In most cases, a general shell won't know that. An exception is when you do something like:
execl("/bin/sh", "/bin/sh", "-c", command_line, (char *)0);
Here, you're invoking the shell to run a single string as the command line (with no other arguments). However, when you're dealing with what people type at the keyboard in a full shell, you won't have the luxury of knowing how many arguments they typed at compile time.
At its simplest, you should be using:
execvp(c_args[0], c_args);
The zeroth argument, the command name, should be what you pass to execvp(). If that's a simple file name (no /), then it will look for the command in directories on your $PATH environment variable. If the command name contains a slash, then it will look for the (relative or absolute) file name specified and execute that if it exists, and fail if it does not. The other arguments should all be in the null-terminated list c_args.
Now, there may also be other memory allocation issues; I've not scrutinized the code. You could check them, though, by diagnostic printing of the argument list:
char **pargs = c_args;
while (*pargs != 0)
puts(*pargs++);
That prints each argument on a separate line. Note that it doesn't stop until it encounters a null pointer; it is crucial that you null terminate your list of pointers to the argument strings.
This bit of your code:
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
looks like overkill in the usual case, and an inadequate memory allocation in the extreme cases. When you're copying strings around, allocate enough length. I see that you're using strtok() to bust apart a string — it'll do for the early incarnations of a shell, but when you get to process command lines like ls -l>$tmp, you will find strtok()'s penchant for trampling over your delimiter before you get to read it becomes a major liability. However, while you're using it, you probably don't have to copy the arguments like that; you can just set c_args[arg++] = result_from_strtok;. When you do need to copy, you should probably use strdup(); it doesn't forget to allocate enough space for the trailing '\0', for example, and neither over-allocates nor under-allocates.
Jonathan has a great answer, I just wanted to add a few more things.
You could use popen or system to just execute by the shell directly. They are usually frowned upon since they can be injected so easily, but if you are writing an open shell, I don't see the harm in using them.
If you are going for a limited shell (which accepts a sh-like syntax), take a look into wordexp. It does a lot, but in my experience it does too much, especially if you are trying to write a moderately secure interpreter (it does silly things like tilde expansion and variable substitution).

running mysql import from execv

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.

Resources