I am writing a simple shell in C. It is actually coming along quite well with i/o redirection and such. One thing that I'd like to add is a way to switch between versions of the exec* functions. Right now I'm sticking to execlp(), execvp(), and execve().
I have all the arguments I want to pass in an array called argv. It is a null terminated array of null terminated strings, so it works fine with execv*, but I can't think of how to get it to work with execlp().
This is what I have now:
if (strcmp(exec_opt, "vp") == 0)
error = execvp(argv[0], argv); /* Execute vp */
else if(strcmp(exec_opt, "lp") == 0)
error = execlp(argv[0], "", argv); /* Execute lp */
else if(strcmp(exec_opt, "ve") == 0)
error = execve(argv[0], argv, environ); /* Execute ve */
else
{
// throw errors about exec_opt
}
if(error != 0)
{
// do something about it
}
In this configuration the compiler doesn't baff at the syntax, but it also doesn't work.
I've also tried
error = execlp(argv[0], (char*) argv); /* As a single string */
char* argv1 = argv[1]; /* don't pass command itself */
error = execlp(argv[0], argv1);
Which do various odd but ultimately incorrect things. Is there a way for me to turn my array into a variable argument list? passing it directly (which makes the most type-sense, since variable argument lists are char* argv[]) yields a compiler error about casting incompatible pointers.
You can't really use execlp() with the array. To use execlp(), you have to write out:
execlp(array[0], array[0], (char *)0);
execlp(array[0], array[0], array[1], (char *)0);
execlp(array[0], array[0], array[1], array[2], (char *)0);
execlp(array[0], array[0], array[1], array[2], array[3], (char *)0);
...
for each alternative number of arguments. This is why execvp() was added to the repertoire (it wasn't part of 7th Edition UNIX™ in 1978, but was part of SUS v2 in 1997). Now you just need execvpe(), which does not exist AFAIK (and I don't know why it isn't provided, either).
7th Edition UNIX did have excevp()
Dave said:
The 7th Edition manual does list execvp.
And ... it does, partially. I think we have an erratum to report since the manual actually contains:
NAME
execl, execv, execle, execve, execlp, execvp, exec, exece, environ – execute a file
SYNOPSIS
execl(name, arg0, arg1, ..., argn, 0)
char *name, *arg0, *arg1, ..., *argn;
execv(name, argv)
char *name, *argv[ ];
execle(name, arg0, arg1, ..., argn, 0, envp)
char *name, *arg0, *arg1, ..., *argn, *envp[ ];
execve(name, argv, envp);
char *name, *argv[ ], *envp[ ];
extern char **environ;
DESCRIPTION
So, execvp() is listed in the NAME section, but there is no synopsis for execvp() (which is what I looked at to come to the conclusion it was missing). There is a reference to execvp() on the next page:
Execlp and execvp are called with the same arguments as execl and execv, but duplicate the shell’s actions in searching for an executable file in a list of directories. The directory list is obtained from the environment.
So, I excuse myself because I scanned the SYNOPSIS and execvp() was omitted from the synopsis. But actually, the system call was present in 7th Edition Unix. I don't think anyone is about to re-release the manuals with the omission fixed.
My print copy (yes, I do have printed properly bound versions of both Volume 1 (ISBN 0-03-061742-1) and Volume 2 (ISBN 0-03-061743-X) of the manual from way back then; I obtained them circa 1989) has the same omission in the SYNOPSIS section.
Try using avcall. I haven't used it myself, I just found an interesting mention of it here: Passing parameters dynamically to variadic functions
Related
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;
}
I have issues creating simple C program which takes arguments from command line, the last argument is path to the file. Program runs cat command on given file, and then runs tr on the result of cat. Tr gets arguments from command line(other than the last argument). I am getting errors:
Missing operand.
write error: Broken Pipe.
I am not sure where the mistake is...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WRITE_END 1
#define READ_END 0
int main(int argc, char* argv[]){
if(argc < 2){
printf("\nPROVIDE AN ARGUMENT\n");
return 1;
}
const char * file = argv[argc - 1];
char ** args = calloc(argc - 2, sizeof(char*));
for( int i = 1; i<argc-2; i++){
args[i - 1 ] = argv[i];
}
int fd[2];
pipe(fd);
pid_t child;
if((child = fork()) == -1)return 2;
if(child == 0){
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
close(fd[WRITE_END]);
execlp("cat", "cat", file, (char*)NULL);
exit(1);
}
else{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
close(fd[READ_END]);
execlp("tr", "tr", *args, (char*)NULL);
exit(1);
}
close(fd[0]);
close(fd[1]);
wait(0);
wait(0);
return 0;
}
There are a few problems here that are keeping you from getting this to work. First, as mentioned by Nate Eldredge in a comment, there are problems with the allocation and copying of the all-but-last arguments to variable args. Second, your use of execlp has a slight problem in that the arguments should include an extra argument corresponding to the name of the program run (not the same as the file opened as the executable, lots of people get confused about this point). Third, as also mentioned by Nate, you need to call execvp in the branch of the if-else corresponding to the parent process (the "else" branch). Its second argument will need to be an array of pointers to character, the last of which is NULL.
So taking these one at a time. First, you need to allocate argc slots for args to use it in something like the way you intend:
char ** args = calloc(argc, sizeof(char*));
memcpy(args, argv, sizeof(char*)*(argc -1));
The first line allocates an array of character pointers the same size as the arg. list. The second line copies all but the last pointer in argv to the corresponding location in args and leaves the last one as NULL (calloc initialized the storage for it to be zero, and you need the last pointer in args to be a null pointer if you're going to pass it to execvp, which you will). Note that you're not duplicating all of the storage under argv, just the pointers in the first dimension (remember: argv[0] is a pointer and argv[0][0] is the first character in the program name).
Note that your use of close and dup was fine. I don't know why anyone objected to that unless they forgot that allocating a file descriptor always takes the lowest-numbered descriptor that is unused. That's about the most important thing about descriptor tables as originally used in UNIX.
Next, the call to execlp that overlays the child process created by fork with "cat" is missing an argument. It should be:
execlp("cat", "cat", file, (char*)NULL);
That extra "cat" in there is the value cat will receive when it enters main() as argv[0]. You're probably noticing that this looks like you could lie about the name of the program you're running with the exec__ functions, and you can (but you can't completely hide having done it).
Finally, that second execlp call. You can't pass arguments through as if they were typed on the command line, in one big string: exec in any form doesn't use a shell to invoke the other program and it's not going to parse the command line for you. In addition, the way you were (apparently, if I've read your intent correctly) trying to concatenate the argument strings was also not right (see above comments about args allocation and the memcpy call). You have to break out individual arguments and pass them to it. So if you have an array of pointer to character and the last one is NULL, like you'll have in args after the changes I indicated for allocating and copying data, then you can just pass args to execvp:
execvp("tr", args);
These aren't huge errors and a lot of people make these kinds of mistakes when starting out with manipulating the argument list and using the fork and exec functions. A lot of people make mistakes trying to use a pipe between parent and child processes but you seem to have gotten that part right.
One last thing: the lines downstream in execution from the exec__ calls only get executed if there's an error performing the actual replacement of the running program with the new one. Errors on the command line of "cat" or "tr", for example, won't cause exec__ to fail. Errors like lack of permission to execute the file given as the first argument or absence of the file will cause the exec__ functions to fail. Unless exec returns an error, nothing downstream of the exec call is executed in the process in which it is executed (a successful exec never returns).
I am trying to use execlp in a c program to run another c program. The exec function does call the program, but it does not pass the integer arguments correctly. My exec call is:
int exec_arg_1, exec_arg_2;
if(pid == 0){
printf("Repeat Number: %d, Process Number: %d\n", exec_arg_1, exec_arg_2);
execlp( "/home/drlight/Desktop/asp/Assignment_3/philosopher.o",
"philosopher.o", &exec_arg_1, &exec_arg_2, NULL );
printf("Exec didn't work...\n");
}
I assign values to the exec_arg ints, and print them right before to make sure they're correct, but the philosopher.o function just reads 0's from the location. If I run philosopher.o from the command line, it reads the arguments normally.
Arguments to programs are always strings.
int exec_arg_1, exec_arg_2;
if (pid == 0){
printf("Repeat Number: %d, Process Number: %d\n", exec_arg_1, exec_arg_2);
char arg1[20], arg2[20];
snprintf(arg1, sizeof(arg1), "%d", exec_arg_1);
snprintf(arg2, sizeof(arg2), "%d", exec_arg_2);
execlp( "/home/drlight/Desktop/asp/Assignment_3/philosopher.o",
"philosopher.o", arg_1, arg_2, NULL );
fprintf(stderr, "Exec didn't work...\n");
exit(1);
}
Note that execlp() is really only useful with a fixed number of arguments (or, a least, when there is a small fixed upper bound on the number of arguments). Most often, execvp() is a better choice.
This page includes plenty of usage examples....
EDIT : Added code snippet from the link
A code snippet from the link above
static void show_info_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
die(_("no info viewer handled the request"));
}
The best practice would be having a look at the execlp(3) man page in the first place I reckon.
EDIT : Added explanation of execlp(3) fro mthe man page
FreeBSD man page explains the usage of execlp() as follows
int
execlp(const char *file, const char *arg, ... /*, (char *)0 */);
The initial argument for these functions is the pathname of a file which
is to be executed.
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 file name associated
with the file being executed. The list of arguments must be terminated
by a NULL pointer.
The functions execlp(), execvp(), and execvP() will duplicate the actions
of the shell in searching for an executable file if the specified file
name does not contain a slash ``/'' character. For execlp() and
execvp(), search path is the path specified in the environment by
``PATH'' variable. If this variable is not specified, the default path
is set according to the _PATH_DEFPATH definition in <paths.h>, which is
set to ``/usr/bin:/bin''
PS : some information, such as default search path, mat vary based on your system
Your issue is that execlp takes string pointers not integers for its arg parameters. From the manpage
int execlp(const char *file, const char *arg, ...);
You will have to convert these to strings before passing them to execlp.
#include<stdio.h>
#include<unistd.h>
#define MAXDIGITS 22
main()
{
int exec_arg_1, exec_arg_2;
char execStr1[MAXDIGITS + 1];
char execStr2[MAXDIGITS + 1];
exec_arg_1 = 750;
exec_arg_2 = 25;
snprintf(execStr1, MAXDIGITS + 1, "%d", exec_arg_1);
snprintf(execStr2, MAXDIGITS + 1, "%d", exec_arg_2);
printf("Our Strings: %s, %s\n", execStr1, execStr2);
execlp("/home/drlight/Desktop/asp/Assignment_3/philosopher.o", "philosopher.o", execStr1, execStr2, NULL);
}
You need to make sure MAXDIGITS is large enough to hold all the decimal digits of your number, but 25 should be sufficient for even longs on most current platforms. However keep in mind that in future versions of gcc and/or with different compilers this may be different. Don't forget to leave room for the negative either. You can check these maximums by importing limits.h and printing the values of INT_MAX and LONG_MAX.
#include<stdio.h>
#include<limits.h>
main(int argc, char * argv[])
{
printf("Int max: %d\n", INT_MAX);
printf("Long max: %ld\n", LONG_MAX);
}
I am writing a program using execl to execute my exe file which is testing and it's work very well and display the output in the Linux CLI. But I have not idea how to change the execl to execv, although I know both of the system call will give the same value. I am confused with the array argument for execv system call
This is my execl sample program
int main(void)
{
int childpid;
if((childpid = fork()) == -1 )
{
perror("can't fork");
exit(1);
}
else if(childpid == 0)
{
execl("./testing","","",(char *)0);
exit(0);
}
else
{
printf("finish");
exit(0);
}
}
can I know how to change the execl to execv. What I read from online, we must set the file path for my exe file and the argument of array . What type of argument need to set for the array in order to ask the program to execute the testing exe file ?
https://support.sas.com/documentation/onlinedoc/sasc/doc/lr2/execv.htmIs it the link consist of the thing I want ? But what I read from it ,the command is request the list the file,not execute the file. Correct me I make any mistake
In order to see the difference, here is a line of code executing a ls -l -R -a
with execl(3):
execl("/bin/ls", "ls", "-l", "-R", "-a", NULL);
with execv(3):
char* arr[] = {"ls", "-l", "-R", "-a", NULL};
execv("/bin/ls", arr);
The char(*)[] sent to execv will be passed to /bin/ls as argv (in int main(int argc, char **argv))
According to the man page the use of execv is quite simple. The first argument is the path as a string to the program you want to execute. The second is an array of string that will be used as the arguments of the program you want to execute. It is the kind of array you get if you get the argv array in your main function.
So the array you will pass as a parameter will be the array received in the main function of the program you execute with execv.
By convention, the first argument should be the program name (the one you try to execute) but it is not mandatory (but strongly recommended since it is the behaviour a lot of programs are expecting). Each other string in the array should be an individual argument.
And of course, the array should be terminated with a NULL pointer to mark the end.
Array example: ["prog_name", "arg1", "arg2", "arg3", NULL]
[] is your array, each string separated with a coma is a frame of your array and at the end you have the null frame.
I hope I am clear enough!
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 };