procexec/demo_clone.c

This is procexec/demo_clone.c, an example to accompany the book, The Linux Programming Interface.

This file is not printed in the book; it is a supplementary file for Chapter 28.

The source code file is copyright 2024, Michael Kerrisk, and is licensed under the GNU General Public License, version 3.

In the listing below, the names of Linux system calls and C library functions are hyperlinked to manual pages from the Linux man-pages project, and the names of functions implemented in the book are hyperlinked to the implementations of those functions.

 

Download procexec/demo_clone.c

  Cover of The Linux Programming Interface

Function list (Bold in this list means a function is not static)

/* demo_clone.c

   Demonstrate the use of the Linux-specific clone() system call.

   This program creates a child using clone(). Various flags can be included
   in the clone() call by specifying option letters in the first command-line
   argument to the program (see usageError() below for a list of the option
   letters). Note that not all combinations of flags are valid. See Section
   28.2.1 or the clone(2) man page about information about which flag
   combinations are required or invalid.

   This program is Linux-specific.
*/
#define _GNU_SOURCE
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mman.h>
#include "print_wait_status.h"
#include "tlpi_hdr.h"

#ifndef CHILD_SIG
#define CHILD_SIG SIGUSR1       /* Signal to be generated on termination
                                   of cloned child */
#endif

typedef struct {        /* For passing info to child startup function */
    int    fd;
    mode_t umask;
    int    exitStatus;
    int    signal;
} ChildParams;
static int              /* Startup function for cloned child */
childFunc(void *arg)
{
    printf("Child:  PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());

    ChildParams *cp = arg;

    /* The following changes may affect parent */

    umask(cp->umask);
    if (close(cp->fd) == -1)
        errExit("child:close");
    if (signal(cp->signal, SIG_DFL) == SIG_ERR)
        errExit("child:signal");

    return cp->exitStatus;      /* Child terminates now */
}
static void             /* Handler for child termination signal */
grimReaper(int sig)
{
    /* UNSAFE: This handler uses non-async-signal-safe functions
       (printf(), strsignal(); see Section 21.1.2) */

    int savedErrno = errno;             /* In case we change 'errno' here */

    printf("Caught signal %d (%s)\n", sig, strsignal(sig));

    errno = savedErrno;
}
static void
usageError(char *progName)
{
    fprintf(stderr, "Usage: %s [arg]\n", progName);
#define fpe(str) fprintf(stderr, "        " str)
    fpe("'arg' can contain the following letters:\n");
    fpe("    d - share file descriptors (CLONE_FILES)\n");
    fpe("    f - share file-system information (CLONE_FS)\n");
    fpe("    s - share signal dispositions (CLONE_SIGHAND)\n");
    fpe("    v - share virtual memory (CLONE_VM)\n");
    exit(EXIT_FAILURE);
}
int
main(int argc, char *argv[])
{
    printf("Parent: PID=%ld PPID=%ld\n", (long) getpid(), (long) getppid());

    /* Set up an argument structure to be passed to cloned child, and
       set some process attributes that will be modified by child */

    ChildParams cp;                     /* Passed to child function */
    cp.exitStatus = 22;                 /* Child will exit with this status */

    const mode_t START_UMASK = S_IWOTH; /* Initial umask setting */
    umask(START_UMASK);
    cp.umask = S_IWGRP;                 /* Child sets umask to this value */

    cp.fd = open("/dev/null", O_RDWR);  /* Child will close this fd */
    if (cp.fd == -1)
        errExit("open");

    cp.signal = SIGTERM;                /* Child will change disposition */
    if (signal(cp.signal, SIG_IGN) == SIG_ERR)  errExit("signal");

    /* Initialize clone flags using command-line argument (if supplied) */

    int flags = 0;
    if (argc > 1) {
        for (char *p = argv[1]; *p != '\0'; p++) {
            if      (*p == 'd') flags |= CLONE_FILES;
            else if (*p == 'f') flags |= CLONE_FS;
            else if (*p == 's') flags |= CLONE_SIGHAND;
            else if (*p == 'v') flags |= CLONE_VM;
            else usageError(argv[0]);
        }
    }

    /* Allocate stack for child */

    const int STACK_SIZE = 65536;

    char *stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
    if (stack == MAP_FAILED)
        errExit("mmap");

    char *stackTop = stack + STACK_SIZE;  /* Assume stack grows downward */

    /* Establish handler to catch child termination signal */

    struct sigaction sa;
    if (CHILD_SIG != 0) {
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        sa.sa_handler = grimReaper;
        if (sigaction(CHILD_SIG, &sa, NULL) == -1)
            errExit("sigaction");
    }

    /* Create child; child commences execution in childFunc() */

    if (clone(childFunc, stackTop, flags | CHILD_SIG, &cp) == -1)
        errExit("clone");

    /* Now that child has been created, we can deallocate the stack */

    munmap(stack, STACK_SIZE);

    /* Parent falls through to here. Wait for child; __WCLONE option is
       required for child notifying with signal other than SIGCHLD. */

    int status;
    pid_t pid = waitpid(-1, &status, (CHILD_SIG != SIGCHLD) ? __WCLONE : 0);
    if (pid == -1)
        errExit("waitpid");

    printf("    Child PID=%ld\n", (long) pid);
    printWaitStatus("    Status: ", status);

    /* Check whether changes made by cloned child have affected parent */

    printf("Parent - checking process attributes:\n");
    if (umask(0) != START_UMASK)
        printf("    umask has changed\n");
    else
        printf("    umask has not changed\n");

    ssize_t s = write(cp.fd, "Hello world\n", 12);
    if (s == -1 && errno == EBADF)
        printf("    file descriptor %d has been closed\n", cp.fd);
    else if (s == -1)
        printf("    write() on file descriptor %d failed (%s)\n",
                cp.fd, strerror(errno));
    else
        printf("    write() on file descriptor %d succeeded\n", cp.fd);

    if (sigaction(cp.signal, NULL, &sa) == -1)
        errExit("sigaction");
    if (sa.sa_handler != SIG_IGN)
        printf("    signal disposition has changed\n");
    else
        printf("    signal disposition has not changed\n");

    exit(EXIT_SUCCESS);
}

 

Download procexec/demo_clone.c

Note that, in most cases, the programs rendered in these web pages are not free standing: you'll typically also need a few other source files (mostly in the lib/ subdirectory) as well. Generally, it's easier to just download the entire source tarball and build the programs with make(1). By hovering your mouse over the various hyperlinked include files and function calls above, you can see which other source files this file depends on.

Valid XHTML 1.1