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

This file is not printed in the book; it demonstrates Linux features that are not described in the book (typically features that have appeared since the book was published).

The source code file is copyright 2022, Michael Kerrisk, and is licensed under the GNU Lesser 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 namespaces/userns_functions.c

  Cover of The Linux Programming Interface

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

/* userns_functions.c

   Some useful auxiliary functions when working with user namespaces.
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <sys/capability.h>
#include "userns_functions.h"
#include "tlpi_hdr.h"
/* Display calling process's (effective) credentials and capabilities */

display_creds_and_caps(char *str)
    printf("%seUID = %ld; eGID = %ld;  ", str,
            (long) geteuid(), (long) getegid());

    cap_t caps = cap_get_proc();
    if (caps == NULL)

    char *s = cap_to_text(caps, NULL);
    if (s == NULL)
    printf("capabilities: %s\n", s);

/* Update the mapping file 'map_file', with the value provided in
   'mapping', a string that defines a UID or GID mapping. A UID or
   GID mapping consists of one or more newline-delimited records
   of the form:

       ID_inside-ns    ID-outside-ns   length

   Requiring the user to supply a string that contains newlines is
   of course inconvenient for command-line use. Thus, we permit the
   use of commas to delimit records in this string, and replace them
   with newlines before writing the string to the file.

   Returns: 0 on success; -1 on error. */

update_map(char *mapping, char *map_file)
    /* Replace commas in mapping string with newlines */

    size_t map_len = strlen(mapping);
    for (int j = 0; j < map_len; j++)
        if (mapping[j] == ',')
            mapping[j] = '\n';

    int fd = open(map_file, O_RDWR);
    if (fd == -1) {
        fprintf(stderr, "ERROR: open %s: %s\n", map_file, strerror(errno));
        return -1;

    int status = 0;

    if (write(fd, mapping, map_len) != map_len) {
        fprintf(stderr, "ERROR: writing to %s: %s\n",
                map_file, strerror(errno));
        status = -1;

    return status;
/* Linux 3.19 made a change in the handling of setgroups(2) and the
   'gid_map' file to address a security issue. The issue allowed
   *unprivileged* users to employ user namespaces in order to drop groups
   from their supplementary group list using setgroups(2).  (Formerly, this
   possibility was available only to privileged processes.) The effect was to
   create possibilities for unprivileged process to access files for which
   they would not otherwise have had permission. (For further details, see
   the user_namespaces(7) man page.)

   The upshot of the 3.19 changes is that in order for a process lacking
   suitable privileges (i.e., one that lacks the CAP_SETGID capability in
   the parent user namespace) to update the 'gid_maps' file, use of the
   setgroups() system call in this user namespace must first be disabled
   by writing "deny" to one of the /proc/PID/setgroups files for this
   namespace.  That is the purpose of the following function.

   Returns: 0 on success; -1 on error. */

proc_setgroups_write(pid_t child_pid, char *str)
    char setgroups_path[PATH_MAX];

    snprintf(setgroups_path, PATH_MAX, "/proc/%ld/setgroups",
            (long) child_pid);

    int fd = open(setgroups_path, O_RDWR);
    if (fd == -1) {

        /* We may be on a system that doesn't support /proc/PID/setgroups.
           In that case, the file won't exist, and the system won't impose
           the restrictions that Linux 3.19 added. That's fine: we don't
           need to do anything in order to permit 'gid_map' to be updated.

           However, if the error from open() was something other than the
           ENOENT error that is expected for that case, let the user know. */

        if (errno == ENOENT) {
            return 0;
        } else {
            fprintf(stderr, "ERROR: open %s: %s\n", setgroups_path,
            return -1;

    int status = 0;

    if (write(fd, str, strlen(str)) == -1) {
        fprintf(stderr, "ERROR: writing to %s: %s\n", setgroups_path,
        status = -1;

    return status;


Download namespaces/userns_functions.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