C: execvp() and command line arguments - c

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.

Related

pathname vs arguments for execve parameters

I am trying to implement a simple shell program that runs user input commands. I want the user to enter "ls" or "dir" and have the shell run /bin/ls or /bin/dir . For the execve argument what would be correct:
char *args[] ={"/bin/", "ls", NULL};
//Fork, pid, etc...then
execve(args[0], args+1, NULL);
Or would it be something different?? I've see some people using /bin/ls as the pathname and then no arguments or ls as the pathname and \bin for the environment? I tried what I had above and it didn't work so I want to know if it is the arguments I am sending it or whether I should be looking elsewhere in my code for the problem. Note I am not interested in other variations of execve such as execvp. I am on a Linux system. Thanks
PATHNAME
The pathname in execve() must be the full path to the executable, such as /bin/ls. If using execvpe(), you could use ls alone as the pathname, but as you already specified, you don’t want to use that.
ARGUMENTS
The arguments should be an array of strings, one for each space-separated argument specified on the command line. The last one should be NULL. The first argument should be the pathname itself. For example:
char* args[] = {"/bin/ls", "-la", "foo/bar", NULL};
ENVIRONMENT
The environment variables cannot be omitted when using execve(). In some implementations, NULL can be passed as the last argument to execve(), but this is not standard. Instead, you should pass a pointer to a null pointer; essentially an empty array of environment variables.
Putting it together
char *args[] ={"/bin/ls", "-foo", "bar", NULL};
//Fork, pid, etc...then
char* nullstr = NULL;
execve(args[0], args, &nullstr);
From execve [emphasis added]:
int execve(const char *pathname, char *const argv[],
char *const envp[]);
execve() executes the program referred to by pathname. This
causes the program that is currently being run by the calling
process to be replaced with a new program, with newly initialized
stack, heap, and (initialized and uninitialized) data segments.
pathname must be either a binary executable, or a script starting
with a line of the form:
#!interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
argv is an array of pointers to strings passed to the new program
as its command-line arguments. By convention, the first of these
strings (i.e., argv[0]) should contain the filename associated
with the file being executed. The argv array must be terminated
by a NULL pointer. (Thus, in the new program, argv[argc] will be
NULL.)
In your case, the pathname should be "/bin/ls" and not "/bin/". If you want to pass any command line argument with the command, you can provide first argument them with argv vector index 1, second argument with index 2 and so on and terminate the argument vector with NULL.
A sample program which replace the current executable image with /bin/ls program and runs /bin/ls testfile:
#include <stdio.h>
#include <unistd.h>
int main (void) {
char *args[] = {"/bin/ls", "testfile", NULL};
// here you can call fork and then call execve in child process
execve(args[0], args, NULL);
return 0;
}
Output:
# ./a.out
testfile

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

Why do we pass the command name twice to execve, as a path and in the argument list?

I have a program written by my professor that prints the working directory (pwd) by using execve(), but I don't understand the parameters.
pid_t pid = fork();
if(pid <0)
perror(NULL);
else if(pid == 0)
{
char*argv[] = {"pwd",NULL};
execve("/bin/pwd",argv,NULL);
perror(NULL);
}
else
printf("Im the parent!");
return 0;
}
"/bin/pwd" gives the path to the executable that will be executed.
This means that it will call the pwd function, doesn't it?
Then why do I need to have the parameter pwd?
Couldn't the program run without that parameter?
By convention, the first argument passed to a program is the file name of the executable. However, it doesn't necessarily have to be.
As an example, take the following program:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("number of arguments: %d\n", argc);
printf("program name: %s\n", argv[0]);
for (i=1; i<argc; i++) {
printf("arg %d: %s\n", argv[i]);
}
return 0;
}
If you run this program from another like this:
char*argv[] = {"myprog", "A", "B", NULL};
execve("/home/dbush/myprog",argv,NULL);
The above will output:
number of arguments: 3
program name: myprog
arg 1: A
arg 2: B
But you could also run it like this
char*argv[] = {"myotherprog", "A", "B", NULL};
execve("/home/dbush/myprog",argv,NULL);
And it will output:
number of arguments: 3
program name: myotherprog
arg 1: A
arg 2: B
You can use the value of argv[0] as a way to know how your program was called and perhaps expose different functionality based on that.
The popular busybox tool does just this. A single executable is linked with different file names. Depending on which link a user used to run the executable, it can read argv[0] to know whether it was called as ls, ps, pwd, etc.
The execve man page has some mention of this. The emphasis is mine.
By convention, the first of these strings should contain the filename associated with the file being executed.
That is, it is not a actually mandatory for the first argv to be the filename. In fact one can test that by changing the argv[0] to any string in the example code and the result will still be correct.
So it really is just a convention. Many programs will use argv[0] and expect it to be the filename. But many programs also do not care about argv[0] (like pwd). So whether argv[0] actually needs to be set to the filename depends on what program is being executed. Having said that, it would be wise to always follow the convention to play nicely with almost everyone's long held expectations.
From execve man page: http://man7.org/linux/man-pages/man2/execve.2.html
argv is an array of argument strings passed to the new program. By
convention, the first of these strings (i.e., argv[0]) should contain
the filename associated with the file being executed. envp is an
array of strings, conventionally of the form key=value, which are
passed as environment to the new program. The argv and envp arrays
must each include a null pointer at the end of the array.
So, argv is treated as command line args for new program to execute with.
Since by default, for a linux binary invoked with arguments, these args are accessed through argc/argv, where argv[0] holds the program name.
I think this is to keep the behavior parity to match with default case (prog invoked with arguments).
From the source:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exec.c#l1376
The argv passed to execve is used to construct argv for the about to be launched binary.

less: missing filename using execvp()

I am trying to use execvp() to run less however I keep running into the same error saying that:
Missing filename ("less --help" for help)
I'm assuming I am trying to input the file completely wrong. Could anyone give me some guidance? Here is my code line trying to implement it:
// args[0] == "tempFile" which is in my directory
execvp("less", args)
argv[0] is meant to be the name you give to the command. The command you execute uses that to know how it was invoked. Typically, you'd want to use something like less here:
argv[0] = "less";
argv[1] = "filename";
argv[2] = NULL;
execvp("less", argv);
Argument 0, taken from argv[0], is conventionally the command name. Typically it's the same string that you pass as the first argument to execvp (that's what shells do).
Argument 1, from argv[1], is the first “real” argument. So pass a 3-element array containing: char *args[] = {"less", "tempFile", NULL}
Most languages follow the same argument numbering. For example, if you invoke a shell script, it sees what you pass as argv[0] as its $0, what you pass as argv[1] as $1, etc. Perl is a notable exception: in a Perl script, argv[0] is $0, argv[1] is $ARGV[0], argv[2] is $ARGV[1], etc.
Just as when you execute a shell script on the command line with
sh -c 'script body here' arg0 arg1 arg2
the arg0 argument gets placed in $0 (this is usually the name of the process) and is not really counted as one of the command line arguments of the script itself (it's not part of $# and $# will not count it). The first command line argument available in $1 is arg1.
In your case, use args[0] = "less" and args[1] = "tempFile" in your C code. args[3] should be a null pointer.

using exec with only some parameters of argv (using 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...)

Resources