Why does my execve() only works when arguments include /bin/? - c

execve() only works when /bin/ is a prefix to the first argument
e.g. ./test.out ls finds nothing while ./test.out /bin/ls works
execve(argv[1], args, getenv("PATH"))

execve() doesn't search PATH for the executable. You have to use execvp() for that. The p in the name stands for PATH.

execve does not search the PATH directories. The first parameter needs to refer to an actual file reachable from the current directory, so needs to contain / characters if the file is not in the current directory.
Linux supports execvpe as a GNU extension which is a cross between the execve and execvp POSIX functions. The p means it will search the PATH for the file as long as it contains no / characters. (If it does contain / characters, it will the same as the non-p version.) The e means that a new environment is passed to the function in the third parameter.
OP's code passes an environment to the execve function that contains only the PATH environment variable from the original environment. If that is the intention, the same effect can be performed in a POSIX compliant manner by stripping the environment before calling execvp. This makes use of the extern char **environ; variable declared by #include <unistd.h>:
char *env_p = getenv("PATH");
char *args[] = {argv[1], argv[2], NULL};
if (environ[0]) {
environ[0] = env_p;
environ[1] = NULL;
}
if (execvp(argv[1], args) < 0) {
perror("execvp");
exit(EXIT_FAILURE);
}

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

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.

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.

Using execve() in c

I need to see a concrete example of how to specify the environment for execve() in a c program. In my class, we are writing a program that will utilize both standard LINUX executables and our own executables. Thus, the environment searching PATH will have to contain tokens for both types of executables. I cannot find a good example of how to specify the environment (third argument) for execve() as every article seems to suggest we use execvp() or *clp() or *cl(), etc., instead.
In my project, we must use execve().
Right now, I'm just trying to get execve() to work for a basic "ls" command so that I can get it to work later for any and all executables.
Here is a snippet of my experiment code:
else if(strcmp(tokens[0], "1") == 0) {
char *args[] = {"ls", "-l", "-a", (char *)0};
char *env_args[] = {"/bin", (char*)0};
execve(args[0], args, env_args);
printf("ERROR\n");
}
Each time command "1" is entered in my shell, I see my error message. I suspect this is because of the way I am declaring env_args[].
Can someone show me a good example of how to implement execve() with a specified command searching environment?
here is the documentation on execve() function http://linux.die.net/man/2/execve
it says:
int execve(const char *filename, char *const argv[], char *const envp[]);
envp is an array of strings, conventionally of the form
key=value, which are passed as environment to the new program.
but in your program env_args does not look like key=value
So probably you should define env_args by the following way:
char *env_args[] = {"PATH=/bin", (char*)0};
or just
char *env_args[] = { (char*)0 };

using a new path with execve to run ls command

I am trying to use execve to run the ls command. Currently I'm running it with the following arguments:
execve(args[0], args, env_args)
//args looks like {"ls", "-l", "-a", NULL}
//env_args looks like {"PATH=/bin", "USER=me", NULL}
What I expected this to do was run the ls command using my new env_args meaning that it would look up ls in my PATH. However, this code actually doesn't do anything and when I run the code it just returns to my command prompt without output.
Using the same args[] I was using execvp and ls worked and searched my current path.
Can you tell me what I am doing wrong?
What I am trying to do is write my own shell program where I can create and export my own environment and have exec use the environment I have defined in a char**. Essentially I am writing my own functions to operate on env_args to add and remove vars and when I call exec i want to be able to call exec on {"ls", "-l", NULL} and have it look down my new environments path variable for a valid program called ls. I hope this explains what I am doing a little better. I don't think the extern environ var will work for me in this case.
The execve() function does not look at PATH; for that, you need execvp(). Your program was failing to execute ls, and apparently you don't report failures to execute a program after the execve(). Note that members of the exec*() family of functions only return on error.
You'd get the result you expected (more or less) if you ran the program with /bin as your current directory (because ./ls - aka ls - would then exist).
You need to provide the pathname of the executable in the first argument to execve(), after finding it using an appropriate PATH setting.
Or continue to use execvp(), but set the variable environ to your new environment. Note that environ is unique among POSIX global variables in that is it not declared in any header.
extern char **environ;
environ = env_args;
execvp(args[0], &args[0]);
You don't need to save the old value and restore it; you're in the child process and switching its environment won't affect the main program (shell).
This seems to work as I'd expect - and demonstrates that the original code behaves as I'd expect.
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
char *args[] = { "ls", "-l", "-a", NULL };
char *env_args[] = { "PATH=/bin", "USER=me", NULL };
execve(args[0], args, env_args);
fprintf(stderr, "Oops!\n");
environ = env_args;
execvp(args[0], &args[0]);
fprintf(stderr, "Oops again!\n");
return -1;
}
I get an 'Oops!' followed by the listing of my directory. When I create an executable ls in my current directory:
#!/bin/sh
echo "Haha!"
then I don't get the 'Oops!' and do get the 'Haha!'.

Resources