execvp filepath clarification - c

I'm writing a shell in C, and I'm having trouble understanding the filepath parameter needed for execvp(filepath,argv).
If the user typed wanted to run ls -a in their current directory ... let's say /home/user1 ... what would be the filepath and argv for running ls in said directory?
Would the filepath be the directory for where the command will be executed from /home/user1 or would it be the command's location /bin/ls?

The filepath argument to execvp() can take either of two forms — it contains a slash or it does not contain a slash.
With slash
The filepath argument specifies the pathname of the executable, either an absolute name (starts with slash) or a relative name (does not start with slash, but does contain a slash). The execvp() function makes no changes to the argument, and the program is executed (or not) assuming that the file named exists and is executable.
Without slash
The filepath arguments specifies a 'simple' name such as ls. The excevp() function then seeks to execute a program with the name ls found in one of the directories listed in the $PATH environment variable. The p in execvp() is for 'path', as in path lookup.
Applying the theory
If the user types ls -a to a shell, the normal way to execute that would be to create an array of character pointers equivalent to:
char *argv[] = { "ls", "-a", 0 };
execvp(argv[0], argv);
The execvp() will now do the path-based analysis and attempt to execute ls from one of the directories listed in $PATH.
If the user types /bin/ls -a to a shell, then the normal way to execute that would be to create an array of character pointers equivalent to:
char *argv[] = { "/bin/ls", "-a", 0 };
execvp(argv[0], argv);
The execvp() will now execute the absolute pathname specified, because that's what the user requested (as opposed to, say, /usr/bin/ls or /usr/local/bin/ls).
Note that the processing is actually the same — you split the command line into words; each word becomes an element of an array of character pointers that is terminated with a null pointer, and you pass the first word to execvp() as the 'filepath' argument, and the whole array as the second argument.
Obviously, a shell can cache the locations of the actual executables, and many shells do, so that execvp() doesn't have to do work trying to find the program (and the shells don't call execvp() but typically call execv() with the absolute pathname of the executable. But that isn't necessary; it is an optimization.
Note, too, that there is nothing to stop you doing:
char *argv[] = { "/honky/tonk/toys", "-a", 0 };
execvp("ls", argv);
Now argv[0] should be "/honky/tonk/toys" and not ls, for all it is the ls executable that is run. What you find in /proc depends on you having /proc on your system (Mac OS X does not support it, for example), but the symlink to the binary should be a link to /bin/ls. On Linux, you're apt to find that ps reports the binary name (ls), even though /proc/PID/cmdline contains the original arguments (so argv[0] is /honky/tonk/toys). Whether this is good or not depends on your viewpoint, but all the world is not Linux.

To run /bin/ls -a in the current directory you would need:
The same as argv passed into main() when a C program starts except NULL terminated, and path is the same as argv[0].
char* argv[] = { "/bin/ls", "-a", NULL };
execvp(argv[0], argv);

Related

What does "/bin/sh" stand for in execlp command?

I want to execute a shell command with execlp, I tried with the following instruction :
execlp("sh", "sh", "-c", p_command, (char*)NULL);
p_command is a pointer to a const char representing a shell command line.
My minimal test tells me the program succeded as expected. I first choose to use "/bin/sh" instead of "sh" but I've learned that p(ath) in execlp allows us to avoid writing the full path, as if exec will complete the path for us ; so I removed "/bin/".
My concern is that I never saw a code using execlp with only "sh", as it effectively does for exemple for ls we can directly use "ls" instead of "/bin/ls".
As a beginner I am wondering what "/bin/sh" stands for, what is the difference between "sh" and "/bin/sh" in this situation and why we have to write the full path for execlp to execute a shell ?
When the path passed to execlp is sh, execlp searches for it in the directories listed in the PATH environment variable. If an attacker is able to modify the PATH variable in the environment that runs your program, they can set it to list a directory of their choosing, and they can place their own program named sh in that directory. Then your program will execute their program instead of executing the system sh program. In some cases (depending on a bit in the file’s mode bits), programs are executed with the permissions of their owners rather than the permissions of the user executing the program. Such programs must be written carefully to avoid situations like this, where an attacker would be able to exploit the program.
When the path passed to execlp is /bin/sh, execlp looks for it in the path that is /bin/sh starting from the root of the file system, called /. This will always use the sh program that the system administrator has put in the /bin directory (usually done as part of system installation).

exec function only running some commands, won't run echo

I'm trying to run command line arguments (specifically echo) through the exec family of functions. I can get the execv function to run if I write my own executable and run it, but if I try to run touch or echo it returns -1
#include <stdio.h>
#include <unistd.h> // exec functions
#include <sys/types.h> // pid_t
#include <sys/wait.h>
#define HIGH 1
#define LOW 0
int digitalWrite(int pin, short type) {
pid_t pid = fork();
if (pid == 0) {
printf("pid == %i\n", pid);
if (type == HIGH) {
char* args[] = {"echo", "1", ">", "/sys/class/gpio/gpio67/value", NULL};
int val = execv(args[0], args);
printf("ran function execl, %i\n", val);
} else {
printf("Unable to do anything but set pin to HIGH\n");
}
} else if (pid < 0) { // pid < 0
printf("fork failed\n");
}
wait(NULL);
}
int main() {
printf("Starting digitalWrite\n");
digitalWrite(0, HIGH);
printf("Completed digitalWrite()\n");
return 0;
}
Just for context here's my build:
$ gcc wiringbeagle.c
$ ./a.out
Starting digitalWrite
pid == 0
ran function execl, -1
Completed digitalWrite()
Completed digitalWrite()
$ ls
a.out wiringbeagle.c
The command echo 1 > /sys/class/gpio/gpio67/value runs fine in the terminal on it's own, and if I create a local file (i.e. touch tmpfile.txt) and try to run echo hi > tmpfile.txt it runs as expected in my command line but doesn't run in the program.
I must be not understanding something with execv, and any assistance would be greatly appreciated!
The first argument to execv is the file to be executed. Unlike your shell, execv does not search through the directories indicated by the PATH environment variable, so you need to give it the complete path to the executable. Unless there is an executable file called echo in your current working directory, execv("echo",...) will fail with a "file not found" error. (Use perror to get better error messages).
If you want to search for the executable as the shell does, use execvp. But note that your shell probably executes echo as a built-in command, so it won't be the same echo as your shell uses. In this case, that's fine.
Once you fix that, you will encounter a different problem. Since you are just invoking a command-line utility with arguments, rather than using a shell, the argument ">" is just an argument. It is the shell which handles redirections (as well as pipes, quoting, and a bunch of other useful stuff). So all you will accomplish is to send the three arguments to stdout.
You could use the system function to execute a command using the shell, or you could set up the redirection yourself by freopening stdout in your child before doing the execvp.
You can get quite a lot of information about system interfaces using the man command. For example, to learn what freopen does, use man freopen. You can also read manpages on the internet, eg. http://man7.org/linux/man-pages/man3/freopen.3.html, but the documentation on your own system is right there, and also applies to the actual version of the software installed on your system (assuming you installed the documentation).
I'm not entirely certain why you're even using the exec family to run external programs in this case. The C standard library provides perfectly adequate file I/O stuff.
For example, you can simply fopen, fprintf, and fclose the file without ever starting another external process to do that work for you:
int bytesWrit = 0;
FILE *gpioHndl = fopen("/sys/class/gpio/gpio67/value");
if (gpioHndl != NULL) {
bytesWrit = fprintf(gpioHndl, "1\n");
fclose(gpioHndl);
}
if (bytesWrit != 2) {
HandleError();
}
This is probably the preferred way to do what you want, which is simply writing a fixed value to a file.
In terms of why your execv call isn't working (though it's totally irrelevant if you take my advice above), there are several things you need to be aware of.
First, while some commands are actually files on the disk that you can exec, others may be internal bash commands(a). On my system, for example:
pax:~$ type ftp
ftp is /usr/bin/ftp
pax:~$ type echo
echo is a shell builtin
One way to solve this is to run the actual bash executable (which, being an on-disk command, can be done via exec), telling it to run its internal echo command. From the command line:
pax:~$ bash -c 'echo xyzzy'
xyzzy
Second, if want to use redirection, this is normally something that's done by the shell, not the exec calls or individual executables.
Trying to do redirection via the exec family will generally only result in the >somefile being passed as a literal parameter to the executable (in the argv array), not being used to attach standard output to a file. In other words, it won't work unless the executable specifically handles redirection, which is rare.
So that means you will have to run the shell with redirection and have it run the executable after performing those redirections, even if the command is not an internal one.
Thirdly, if you want the path searched for your executable, execvp is the call you want, not execv (the latter just uses the file you explicitly provide, either relative from the current working directory or an absolute path like /bin/ls). So, in your case, you should either:
use execvp to search the path; or
fully specify the path with execv.
(a) The echo command, while it is bash-internal may also be provided as a separate executable (I believe Posix requires this), so this may not be an issue here. It may be an issue if you expect them to act exactly the same in terms of more esoteric arguments :-)
execv() does not search the PATH environment variable in order to find an executable file. Per the Linux execv() man page (bolded text added):
...
Special semantics for execlp(), execvp(), and execvpe()
The execlp(), execvp(), and execvpe() functions duplicate the actions
of the shell in searching for an executable file if the specified
filename does not contain a slash (/) character. ...
...
So, those three will search the PATH environment variable if the filename passed does not contain a / character.
You're using execv(), which is not one of those three. Therefore, execv() will not search the PATH environment variable.
Since your current working directory doesn't contain an executable file called echo, execv() fails.
You need to use execvp() per the man page.
You need to use absolute path as first parameter in execv
Then, the correct is:
char* args[] = {"/bin/echo","echo", "1", ">", "/sys/class/gpio/gpio67/value", NULL};
But to run what you want (put value '1' in file '/sys/class/gpio/gpio67/value'), you need to use command sh:
char* args[] = {"/bin/sh", "sh","-c", "/bin/echo 1 > /sys/class/gpio/gpio67/value", NULL};
The parameter to "sh -c" is a string. Then, you need to put all command together as a string

Why do we double state the executable name in `execlp`?

To run a command with execlp we do
execlp("ps", "ps", NULL);
A redundancy can be seen here, since we pass ps twice. This behavior is consistent across all exec variants.
Why does exec want such redundancy? Why isn't it written so that we can simply
execlp("ps", NULL);
Other answers have explained that you may provide a different argv[0] than the name of the program, but haven't explained why you might want to do this.
Some programs behave differently depending on the name that's used to invoke them. A common example is shells, e.g. sh, bash, and csh. They check the first character of argv[0], and if this is - they operate as a login shell rather than a regular shell. So when /bin/login is invoking the user's login shell, it does something like:
execlp("/bin/bash", "-bash", (char*)NULL);
This way, bash knows that it's being run as part of a login and can behave accordingly. This could have been done using an option parameter, but then every program that might be used as a login shell would have to recognize that option (some special usernames have login shells that aren't really shells, but are other programs, and requiring them to support the same option as real shells might be problematic).
The first argument is the path of the file to be executed (/bin/ls, /home/development/myproject/foo). The remaining arguments correspond to the argv vector passed to main. Imagine typing the following in your shell:
$ ./foo bar bletch
The executable path is ./foo - that's the first argument passed to execlp. By convention, argv[0] is supposed to be the string used to invoke the program, so the complete argv vector is {"./foo", "bar", "bletch", NULL}. Hence,
execlp( "./foo", /* executable path */
"./foo", /* argv[0] */
"bar", /* argv[1] */
"bletch", /* argv[2] */
NULL /* argv[3] */
);
It's possible that you may not want argv[0] to be the same as the actual command path (say because the path is aliased or you don't want to expose the exact path), in which case you may use something like
execlp( "./secret/path/to/foo", "./foo", "bar", "bletch", NULL );
You don't have to give your program the name of the executable as its argv[0]. You could write a program (rvw11.c compiled to rvw11 executable):
#include <unistd.h>
int main(void)
{
execlp("sleep", "rip van winkle", "40", (char *)0);
return 1;
}
and you would find that if you run:
$ ./rvw11 &
[1] 53034
$ ps
PID TTY TIME CMD
515 ttys000 0:00.07 -bash
534 ttys002 0:00.09 -bash
543 ttys003 0:01.15 -bash
558 ttys004 0:00.32 -bash
53034 ttys004 0:00.01 rip van winkle 40
$
So, the name that shows up in the ps listing is the value given as argv[0] in the argument list, not the name of the executable. (Demonstration on macOS Sierra 10.12.2, but you'd get a similar result on most Unix-like systems.)
This also demonstrates why it is not necessarily feasible to determine the name of the executable from the argument list.
The second parameter should match the first one, but it is not required to be the same.
According to documentation,
argv is an array of argument strings passed to the new program. By convention, the first of these strings should contain the filename associated with the file being executed.
If you have a compelling reason to hide the name of the file being executed from the program that you are planning to run, you can pass any string you want for the second parameter, including an empty or a NULL string:
execlp("ps", "<hidden>", NULL);

execve not taking environment parameters

I want to write a program which executes the Linux ls command.
I really only want to type in ls and not /bin/ls, so I want to do this using execve (execvp is not an option).
I tried:
char *env[] = { "SHELL=/bin/bash",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games",
"_=/usr/bin/env",
(char *)0 };
execve(parmList[0], parmList, env);
But it does not seem to work, ls is not recognized but /bin/ls is. Nothing is wrong with parmList because I tried this with execvp and it worked.
Any ideas?
If parmList[0] contains just ls, execve() won't find the ls command unless it is in the current directory.
With execve(), the value of PATH in the environment is immaterial — execve() does not use it. You'd use execvp() (or execvpe() if it is available) to execute the command on the current process's PATH.

Can't use execl to list a specific directory

I am making a C program that lists files using execl to execute the ls command. If the program is called without any command line arguments, the current directory is listed and if the user specifies a file directory as a command line argument then that directory is listed.
execl("/bin/ls", "ls", NULL); works fine for listing the current directory
execl(argv[1], "ls", NULL); is what I'm using for the command line argument. I think this works fine code wise but I can't get the syntax right when I'm making the command line argument:
./a.out /test/ls
Straight from the man page for execl
The initial argument for these functions is the pathname of a file
which is to be executed.
So if the command you want to run is ls, then the first argument to execl should be "/bin/ls".
The second argument to execl should also be "/bin/ls". This is due to the fact that the second argument to execl is passed as argv[0] to the program, and argv[0] is supposed to be the path to the program.
Thus, it's only starting at the third argument to execl that you actually start to pass real parameters to the command. So the call should look like this
execl( "/bin/ls", "/bin/ls", argv[1], NULL );

Resources