C execve() parameters [spawn a shell example] - c

I have to fill the parameters for:
int execve(const char *filename, char *const argv[], char *const envp[]);
If I execute this program:
#include <unistd.h>
int main() {
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL;
execve(args[0], args, NULL);
}
the shell is spawned correctly as expected.
My problem is that the shell is spawned correctly also if I pass a NULL as second parameter like that:
#include <unistd.h>
int main() {
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL;
execve(args[0], NULL, NULL);
}
So what's the purpose to use the args vector (with the "/bin/sh" + NULL) as second parameter instead of a NULL?

In this line: execve(args[0], NULL, NULL); you are simply using the first element of args array. You could also use something like char* command="/bin/sh". You have to pass something, because that's how execve() was defined. In your case you pass NULL because you don't need to pass anything.
The point of the second argument of execve() is to pass arguments to the command you are spawning. Suppose that instead of shell you simply want to execute ls, you could then pass f.e. these arguments:
#include <unistd.h>
int main() {
char *args[2];
args[0] = "/bin/ls";
args[1] = "-lh";
execve(args[0], args, NULL);
}
Also, quoting man execve:
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. envp is an array of
strings, conventionally of the form key=value, which are passed as
environment to the new program.

If you pass a null pointer as the second, or the third, argument of execve, your program is incorrect according to POSIX; both of these arguments are required to be non-null. (This is not all that clearly stated in the specification of execve, but it's in there.) I am currently typing this on an operating system that treats passing null pointers,
execve("executable", 0, 0);
as equivalent to passing empty arrays, e.g.
execve("executable", (char *[]){0}, (char *[]){0});
but it would not surprise me to learn that other operating systems would fire a segmentation fault or return -1 with errno set to EFAULT or EINVAL.
Passing empty arrays for these arguments is allowed, but the newly executed program will receive zero arguments in argc/argv if the second argument is an empty array, and/or zero environment variables in envp/environ if the third argument is an empty array. Many programs will malfunction under these conditions. For instance, it is very common to see things like
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "usage: %s one two three\n", argv[0]);
return 2;
}
// ...
}
where the program implicitly assumes that argv[0] will always be non-null.
So, you should always provide non-empty arrays for both arguments. The usual convention is that if you don't have anything else to do, you use
execve(program, (char *[]){program, 0}, environ);
which supplies the program's own name as argv[0] and no further arguments, and the same set of environment variables you got from your own parent.

Related

Why do programs with arguments passed in argc and argv get different results when executed in different ways

void main(int argc,char *argv[])
{
for (int i = 0; i < argc; i++)
{
printf("%s ", argv[i]);
}
}
when I use command ./test 1 2 3 in terminal to execute this program, I got result ./test 1 2 3 ,but when I use function execl("/usr/src/test", "1", "2", "3", NULL) in another program I got result
1 2 3,why?
The syntax of execl() is:
int execl(const char *path, const char *arg0, ..., /*, (char *)0, */);
So you have
path = "/usr/src/test"
arg0 = "1"
arg1 = "2"
arg3 = "3"
The argN parameters are put into the argv array of the new process.
You have to repeat the path as arg0 to put that into argv[0].
execl("/usr/src/test", "/usr/src/test", "1", "2", "3", NULL)
This isn't done automatically because argv[0] isn't required to be the same as the program path, and there are some situations where it isn't (for instance, login shells are invoked by adding a - prefix in argv[0]).
Check the documentation for exec() family of functions:
It is explained there as follows:
The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-terminated strings that represent the argument list available to the executed program. The first argument, by convention, should point to the filename associated with the file being executed. The list of arguments must be terminated by a null pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.

Double pointer as argument to execvp()

I am trying to execute execvp() using a custom **tokens double pointer as input, instead of argv[] on a "create a custom shell" assignment, like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
char *token;
char **tokens = malloc(sizeof(char*)*512); //512 is for the maximum input-command length
char *command=malloc(sizeof(char)*512);
int i = 0;
pid_t child_pid;
int status;
//***********take input from user*************************************
fgets(command,512,stdin);
//***********parse input*********************************************
token = strtok(command," \t");
while( token != NULL ) {
tokens[i]=token;
i ++;
token = strtok(NULL, " \t");
}
child_pid = fork();
if(child_pid == 0) {
/* This is done by the child process. */
execvp(tokens[0], tokens);
} else {
waitpid(child_pid, &status, WUNTRACED);
}
}
The problem is definately on this line:
execvp(tokens[0], tokens);
and I just can't understand why it can't be executed and print to my stdout.
I have tried this:
execvp("ls", tokens);
and it works just fine.
And this:
printf("%s\n", tokens[0]);
with the output being (according to the test input: ls ):
ls
You have several problems in your code, including:
The array of argument pointers passed to execvp() must be terminated by a null pointer. You do not ensure that.
The string obtained via fgets will include all characters up to and including the line's newline, if the buffer is large enough to accommodate it. You do not include the newline among your token delimiter characters, so for a one-word command ls, the command passed to execvp() is equivalent to "ls\n", not "ls". It is unlikely (but not impossible) that ls\n is an available command on your machine.
You do not check the return value of execvp(), or of any of your other functions, nor do you handle any errors. execvp() is special in that it returns only if there is an error, but you would have saved yourself some confusion if you had handled that case by emitting an error message.
After I correct the first two of those, your program successfully runs an "ls" command for me.
You need to allocate the memory with sizeof(char *).
char **tokens = malloc(sizeof(char *)*512);
^^----------->Size of char pointer
As of now you are allocating sizeof(char) thus invoking undefined behavior.
Also consider the first comment pointed by #n.m

Is it possible to add an argument to main by scanf?

I tried to add an argument to main by scanf. But it didn't work. The following is my program. My question is: what's wrong with this program? And is it possible to add an argument to main by scanf() instead of in the command line?
#include <stdio.h>
#include <errno.h>
int main(int argc, char* argv[]){
/*check if there is no argument, that is, argc!=2*/
if(argc != 2){
puts("Please enter an argument: ");
scanf("%s", &argv[1]);
printf("\nYou've entered argument: %s\n", argv[1]);
return 1;
}
printf("\nYou've entered argument: %s\n", argv[1]);
return 0;
}
Yes, code can effectively add arguments.
"The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination." C11dr §5.1.2.2.1 2
So this allows code to change argc and argv and change argv[0][0] (if argc > 0) - but wait, there's more...
Can code change the original elements of argv[], like argv[1], etc? That is an open question still hanging in SO here. So let us avoid that.
The way to change the contents of argv[] is to first create an alternate set argv_alt[] array and then assign argv_alt --> argv.
#include <stdio.h>
int main(int argc, char *argv[]) {
// Assume argc == 1
printf("%d '%s'\n", argc, argv[0]);
char a[3][10] = { "abc", "def", "fgh" };
char *argv_alt[] = { a[0], a[1], a[2], NULL };
argc = 3;
argv = argv_alt;
printf("%d '%s' '%s' '%s'\n", argc, argv[0], argv[1], argv[2]);
return 0;
}
Argv is allocated by the system when the user types in her arguments. If there wasn't arguments you cannot use the position 1 on that array. Although, as pointed out on the comments, you shouldn't do the way you are willing to, you could scanf arguments if you declare your own local variable.
The %s specifier in scanf expects that you pass in a pointer which points to space that has already been allocated and is big enough to hold whatever will be read.
Instead you pass in a pointer which points to an area where another pointer is stored. This causes undefined behaviour because the pointer you pass in has the wrong type.
It's not possible to extend the length of the argv array. In fact, writing to argv[n] probably causes undefined behaviour anyway.
Instead you should just read into a variable inside main.

What arguments does a c application like snmpget take?

I want to call snmpget.c from another c program in the same project. For that reason I have changed the main() into a function say get_func() which takes the same arguments. But i an not sure how to give the arguments namely argv[0]
My arguments look something like this:
char *argstr[]=
{
"v",
"1",
"c",
"public",
"-Ovq",
"192.168.1.1",
"ifInOctets.7",
"ifOutOctets.7",
NULL
};
And then
i = get_func(10, argstr);
1.Should argv[0] be the app name or path?
2.Is using char *argstr[] correct for c?
3.snmpget is not taking these arguments correctly. What could the reason be?
It works correctly with the same args in command.
Your get_func expects the arguments starting at argv[1], so your argstr argument should not start with "v" but with something else (e.g. the programme name or just an empty string if get_func doesn’t use it).
Yes. But be aware that your argstr contains non-modifiable strings, if get_func wants to modify them, you can use compound literals
char *argstr[]=
{
(char []){ "v" },
(char []){ "1" },
/* etc */
NULL
};
See 1. and 2. Additionally, argc is incorrect (must be sizeof argstr/sizeof *argstr - 1, which is 8 in your case, not 10).
Not directly an answer to your question, but consider redesigning this (depends on what exactly you’re currently doing, however). For example, write a function accepting a structure where the different options are stored (already parsed and validated) and change the old main from snmpget.c to a function only scanning and validating arguments, initializing such a structure object, and calling this function. And then, perhaps split your files into snmpget.c, snmpget_main.c, another_c_file.c (with better names, of course) and link both user interface implementations against the object file of snmpget.c.
Yes, if your main uses it. If not, just pass NULL is enough >o<
Sure, it's array of pointers. char *argstr[9] is equal to
typedef char *pchar;
pchar argstr[9];
Well, I assume you don't give appropriate argc and don't pass the app name by argv[0] because the argc is 10, but the number of content of argv is 8. (I've counted excluding NULL, but the NULL is required yet - argv[argc] should be NULL.)
To reduce mistakes, I suggest to use sizeof(argstr) / sizeof(argstr[0]) - 1 instead of calculating argc yourself.
See live example. Code:
#include <stdio.h>
int test(int argc, char *argv[]);
int main()
{
char *argstr[] = {
"test.exe",
"--opt-1",
"--output",
"test.txt",
NULL
};
int argcount = sizeof(argstr) / sizeof(argstr[0]) - 1;
return test(argcount, argstr);
}
int test(int argc, char *argv[])
{
int i;
printf("argc: %d\n", argc);
printf("program name: %s\n", argv[0]);
for (i = 1; argv[i] != NULL; i++)
{
printf("argument %d is: %s\n", i, argv[i]);
}
return 0;
}
Output:
argc: 4
program name: test.exe
argument 1 is: --opt-1
argument 2 is: --output
argument 3 is: test.txt

How to initialise a char array with command line argument in C

I have just started learning C and I have a basic question. How do I read out a command line argument. For example, if I execute:
./main "test"
How can I get the command line parameter "test" into a variable:
int main(int argc, char **argv){
char s[] is supposed to equal "test"
}
EDIT: Basically I want to create a new char array that equals argv[1].
char * s = argv[1];//to read the test
if(strcmp(s,"test") == 0){
//the command line argument is equal to the string test
}
The arguments argc and argv of the main function are used to access the string arguments passed to your program when it is started. argc is the number of arguments passed. For example when run like this - ./myprogram arg1 arg2 arg3, argc will have a value of 4. This is because along with the strings the user passes in the name of the program is also passed. That is argv[0] points to a string myprogram, argv[1] points to arg1 etc. To get the nth argument then you must access argv[n + 1].
Knowing this, to make a copy of the first argument you can do as follows
char * s = malloc(strlen(argv[1]) + 1);
strcpy(s, argv[1]);
However I would advise making sure that the argument you want doesn't point to NULL before copying it. This is where argc is handy. Before accessing argv[1] I would check if argc >= 2.
There is a much better explanation here http://crasseux.com/books/ctutorial/argc-and-argv.html or here http://www.cprogramming.com/tutorial/c/lesson14.html
Edit:
Remember to free any memory you allocate via free
eg. free(s).

Resources