Home » Support » Unix C Programming FAQ - Section 8

Go to the first, previous, next, last section, table of contents.


Examples

Catching SIGCHLD

#include <sys/types.h>  /* include this before any other sys headers */
#include <sys/wait.h>   /* header for waitpid() and various macros */
#include <signal.h>     /* header for signal functions */
#include <stdio.h>      /* header for fprintf() */
#include <unistd.h>     /* header for fork() */

void sig_chld(int);     /* prototype for our SIGCHLD handler */

int main()
{
    struct sigaction act;
    pid_t pid;

    /* Assign sig_chld as our SIGCHLD handler */
    act.sa_handler = sig_chld;

    /* We don't want to block any other signals in this example */
    sigemptyset(&act.sa_mask);

    /*
     * We're only interested in children that have terminated, not ones
     * which have been stopped (eg user pressing control-Z at terminal)
     */
    act.sa_flags = SA_NOCLDSTOP;

    /*
     * Make these values effective. If we were writing a real
     * application, we would probably save the old value instead of
     * passing NULL.
     */
    if (sigaction(SIGCHLD, &act, NULL) < 0)
    {
        fprintf(stderr, "sigaction failed\n");
        return 1;
    }

    /* Fork */
    switch (pid = fork())
    {
    case -1:
        fprintf(stderr, "fork failed\n");
        return 1;

    case 0:                         /* child -- finish straight away */
        _exit(7);                   /* exit status = 7 */

    default:                        /* parent */
        sleep(10);                  /* give child time to finish */
    }

    return 0;
}

/*
 * The signal handler function -- only gets called when a SIGCHLD
 * is received, ie when a child terminates
 */
void sig_chld(int signo)
{
    int status, child_val;

    /* Wait for any child without blocking */
    if (waitpid(-1, &status, WNOHANG) < 0)
    {
        /*
         * calling standard I/O functions like fprintf() in a
         * signal handler is not recommended, but probably OK
         * in toy programs like this one.
         */
        fprintf(stderr, "waitpid failed\n");
        return;
    }

    /*
     * We now have the info in 'status' and can manipulate it using
     * the macros in wait.h.
     */
    if (WIFEXITED(status))                /* did child exit normally? */
    {
        child_val = WEXITSTATUS(status); /* get child's exit status */
        printf("child's exited normally with status %d\n", child_val);
    }
}

Reading the process table -- SUNOS 4 version

#define _KMEMUSER
#include <sys/proc.h>
#include <kvm.h>
#include <fcntl.h>

char regexpstr[256];
#define INIT            register char *sp=regexpstr;
#define GETC()          (*sp++)
#define PEEKC()         (*sp)
#define UNGETC(c)       (--sp)
#define RETURN(pointer) return(pointer);
#define ERROR(val)
#include <regexp.h>

pid_t
getpidbyname(char *name,pid_t skipit)
{
    kvm_t *kd;
    char **arg;
    int error;
    char *p_name=NULL;
    char expbuf[256];
    char **freeme;
    int curpid;
    struct user * cur_user;
    struct user myuser;
    struct proc * cur_proc;


    if((kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL))==NULL){
        return(-1);
    }
    sprintf(regexpstr,"^.*/%s$",name);
    compile(NULL,expbuf,expbuf+256,'\0');

    while(cur_proc=kvm_nextproc(kd)){
        curpid = cur_proc->p_pid;
        if((cur_user=kvm_getu(kd,cur_proc))!=NULL){
            error=kvm_getcmd(kd,cur_proc,cur_user,&arg,NULL);
            if(error==-1){
                if(cur_user->u_comm[0]!='\0'){
                    p_name=cur_user->u_comm;
                }
            }
            else{
                p_name=arg[0];
            }
        }
        if(p_name){
            if(!strcmp(p_name,name)){
                if(error!=-1){
                    free(arg);
                }
                if(skipit!=-1 && ourretval==skipit){
                    ourretval=-1;
                }
                else{
                    close(fd);
                    break;
                }
                break;
            }
            else{
                if(step(p_name,expbuf)){
                    if(error!=-1){
                        free(arg);
                    }
                    break;
                }
            }
        }
        if(error!=-1){
            free(arg);
        }
        p_name=NULL;
    }
    kvm_close(kd);
    if(p_name!=NULL){
        return(curpid);
    }
    return (-1);
}

Reading the process table -- SYSV version

pid_t
getpidbyname(char *name,pid_t skipit)
{
    DIR  *dp;
    struct dirent *dirp;
    prpsinfo_t retval;
    int fd;
    pid_t ourretval=-1;

    if((dp=opendir("/proc"))==NULL){
        return -1;
    }
    chdir("/proc");
    while((dirp=readdir(dp))!=NULL){
        if(dirp->d_name[0]!='.'){
            if((fd=open(dirp->d_name,O_RDONLY))!=-1){
                if(ioctl(fd,PIOCPSINFO,&retval)!=-1){
                    if(!strcmp(retval.pr_fname,name)){
                        ourretval=(pid_t)atoi(dirp->d_name);
                        if(skipit!=-1 && ourretval==skipit){
                            ourretval=-1;
                        }
                        else{
                            close(fd);
                            break;
                        }
                    }
                }
                close(fd);
            }
        }
    }
    closedir(dp);
    return ourretval;
}

Reading the process table -- AIX 4.2 version

#include <stdio.h>
#include <procinfo.h>

int getprocs(struct procsinfo *, int, struct fdsinfo *,
             int, pid_t *, int);

pid_t getpidbyname(char *name, pid_t *nextPid)
{
  struct procsinfo  pi;
  pid_t             retval = (pid_t) -1;
  pid_t             pid;

  pid = *nextPid;

  while(1)
  {
    if(getprocs(&pi, sizeof pi, 0, 0, &pid, 1) != 1)
      break;

    if(!strcmp(name, pi.pi_comm))
    {
      retval = pi.pi_pid;
      *nextPid = pid;
      break;
    }
  }

  return retval;
}

int main(int argc, char *argv[])
{
  int   curArg;
  pid_t pid;
  pid_t nextPid;

  if(argc == 1)
  {
    printf("syntax: %s <program> [program ...]\n",argv[0]);
    exit(1);
  }

  for(curArg = 1; curArg < argc; curArg++)
  {
    printf("Process IDs for %s\n", argv[curArg]);

    for(nextPid = 0, pid = 0; pid != -1; )
      if((pid = getpidbyname(argv[curArg], &nextPid)) != -1)
        printf("\t%d\n", pid);
  }
}

Reading the process table using popen and ps

#include <stdio.h>      /* FILE, sprintf, fgets, puts */
#include <stdlib.h>     /* atoi, exit, EXIT_SUCCESS */
#include <string.h>     /* strtok, strcmp */
#include <sys/types.h>  /* pid_t */
#include <sys/wait.h>   /* WIFEXITED, WEXITSTATUS */

char *procname(pid_t pid)
{
   static char line[133], command[80], *linep, *token, *cmd;
   FILE *fp;
   int status;

   if (0 == pid) return (char *)0;

   sprintf(command, "ps -p %d 2>/dev/null", pid);
   fp = popen(command, "r");
   if ((FILE *)0 == fp) return (char *)0;

   /* read the header line */
   if ((char *)0 == fgets(line, sizeof line, fp))
   {
      pclose(fp);
      return (char *)0;
   }

   /* figure out where the command name is from the column headings.
    * (BSD-ish machines put the COMMAND in the 5th column, while SysV
    * seems to put CMD or COMMAND in the 4th column.)
    */
   for (linep = line; ; linep = (char *)0)
   {
      if ((char *)0 == (token = strtok(linep, " \t\n")))
      {
         pclose(fp);
         return (char *)0;
      }
      if (0 == strcmp("COMMAND", token) || 0 == strcmp("CMD", token))
      { /*  we found the COMMAND column */
         cmd = token;
         break;
      }
   }

   /* read the ps(1) output line */
   if ((char *)0 == fgets(line, sizeof line, fp))
   {
      pclose(fp);
      return (char *)0;
   }

   /* grab the "word" underneath the command heading... */
   if ((char *)0 == (token = strtok(cmd, " \t\n")))
   {
      pclose(fp);
      return (char *)0;
   }

   status = pclose(fp);
   if (!WIFEXITED(status) || 0 != WEXITSTATUS(status))
     return (char *)0;

   return token;
}

int main(int argc, char *argv[])
{
   puts(procname(atoi(argv[1])));
   exit(EXIT_SUCCESS);
}

Daemon utility functions

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* closeall() -- close all FDs >= a specified value */

void closeall(int fd)
{
    int fdlimit = sysconf(_SC_OPEN_MAX);

    while (fd < fdlimit)
      close(fd++);
}

/* daemon() - detach process from user and disappear into the background
 * returns -1 on failure, but you can't do much except exit in that case
 * since we may already have forked. This is based on the BSD version,
 * so the caller is responsible for things like the umask, etc.
 */

/* believed to work on all Posix systems */

int daemon(int nochdir, int noclose)
{
    switch (fork())
    {
        case 0:  break;
        case -1: return -1;
        default: _exit(0);          /* exit the original process */
    }

    if (setsid() < 0)               /* shoudn't fail */
      return -1;

    /* dyke out this switch if you want to acquire a control tty in */
    /* the future -- not normally advisable for daemons */

    switch (fork())
    {
        case 0:  break;
        case -1: return -1;
        default: _exit(0);
    }

    if (!nochdir)
      chdir("/");

    if (!noclose)
    {
        closeall(0);
        open("/dev/null",O_RDWR);
        dup(0); dup(0);
    }

    return 0;
}

/* fork2() -- like fork, but the new process is immediately orphaned
 *            (won't leave a zombie when it exits)
 * Returns 1 to the parent, not any meaningful pid.
 * The parent cannot wait() for the new process (it's unrelated).
 */

/* This version assumes that you *haven't* caught or ignored SIGCHLD. */
/* If you have, then you should just be using fork() instead anyway.  */

int fork2()
{
    pid_t pid;
    int rc;
    int status;

    if (!(pid = fork()))
    {
        switch (fork())
        {
          case 0:  return 0;
          case -1: _exit(errno);    /* assumes all errnos are <256 */
          default: _exit(0);
        }
    }

    if (pid < 0 || waitpid(pid,&status,0) < 0)
      return -1;

    if (WIFEXITED(status))
      if (WEXITSTATUS(status) == 0)
        return 1;
      else
        errno = WEXITSTATUS(status);
    else
      errno = EINTR;  /* well, sort of :-) */

    return -1;
}

An example of using the above functions:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <errno.h>

int daemon(int,int);
int fork2(void);
void closeall(int);

#define TCP_PORT 8888

void errexit(const char *str)
{
    syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
    exit(1);
}

void errreport(const char *str)
{
    syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
}

/* the actual child process is here. */

void run_child(int sock)
{
    FILE *in = fdopen(sock,"r");
    FILE *out = fdopen(sock,"w");
    int ch;

    setvbuf(in, NULL, _IOFBF, 1024);
    setvbuf(out, NULL, _IOLBF, 1024);

    while ((ch = fgetc(in)) != EOF)
      fputc(toupper(ch), out);

    fclose(out);
}

/* This is the daemon's main work -- listen for connections and spawn */

void process()
{
    struct sockaddr_in addr;
    int addrlen = sizeof(addr);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    int flag = 1;
    int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                        &flag, sizeof(flag));

    if (rc < 0)
      errexit("setsockopt");

    addr.sin_family = AF_INET;
    addr.sin_port = htons(TCP_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    rc = bind(sock, (struct sockaddr *) &addr, addrlen);
    if (rc < 0)
      errexit("bind");

    rc = listen(sock, 5);
    if (rc < 0)
      errexit("listen");

    for (;;)
    {
        rc = accept(sock, (struct sockaddr *) &addr, &addrlen);

        if (rc >= 0)
          switch (fork2())
          {
            case 0:  close(sock); run_child(rc); _exit(0);
            case -1: errreport("fork2"); close(rc); break;
            default: close(rc);
          }
    }
}

int main()
{
    if (daemon(0,0) < 0)
    {
        perror("daemon");
        exit(2);
    }

    openlog("test", LOG_PID, LOG_DAEMON);

    process();

    return 0;
}

Modem handling example

/* issue some simple modem commands
 * requires the name of a serial device (preferably a dial-out device,
 * or a non-modem-control device) as its only parameter.
 * If you don't have functional dial-out devices, then move CLOCAL
 * to CFLAGS_TO_SET instead.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>   /* maybe; system-dependent */
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#define CFLAGS_TO_SET (CREAD | HUPCL)
#define CFLAGS_TO_CLEAR (CSTOPB | PARENB | CLOCAL)

enum flowmode { NoFlow, HardFlow, SoftFlow };

/* system-dependent */
#define CFLAGS_HARDFLOW (CRTSCTS)

#define EXAMPLE_BAUD B19200
#define EXAMPLE_FLOW HardFlow

static void die(const char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

static int close_and_complain(int fd, const char *msg, int err)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(err));
    if (fd >= 0)
        close(fd);
    errno = err;
    return -1;
}

int open_port(const char *name, speed_t baud, enum flowmode flow)
{
    int flags;
    struct termios attr;

    int fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY);

    if (fd < 0)
        return close_and_complain(-1, "open", errno);

    /* set vaguely sensibe settings */

    if (tcgetattr(fd, &attr) < 0)
        return close_and_complain(fd, "tcgetattr", errno);

    /* no special input or output processing */

    attr.c_iflag = (flow == SoftFlow) ? (IXON | IXOFF) : 0;
    attr.c_oflag = 0;

    /* set 8-bit character size and miscellanous control modes */

    attr.c_cflag &= ~(CSIZE | CFLAGS_TO_CLEAR | CFLAGS_HARDFLOW);
    attr.c_cflag |= (CS8 | CFLAGS_TO_SET);
    if (flow == HardFlow)
        attr.c_cflag |= CFLAGS_HARDFLOW;

    /* local modes */

    attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG);

    /* special characters -- most disabled by prior settings anyway */

    {
        int i;
#ifdef _POSIX_VDISABLE
        attr.c_cc[0] = _POSIX_VDISABLE;
#else
        attr.c_cc[0] = fpathconf(fd, _PC_VDISABLE);
#endif
        for (i = 1; i < NCCS; i++)
            attr.c_cc[i] = attr.c_cc[0];
    }

    attr.c_cc[VSTART] = 0x11;
    attr.c_cc[VSTOP] = 0x13;

    /* timing controls for read() */

    attr.c_cc[VMIN] = 1;
    attr.c_cc[VTIME] = 0;

    /* baud rate */

    cfsetispeed(&attr, baud);
    cfsetospeed(&attr, baud);

    /* write settings */

    if (tcsetattr(fd, TCSANOW, &attr) < 0)
        return close_and_complain(fd, "tcsetattr", errno);

    /* turn off O_NONBLOCK if the device remembered it */

    flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0)
        return close_and_complain(fd, "fcntl(GETFL)", errno);
    if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
        return close_and_complain(fd, "fcntl(SETFL)", errno);

    return fd;
}

/* some simple timing utilities */

/* add SECS and USECS to *TV */

static void timeradd(struct timeval *tv, long secs, long usecs)
{
    tv->tv_sec += secs;
    if ((tv->tv_usec += usecs) >= 1000000)
    {
        tv->tv_sec += tv->tv_usec / 1000000;
        tv->tv_usec %= 1000000;
    }
}

/* Set *RES = *A - *B, returning the sign of the result */

static int timersub(struct timeval *res,
                    const struct timeval *a, const struct timeval *b)
{
    long sec = a->tv_sec - b->tv_sec;
    long usec = a->tv_usec - b->tv_usec;

    if (usec < 0)
        usec += 1000000, --sec;

    res->tv_sec = sec;
    res->tv_usec = usec;

    return (sec < 0) ? (-1) : ((sec == 0 && usec == 0) ? 0 : 1);
}

/* this doesn't try and cope with pathological strings (e.g. ababc)
 * timeout is in millisecs
 * A more usual approach to this is to use alarm() for the timeout.
 * This example avoids signal handling for simplicity and to illustrate
 * an alternative approach
 */

int expect(int fd, const char *str, int timeo)
{
    int matchlen = 0;
    int len = strlen(str);
    struct timeval now,end,left;
    fd_set fds;
    char c;

    gettimeofday(&end, NULL);
    timeradd(&end, timeo/1000, timeo%1000);

    while (matchlen < len)
    {
        gettimeofday(&now, NULL);
        if (timersub(&left, &end, &now) <= 0)
            return -1;

        FD_ZERO(&fds);
        FD_SET(fd, &fds);
        if (select(fd+1, &fds, NULL, NULL, &left) <= 0)
            return -1;

        if (read(fd, &c, 1) != 1)
            return -1;

        if (isprint((unsigned char)c) || c == '\n' || c == '\r')
            putchar(c);
        else
            printf("\\x%02x", c);

        if (c == str[matchlen])
            ++matchlen;
        else
            matchlen = 0;
    }

    return 0;
}

int main(int argc, char **argv)
{
    int fd;
    unsigned char c;

    if (argc < 2)
        die("no port specified");

    setvbuf(stdout, NULL, _IONBF, 0);

    fd = open_port(argv[1], EXAMPLE_BAUD, EXAMPLE_FLOW);
    if (fd < 0)
        die("cannot open port");

    write(fd, "AT\r", 3);
    if (expect(fd, "OK", 5000) < 0)
    {
        write(fd, "AT\r", 3);
        if (expect(fd, "OK", 5000) < 0)
        {
            tcflush(fd, TCIOFLUSH);
            close(fd);
            die("no response to AT");
        }
    }

    write(fd, "ATI4\r", 5);
    expect(fd, "OK", 10000);

    putchar('\n');

    tcflush(fd, TCIOFLUSH);
    close(fd);

    return 0;
}

Job Control example


/* functions to spawn foreground/background jobs */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* Some of the functions below fail if the control tty can't be
 * located, or if the calling process isn't in the foreground. In the
 * first case, we are assuming that a foreground process will have the
 * ctty open on either stdin, stdout or stderr, and we return ENOTTY
 * if it isn't. In the second case, we return EPERM if a
 * non-foreground process attempts to put something into the
 * foreground (probably overly paranoid) except for the special case
 * of foreground_self().
 */

/* assign the terminal (open on ctty) to a specific pgrp. This wrapper
 * around tcsetpgrp() is needed only because of extreme bogosity on the
 * part of POSIX; conforming systems deliver STGTTOU if tcsetpgrp is
 * called from a non-foreground process (which it almost invariably is).
 * A triumph of spurious consistency over common sense.
 */

int assign_terminal(int ctty, pid_t pgrp)
{
    sigset_t sigs;
    sigset_t oldsigs;
    int rc;

    sigemptyset(&sigs);
    sigaddset(&sigs,SIGTTOU);
    sigprocmask(SIG_BLOCK, &sigs, &oldsigs);

    rc = tcsetpgrp(ctty, pgrp);

    sigprocmask(SIG_SETMASK, &oldsigs, NULL);

    return rc;
}

/* Like fork(), but does job control. FG is true if the newly-created
 * process is to be placed in the foreground. (This implicitly puts
 * the calling process in the background, so watch out for tty I/O
 * after doing this.) PGRP is -1 to create a new job, in which case
 * the returned pid is also the pgrp of the new job, or specifies an
 * existing job in the same session (normally used only for starting
 * second or subsequent process in a pipeline).  */

pid_t spawn_job(int fg, pid_t pgrp)
{
    int ctty = -1;
    pid_t pid;

    /* If spawning a *new* foreground job, require that at least one
     * of stdin, stdout or stderr refer to the control tty, and that
     * the current process is in the foreground.
     * Only check for controlling tty if starting a new foreground
     * process in an existing job.
     * A session without a control tty can only have background jobs
     */

    if (fg)
    {
        pid_t curpgrp;

        if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
            && (curpgrp = tcgetpgrp(ctty = 0)) < 0
            && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
            return errno = ENOTTY, (pid_t)-1;

        if (pgrp < 0 && curpgrp != getpgrp())
            return errno = EPERM, (pid_t)-1;
    }

    switch (pid = fork())
    {
        case -1: /* fork failure */
            return pid;

        case 0: /* child */

            /* establish new process group, and put ourselves in
             * foreground if necessary
             * unclear what to do if setpgid fails ("can't happen")
             */

            if (pgrp < 0)
                pgrp = getpid();

            if (setpgid(0,pgrp) == 0 && fg)
                assign_terminal(ctty, pgrp);

            return 0;

        default: /* parent */

            /* establish child process group here too. */

            if (pgrp < 0)
                pgrp = pid;

            setpgid(pid, pgrp);

            return pid;
    }

    /*NOTREACHED*/
}

/* Kill job PGRP with signal SIGNO */

int kill_job(pid_t pgrp, int signo)
{
    return kill(-pgrp, signo);
}

/* Suspend job PGRP */

int suspend_job(pid_t pgrp)
{
    return kill_job(pgrp, SIGSTOP);
}

/* Resume job PGRP in background */

int resume_job_bg(pid_t pgrp)
{
    return kill_job(pgrp, SIGCONT);
}

/* resume job PGRP in foreground */

int resume_job_fg(pid_t pgrp)
{
    pid_t curpgrp;
    int ctty;

    if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
        && (curpgrp = tcgetpgrp(ctty = 0)) < 0
        && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
        return errno = ENOTTY, (pid_t)-1;

    if (curpgrp != getpgrp())
        return errno = EPERM, (pid_t)-1;

    if (assign_terminal(ctty, pgrp) < 0)
        return -1;

    return kill_job(pgrp, SIGCONT);
}


/* put ourselves in the foreground, e.g. after suspending a foreground
 * job
 */

int foreground_self()
{
    pid_t curpgrp;
    int ctty;

    if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
        && (curpgrp = tcgetpgrp(ctty = 0)) < 0
        && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
        return errno = ENOTTY, (pid_t)-1;

    return assign_terminal(ctty, getpgrp());
}

/* closeall() - close all FDs >= a specified value */

void closeall(int fd)
{
    int fdlimit = sysconf(_SC_OPEN_MAX);

    while (fd < fdlimit)
        close(fd++);
}

/* like system(), but executes the specified command as a background
 * job, returning the pid of the shell process (which is also the pgrp
 * of the job, suitable for kill_job etc.)
 * If INFD, OUTFD or ERRFD are non-NULL, then a pipe will be opened and
 * a descriptor for the parent end of the relevent pipe stored there.
 * If any of these are NULL, they will be redirected to /dev/null in the
 * child.
 * Also closes all FDs > 2 in the child process (an oft-overlooked task)
 */

pid_t spawn_background_command(const char *cmd,
                               int *infd, int *outfd, int *errfd)
{
    int nullfd = -1;
    int pipefds[3][2];
    int error = 0;

    if (!cmd)
        return errno = EINVAL, -1;

    pipefds[0][0] = pipefds[0][1] = -1;
    pipefds[1][0] = pipefds[1][1] = -1;
    pipefds[2][0] = pipefds[2][1] = -1;

    if (infd && pipe(pipefds[0]) < 0)
        error = errno;
    else if (outfd && pipe(pipefds[1]) < 0)
        error = errno;
    else if (errfd && pipe(pipefds[2]) < 0)
        error = errno;

    if (!error && !(infd && outfd && errfd))
    {
        nullfd = open("/dev/null",O_RDWR);
        if (nullfd < 0)
            error = errno;
    }

    if (!error)
    {
        pid_t pid = spawn_job(0, -1);
        switch (pid)
        {
            case -1: /* fork failure */
                error = errno;
                break;

            case 0: /* child proc */

                dup2(infd ? pipefds[0][0] : nullfd, 0);
                dup2(outfd ? pipefds[1][1] : nullfd, 1);
                dup2(errfd ? pipefds[2][1] : nullfd, 2);
                closeall(3);

                execl("/bin/sh","sh","-c",cmd,(char*)NULL);

                _exit(127);

            default: /* parent proc */

                close(nullfd);
                if (infd)
                    close(pipefds[0][0]), *infd = pipefds[0][1];
                if (outfd)
                    close(pipefds[1][1]), *outfd = pipefds[1][0];
                if (errfd)
                    close(pipefds[2][1]), *errfd = pipefds[2][0];

                return pid;
        }
    }

    /* only reached if error */

    {
        int i,j;
        for (i = 0; i < 3; ++i)
            for (j = 0; j < 2; ++j)
                if (pipefds[i][j] >= 0)
                    close(pipefds[i][j]);
    }

    if (nullfd >= 0)
        close(nullfd);

    return errno = error, (pid_t) -1;
}

/*--------------------------------------------------------------------*/
/* This bit is a somewhat trivial example of using the above.         */

pid_t bgjob = -1;
volatile int signo = 0;

#ifndef WCOREDUMP
 /* If WCOREDUMP is missing, you might want to supply a correct
  * definition for your platform (this is usually (status & 0x80) but
  * not always) or punt (as in this example) by assuming no core dumps.
  */
# define WCOREDUMP(status) (0)
#endif

int check_children()
{
    pid_t pid;
    int status;
    int count = 0;

    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
    {
        if (pid == bgjob && !WIFSTOPPED(status))
            bgjob = -1;

        ++count;

        if (WIFEXITED(status))
            fprintf(stderr,"Process %ld exited with return code %d\n",
                    (long)pid, WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            fprintf(stderr,"Process %ld killed by signal %d%s\n",
                    (long)pid, WTERMSIG(status),
                    WCOREDUMP(status) ? " (core dumped)" : "");
        else if (WIFSTOPPED(status))
            fprintf(stderr,"Process %ld stopped by signal %d\n",
                    (long)pid, WSTOPSIG(status));
        else
            fprintf(stderr,"Unexpected status - pid=%ld, status=0x%x\n",
                    (long)pid, status);
    }

    return count;
}

void sighandler(int sig)
{
    if (sig != SIGCHLD)
        signo = sig;
}

int main()
{
    struct sigaction act;
    int sigcount = 0;

    act.sa_handler = sighandler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGQUIT,&act,NULL);
    sigaction(SIGTERM,&act,NULL);
    sigaction(SIGTSTP,&act,NULL);
    sigaction(SIGCHLD,&act,NULL);

    fprintf(stderr,"Starting background job 'sleep 60'\n");
    bgjob = spawn_background_command("sleep 60", NULL, NULL, NULL);
    if (bgjob < 0)
    {
        perror("spawn_background_command");
        exit(1);
    }
    fprintf(stderr,"Background job started with id %ld\n", (long)bgjob);
    while (bgjob >= 0)
    {
        if (signo)
        {
            fprintf(stderr,"Signal %d caught\n", signo);
            if (sigcount++)
                kill_job(bgjob, SIGKILL);
            else
            {
                kill_job(bgjob, SIGTERM);
                kill_job(bgjob, SIGCONT);
            }
        }

        if (!check_children())
            pause();
    }

    fprintf(stderr,"Done - exiting\n");
    return 0;
}