Recently I've been working on a project at work which required me to be managing shell commands from a C program. It's quite an interesting project, when its ready I hope to do a bit of a post on it. I've got quite a limited history of C experience but I do quite enjoy working with the language. Nowhere in my past experience with it had I ever been invoking other programs within my code, so this was definitely a learning experience for me.
For the purposes of any examples here is the source for the program I will be invoking as test_args
1 #include <stdio.h>
2
3 int main(int argc, char **argv) {
4 FILE *log;
5 int i;
6
7 log = fopen("./out.txt", "a+"); // assume it worked
8 fprintf(log, "Called with %d args as: ", argc);
9
10 for ( i = 0; i < argc ; i++ ) {
11 fprintf(log, "\"");
12 fprintf(log, argv[i]);
13 fprintf(log, "\"");
14 fprintf(log, " ");
15 }
16
17 fprintf(log, "\n");
18
19 return 0;
20 }
I've opted to write the arguments passed to the program to a text file for the sake of simplicity.
execve(2) and friends
The main system call to invoke an external program within your code is execve(2). Now that I've said that I'm going to retract nearly all of it. From the manpage
execve() transforms the calling process into a new process.
we see something a bit different, and initially for me a source of confusion. To begin with I was simply calling execve(2) in my code and letting it do its thing. This is not correct as any veteran of execve(2) knows and will probably baulk at my stupidity for not getting it right away.
So what does the manpage mean in this case, well as I found out to my cost, it means exactly what it says. The moment execve(2) is entered, provided it doesn't encounter an error, your program becomes the program invoked by execve(2). It will run to the end of that invoked program and return you whatever that program would if you'd run it yourself. This was my first tripping point and I quickly found out that I needed to fork(2) my way around it. As an example, most definitely not to be followed, here is what I had started with
1 int ret;
2 char *envp[] = { NULL };
3 char *argv[] = { "./test_args", "hello", "there", NULL };
4
5 ret = execve("./test_args", argv, envp);
6
7 // do things based on ret from test_args down here
as I've described, with the exception of an error, nothing after the execve(2) would be run.
Now I mentioned friends, execve(2) is wrapped by a number of functions such as execl, execle, execlp, execv, execvp and execvP which are documented in exec(3). These functions primarily offer a selection of different ways to wrap up the call to execve(2) for your convenience. I won't touch on these again as you can look into them yourself.
Another friend of execve(2) is system(2), this function takes a command string and passes it to sh(1) the default shell for interpretation and execution. For small commands it can be a quick way of getting things done but for my needs I didn't feel like it gave me enough control.
fork(2)ing around to keep in your program
So I needed to first fork(2) my program before calling execve(2) to invoke the external program. Now the important thing to remember about fork(2) is that your program branches into a child and parent. The parent is your controlling process and the child is what you will offer up to the gods of execve(2) to be transformed into the program you wish to invoke.
Here follows a crude example...
1 pid_t pid;
2 int status;
3 char *envp[] = { NULL };
4 char *argv[] = { "./test_args", "hello", "there", NULL };
5
6 switch ( pid = fork() ) {
7 case -1:
8 perror("fork()");
9 exit(EXIT_FAILURE);
10 case 0: // in the child
11 status = execve("./test_args", argv, envp);
12 exit(status); // only happens if execve(2) fails
13 default: // in parent
14 if ( waitpid(pid, &status, 0) < 0 ) {
15 perror("waitpid()");
16 exit(EXIT_FAILURE);
17 }
18
19 if ( WIFEXITED(status) ) {
20 // return status from child, ie ./test_args
21 exit( WEXITSTATUS(status) );
22 }
23 exit(EXIT_FAILURE);
24 }
So again we setup the envp and argv string arrays to pass to execve(2) but before that we declare a variable of type pid_t. This is a type to store Process Identifiers, PIDs, which are returned from fork(2).
I've opted for a switch statement here, you could equally use an if but I prefer the readability and clarity of the switch.
So we fork our process and save the returned PID value as we'll need this in the parent. If our PID has a value of -1 then fork(2) has failed. Now we have the fun part, the child will always get a 0 return value from fork(2), if you want the PID in the child you can call getpid(2). So inside the child we make the call to execve(2) and capture the result of the call in the event of an error. From this point on the child is test_args and not our main program. Now the fork(2) call will return the value of the PID for the child to the parent, so we can catch this with the default case. In this rather simple example we wait for the child process to complete with waitpid(2) which will fill in our status variable with the return value of the child. Next we check the status to ensure the child exited and didn't crash or die, we then return the status code or indicate failure.
Thats the basic gist of using execve(2) to invoke external programs in your code. In a later post I'll describe how I went about handling more free form argument lists.