I do not understand how execlp() works in Linux - c

I have spent the last 2 days trying to understand the execlp() system call, but yet here I am. Let me get straight to the issue.
The man page of execlp declares the system call as int execlp(const char *file, const char *arg, ...); with the description: The const char arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn.
Yet I see the system call being called like this in our text book: execlp(“/bin/sh”, ..., “ls -l /bin/??”, ...); (the "..." are for us to figure out as students). However this system call doesn´t even resemble anything like the declaration on the man page of the system call.
I am super confused. Any help is appreciated.

this prototype:
int execlp(const char *file, const char *arg, ...);
Says that execlp ìs a variable argument function. It takes 2 const char *. The rest of the arguments, if any, are the additional arguments to hand over to program we want to run - also char * - all these are C strings (and the last argument must be a NULL pointer)
So, the file argument is the path name of an executable file to be executed. arg is the string we want to appear as argv[0] in the executable. By convention, argv[0] is just the file name of the executable, normally it's set to the same as file.
The ... are now the additional arguments to give to the executable.
Say you run this from a commandline/shell:
$ ls
That'd be execlp("ls", "ls", (char *)NULL);
Or if you run
$ ls -l /
That'd be execlp("ls", "ls", "-l", "/", (char *)NULL);
So on to execlp("/bin/sh", ..., "ls -l /bin/??", ...);
Here you are going to the shell, /bin/sh , and you're giving the shell a command to execute. That command is "ls -l /bin/??". You can run that manually from a commandline/shell:
$ ls -l /bin/??
Now, how do you run a shell and tell it to execute a command ? You open up the documentation/man page for your shell and read it.
What you want to run is:
$ /bin/sh -c "ls -l /bin/??"
This becomes
execlp("/bin/sh","/bin/sh", "-c", "ls -l /bin/??", (char *)NULL);
Side note:
The /bin/?? is doing pattern matching, this pattern matching is done by the shell, and it expands to all files under /bin/ with 2 characters. If you simply did
execlp("ls","ls", "-l", "/bin/??", (char *)NULL);
Probably nothing would happen (unless there's a file actually named /bin/??) as there's no shell that interprets and expands /bin/??

The limitation of execl is that when executing a shell command or any other script that is not in the current working directory, then we have to pass the full path of the command or the script.
Example:
execl("/bin/ls", "ls", "-la", NULL);
The workaround to passing the full path of the executable is to use the function execlp, that searches for the file (1st argument of execlp) in those directories pointed by PATH:
execlp("ls", "ls", "-la", NULL);

Related

execlp function with user input C language [duplicate]

I have spent the last 2 days trying to understand the execlp() system call, but yet here I am. Let me get straight to the issue.
The man page of execlp declares the system call as int execlp(const char *file, const char *arg, ...); with the description: The const char arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn.
Yet I see the system call being called like this in our text book: execlp(“/bin/sh”, ..., “ls -l /bin/??”, ...); (the "..." are for us to figure out as students). However this system call doesn´t even resemble anything like the declaration on the man page of the system call.
I am super confused. Any help is appreciated.
this prototype:
int execlp(const char *file, const char *arg, ...);
Says that execlp ìs a variable argument function. It takes 2 const char *. The rest of the arguments, if any, are the additional arguments to hand over to program we want to run - also char * - all these are C strings (and the last argument must be a NULL pointer)
So, the file argument is the path name of an executable file to be executed. arg is the string we want to appear as argv[0] in the executable. By convention, argv[0] is just the file name of the executable, normally it's set to the same as file.
The ... are now the additional arguments to give to the executable.
Say you run this from a commandline/shell:
$ ls
That'd be execlp("ls", "ls", (char *)NULL);
Or if you run
$ ls -l /
That'd be execlp("ls", "ls", "-l", "/", (char *)NULL);
So on to execlp("/bin/sh", ..., "ls -l /bin/??", ...);
Here you are going to the shell, /bin/sh , and you're giving the shell a command to execute. That command is "ls -l /bin/??". You can run that manually from a commandline/shell:
$ ls -l /bin/??
Now, how do you run a shell and tell it to execute a command ? You open up the documentation/man page for your shell and read it.
What you want to run is:
$ /bin/sh -c "ls -l /bin/??"
This becomes
execlp("/bin/sh","/bin/sh", "-c", "ls -l /bin/??", (char *)NULL);
Side note:
The /bin/?? is doing pattern matching, this pattern matching is done by the shell, and it expands to all files under /bin/ with 2 characters. If you simply did
execlp("ls","ls", "-l", "/bin/??", (char *)NULL);
Probably nothing would happen (unless there's a file actually named /bin/??) as there's no shell that interprets and expands /bin/??
The limitation of execl is that when executing a shell command or any other script that is not in the current working directory, then we have to pass the full path of the command or the script.
Example:
execl("/bin/ls", "ls", "-la", NULL);
The workaround to passing the full path of the executable is to use the function execlp, that searches for the file (1st argument of execlp) in those directories pointed by PATH:
execlp("ls", "ls", "-la", NULL);

Bash internal commands from C program (Ubuntu v/s MacOS)

The following code successfully lists the contents of current directory on Ubuntu bash and MacOS bash.
int main() {
char* args[3];
args[0] = "ls";
args[1] = NULL;
args[2] = NULL;
execvp(args[0], args);
return 0;
}
The following code doesn't print anything on Ubuntu bash but prints ls is /bin/ls on MacOS bash.
int main() {
//pid_t pid = fork();
char * args[3];
args[0] = "type";
args[1] = "ls";
args[2] = NULL;
//if (!pid)
execvp(args[0], args);
return 0;
}
When I run type on Ubuntu bash directly, it prints ls is hashed (/bin/ls).
The difference is that type is a bash internal command while ls is not. But why does bash on Ubuntu behave differently from that on MacOS?
Ubuntu bash version: GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
MacOS bash version: GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Judging purely by version numbers (which may be an incorrect thing to do), older version prints output correctly while newer version doesn't?
You forgot to test against failure of execvp. Try at least to code:
if (execvp(args[0], args)) {
fprintf(stderr, "execvp %s failed: %s\n",
args[0], strerror(errno));
exit(EXIT_FAILURE);
}
Probably on your Ubuntu execvp of type fails. Perhaps MacOSX has some /usr/bin/type or whatever is found in your PATH.
Read carefully the documentation of execvp(3) on both systems. Consider also using strace(1) on Linux to understand what is going on (you can find a similar thing for MacOSX).
Notice that execvp works only on executable files (not on shell builtin commands)
There is no bash in your question. That is to say, nothing in the execution of that program has anything to do with bash.
execvp is, effectively, a system call which has the effect (if it succeeds, which should not be taken for granted) of replacing the current execution environment with a new process image, loading the executable from the file indicated as the first argument. The OS neither needs or seeks assistance from bash to execute the program.
If you want to use bash, you need to ask the OS to run bash. This might be useful if you want to run a bash built-in command:
char* args[] = { "bash", "-c", "type ls", 0};
execvp(args[0], args);
But since you are not invoking bash, you rely on the existence of an external command utility named type. And it is the existence or not of that utility which leads to the different behaviour. It has nothing to do with bash or any other shell.

Relation between PATH env and argv[0]

Generally argv[0] is as same as exec file name. For example:
If I execute program with ./my_program then argv[0] is ./my_program
If I execute program with /home/username/my_program then argv[0] is /home/username/my_program.
My question is, if PATH=/home/username why I can't see argv[0] value?
This is my real situation
PATH=/home/knight/bin:/home/knight/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/knight
My test program source is:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("%s\n", argv[0]);
}
My home directory is /home/knight so I can execute program directly.
knight#knight-desktop:~$ test
knight#knight-desktop:~$ ./test
./test
I can't understand, why doesn't the knight#knight-desktop:~$ test command print any result?
Because test is a shell builtin command.
And there is a big difference between ./test(it is an executable file) while test is a command passed direct to the shell of which if typed incorrect, it could have been not recognised for example lets say you use the command tst the result will be -bash: tst: command not found
To check if any word is a builtin command/reserved keyword for shell,use command type.
on terminal,
$type test
test is a shell builtin
$type if
if is a shell keyword

How to wrap command arguments with /bin/sh -c and execvpe()

Currently I'm trying to write a program that takes in a custom command and arguments from the user and execute it.
I'm trying to use execvpe that can pass in command args and env together.
However, I'm not able to figure out what's the right way to call execvpe in my case.
For example this command input: ps aux
I don't want to build up a command string as then I need to be careful about escaping quotes and semicolons (ie: JSON strings), and ideally just pass that as one arg in the array.
I see that the only way I can run this directly in bash is like this:
/bin/sh -c "ps \$0" aux
However, trying to translate that into execvpe I tried this:
char * argv[] = { "sh", "-c", "\"ps \\$0\"", "aux", NULL };
execve("/bin/sh", (char **)argv, NULL);
However this gives me a error:
aux: ps $0: command not found
What's the correct way to formulate this with execvpe?
What you did in your C code is equivalent to:
$ bash -c '"ps \$0"' aux
aux: ps $0: command not found
You can't backslash the double quotes and dollar sign. Try this: { "sh", "-c", "ps $0", "aux", NULL }
P.S. I can smell that what you are trying to do is very vulnerable.

How to create a file using execl command in c

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAXLINIE 100
main(int argc, char* argv[]) {
if (fork()==0){
execl("/bin/> temporar.txt", "/bin/> temporar.txt", ">temporar.txt", NULL);
}
}
Basically, what I am trying to do is creating a file using a process in unix, here is my code, but for some reason it does not work, I do not really understand the execl command and why the first two parameters have to be the same: execl("/bin/ls", "/bin/ls", "-l", NULL); this is working well, could someone help me ?
Thanks a lot!
first search whereis is touch:
~$ whereis touch
touch: /bin/touch /usr/bin/touch /usr/bin/X11/touch
use: int execl(const char *path, const char *arg, ...);
execl("/bin/touch", "touch", "filename", NULL);
^ ^ ^ ^
command command argument
path name
arg 0 arg 1
Consider use system() instead:
system("/bin/ls -l > temporar.txt");
Or using execl call /bin/sh to redirect stream:
execl("/bin/sh", "/bin/sh", "-c" , "/bin/ls -l >temporar.txt", NULL);
First parameter of execl is a command to execute, second is a first parameter to be passed to command (argv[0]), third and next - other arguments argv[1] ...

Resources