Issue passing optional parameters into C execlp() - c

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.

Related

ls: cannot access 'PATH=./': No such file or directory

I'm new to using linux and the bash command line but I was trying to see whether I could execute basic bash commands (or program) from a c file. My code is really simple and I could've sworn that it was working but suddenly it is not acknowledging that the path even exists.
char* arg[] = {"ls", "-l"};
char* environ[] = {"PATH=./", (char*)0};
execve("/bin/ls", arg, environ);/*execute the command 'ls -l' in curdir*/
I've tried to set as PATH=/bin but it's not even executing the command in that directory. it just returns a similar error.
NOTE: I have tried
char* environ[] = {"PATH=./", NULL};
I've even tried using the envp from main() and that STILL doesn't work.
This error message ...
ls: cannot access 'PATH=./': No such file or directory
... indicates that the ls utility is attempting to access a file named "PATH=./", which it does not find. That is a manifestation of the undefined behavior arising from your code ...
char* arg[] = {"ls", "-l"};
char* environ[] = {"PATH=./", (char*)0};
execve("/bin/ls", arg, environ);/*execute the command 'ls -l' in curdir*/
... on account of execve() expecting and relying upon the argument list to which arg points being terminated by a null pointer.
Although it is somewhat fraught to try to rationalize or interpret undefined behavior, you can imagine that the contents of arrays arg and environ are laid out one immediately after the other in memory, so that the combined representation of these two is the same as the representation of a four-element array of char *. This view is perhaps useful for understanding why the arg and env arrays must be terminated by null pointers in the first place.
The fix is to append a null pointer to the value of arg:
char* arg[] = {"ls", "-l", NULL};
Note also that in this particular case there is no apparent advantage to specifying an environment, so you could simplify by using execv() instead.
Note, too, that path elements other than / itself should not contain a trailing / character. This is not part of a correct name for a directory, and although you can often get away with it, it is poor form.
Put NULL in the end of arg array.
char* arg[] = {"ls", "-l", NULL};
The NULL is used to mark the end of the array.
Please always post a complete program.
As said in the man page, and as said in the comments above, you need a NULL at the end of the arguments list.
You do not need to pass anything in envp[] since you are running just ls
By convention you should pass the full path of the executable in argv[0]
In short, this works
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char* arg[] = { "/bin/ls", "-l", NULL };
execve("/bin/ls", arg, NULL);
return 0;
}

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

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);
}

C: execvp() and command line arguments

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.

malloc on (char**)

Well, I'm trying to write a shell for linux using C. Using the functions fork() and execl(), I can execute each command, but now I'm stuck trying to read the arguments:
char * command;
char ** c_args = NULL;
bytes_read = getline (&command, &nbytes, stdin);
command = strtok(command, "\n ");
int arg = 0;
c_arg = strtok(NULL, "\n ");
while( c_arg != NULL ) {
if( c_args == NULL ) {
c_args = (char**) malloc(sizeof(char*));
}
else {
c_args = (char**) realloc( c_args, sizeof(char*) * (arg + 1) );
}
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
c_arg = strtok(NULL, "\n ");
arg++;
}
...
pid_t pid = fork()
...
...
execl( <path>, command, c_args, NULL)
...
...
That way I get errors from the command when I try to pass arguments, for example:
ls -l
Gives me:
ls: cannot access p��: No such file or directory
I know that the problem is the c_args allocation. What's wrong with it?
Cheers.
You can't use execl() for a variable list of arguments; you need to use execv() or one of its variants (execve(), execvp(), etc). You can only use execl() when you know all the arguments that will be present at compile time. In most cases, a general shell won't know that. An exception is when you do something like:
execl("/bin/sh", "/bin/sh", "-c", command_line, (char *)0);
Here, you're invoking the shell to run a single string as the command line (with no other arguments). However, when you're dealing with what people type at the keyboard in a full shell, you won't have the luxury of knowing how many arguments they typed at compile time.
At its simplest, you should be using:
execvp(c_args[0], c_args);
The zeroth argument, the command name, should be what you pass to execvp(). If that's a simple file name (no /), then it will look for the command in directories on your $PATH environment variable. If the command name contains a slash, then it will look for the (relative or absolute) file name specified and execute that if it exists, and fail if it does not. The other arguments should all be in the null-terminated list c_args.
Now, there may also be other memory allocation issues; I've not scrutinized the code. You could check them, though, by diagnostic printing of the argument list:
char **pargs = c_args;
while (*pargs != 0)
puts(*pargs++);
That prints each argument on a separate line. Note that it doesn't stop until it encounters a null pointer; it is crucial that you null terminate your list of pointers to the argument strings.
This bit of your code:
c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );
looks like overkill in the usual case, and an inadequate memory allocation in the extreme cases. When you're copying strings around, allocate enough length. I see that you're using strtok() to bust apart a string — it'll do for the early incarnations of a shell, but when you get to process command lines like ls -l>$tmp, you will find strtok()'s penchant for trampling over your delimiter before you get to read it becomes a major liability. However, while you're using it, you probably don't have to copy the arguments like that; you can just set c_args[arg++] = result_from_strtok;. When you do need to copy, you should probably use strdup(); it doesn't forget to allocate enough space for the trailing '\0', for example, and neither over-allocates nor under-allocates.
Jonathan has a great answer, I just wanted to add a few more things.
You could use popen or system to just execute by the shell directly. They are usually frowned upon since they can be injected so easily, but if you are writing an open shell, I don't see the harm in using them.
If you are going for a limited shell (which accepts a sh-like syntax), take a look into wordexp. It does a lot, but in my experience it does too much, especially if you are trying to write a moderately secure interpreter (it does silly things like tilde expansion and variable substitution).

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