Menu

Class 11: Exec


Reading
Sections 8.10 (exec) and 3.1-3.8 (file descriptors) in APUE.

Homework
Printout the Homework and answer the questions on that paper.

Exec
Ever wanted to change who you are? Well a process that is an executing instance of program foo can tranform itself into an executing instance of a different program bar. This is important because, after a fork, we often would like the child to transform into a different program.

There are six system calls to make this tranformation of a process -- all variations on the same basic idea --- and we refer to them generically as exec. In fact man -s2 exec gets you the documentation on all the functions, despite the fact that none of the functions is actually called "exec". You give exec a program name, command line arguments and, optionally, an environement (i.e. the environment variable name-value pairs) ... essentially everything you'd need to launch a new program. The differences between the six execs is in the details of how you want to pass that info. Section 8.10 explains all six very nicely.

#include <unistd.h>

int execl(const char *path, const char *arg0, ... /* const char *argn, (char *)0 */);

int execv(const char *path, char *const argv[]);

int execle(const char *path, const char *arg0, ... /* const char *argn, (char *)0,char *const envp[]*/);

int execve(const char *path, char *const argv[], char *const envp[]);

int execlp(const char *file, const char *arg0, ... /* const char *argn, (char *)0 */);

int execvp(const char *file, char *const argv[]);
Let's look at execlp first. The first argument file is the name of the program that the current process will transform into. The current environment's PATH variable will be used to find the program from the name. The subsequent arguments (and there may be as many as you like) will become the argv values for the new program. Note that the first argument, file, will not be put in argv, so you must echo it to get the usual argv[0] being the program name. The last argument must be NULL ... that essentially tells exec that the argument list is done. Look at this example, in which a foo process changes itself into a bar process by calling exec.

foo.c bar.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  printf("I am %i! I am foo!  My argv is:", getpid());
  int i;
  for(i = 0; i < argc; ++i)
    printf(" %s",argv[i]);
  printf("\n");
  execlp("./bar","./bar","fi","fi","fo",NULL);
  printf("Die Bart, die!\n");
  return 0;
}
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  printf("I am %i! I am bar!  My argv is:", getpid());
  int i;
  for(i = 0; i < argc; ++i)
    printf(" %s",argv[i]);
  printf("\n");
  return 0;
}

The output of this program is interesting primarily because we see that the process ID does not change. So we don't get a new process, the old process takes on a new role.

Exec and fork together
Most often, exec is used in combination with fork. Together they allow a process to spawn a new executing instance of a new program. This makes it possible to combine programs in sophisticated ways. Below is a simple example in which we have a program that plots a function in the range 0..B, for as many B values as the user enters. Don't know how to write a plotting program? No problem. Let gnuplot do the work for you!
/*
  plotter:  This program plots the function

  f(x) = sin(x)*sin(5*x)*10/(1+x)

  in the range 0..B, where B is read from stdin.  You may enter as
  many B values as you like, each pops up a new plot window.
  The program spawns a gnuplot process to display the plot.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
  
  /* Loop over each user input B and plot f(x) in the range 0..B. */
  double B;
  while(scanf("%lf",&B) == 1)
  {
    /* Write file of plot commands */
    FILE *fq = fopen("commands","w");    
    fprintf(fq,"set samples 500\n");
    fprintf(fq,"plot [x=0:%f] sin(x)*sin(5*x)*10/(1+x) smooth csplines\n",B);
    fclose(fq);
  
    /* Spawn a gnuplot process to plot data file */
    if (fork() == 0)
      if (execlp("gnuplot","gnuplot","-persist","commands",NULL) < 0)
      {
	fprintf(stderr,"Error!\n");
	exit(1);
      }
  }

  return 0;
}

Changing stdin & stdout before you exec
When there's a program that you want to use, and it requires input on stdin and output to stdout, you can create a sort of wrapper program that does nothing more than prepare stdin and stdout appropriately then fork. For example, the program sssk below copies stdin to stdout while removing whitespace. We can provide a new program that does the same thing, but gets an input file name from the command line, and produces output in a file of the same name, but with .mod on the end ... without changing sssk, or knowing anything about how it works:

sssk.c wrapper.c
/* 
   sssk: copies stdin to stdout removing whitespace.
   What a hack!  Never write a program like this!
*/
#include <unistd.h>

int main()
{
  char c;
  while(read(0,&c,1) == 1 && (isspace(c) || write(1,&c,1) == 1));
  write(1,"\n",1);
  return 0;
}
/*
  This program demonstrates how we can use a program that requires
  input from stdin and output from stdout (sssk in this case) with
  code to set stdin/stdout appropriately and then execs.
 */
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  /* Check for sufficient agruments! */
  if (argc < 2) 
  { 
    fprintf(stderr,"usage: wrapper <file>\n"); 
    return 1; 
  }

  /* Set stdin to the file given by argv[1]. */
  int fdin;
  if ((fdin = open(argv[1],O_RDONLY)) < 0)
  {
    fprintf(stderr,"wrapper: \"%s\" not found!\n",argv[1]); 
    return 2;
  }
  dup2(fdin,0);

  /* Set stdout to the file given by argv[1] with .mod at the end. */
  char *buff = (char*)malloc(strlen(argv[1]) + 5);
  buff[0] = '\0';
  strcpy(buff,argv[1]);
  strcat(buff,".mod");  
  int fdout;
  if ((fdout = open(buff,O_WRONLY|O_CREAT)) < 0)
  {
    fprintf(stderr,"wrapper: \"%s\" could not be opened!\n",argv[1]); 
    return 3;
  }
  dup2(fdout,1);

  /* exec into sssk ... if exec retuns, it failed! */
  execlp("./sssk","sssk",NULL);
  fprintf(stderr,"wrapper: exec failed!\n"); 
  return 4;
}

It's worth noting that you can do this kind of thing really easily with shell scripts.