In the last article about namespaces, we looked at the PID namespace. This time, we’ll take a look at the mount namespace.

Mount namespaces isolate a set of mount points for a process or processes in a given namespace, providing the opportunity for different processes on a system to have different views of the host’s filesystem. When a new mount namespace is created with either of the system calls clone or unshare, it is done so with a copy of the set of mounts that exist in the mount namespace where the calling process resides. Any subsequent changes to the set of mounts in the ‘parent’ or ‘child’ mount namespaces, are not reflected in the other …. unless the mount is a shared mount.

In the article where we discussed PID namespaces, we discovered that the process we cloned into a new PID namespace, appeared as if it resided in the PID namespace of its parent. This is because the parent and child processes share the same mount namespace and view of the filesystem, and when we listed the contents of /proc/$$/ns for the process in the new PID namespace (which has PID 1), we actually listed the namespaces for the ‘init’ process in the global PID namespace! That’s very confusing.

To get around this problem, as well as cloning our process into a new PID namespace, we’ll also create a new mount namespace for it at the same time, and mount a new procfs. The program we started with in the previous article, has been adapted as invoke_ns2.c, and can be downloaded here.

There are a couple of trivial changes; the first is the addition of the -m command line option to specify creation of a new mount namespace, and the second is the parsing of that option, which adds the CLONE_NEWNS constant to the clone flags:

// Parse command line options and construct arguments
// to be passed to childFunction
while ((option = getopt(argc, argv, "+hvpm")) != -1) {
    switch (option) {
        case 'm':
            flags |= CLONE_NEWNS;
            break;
        case 'p':
            flags |= CLONE_NEWPID;
            break;
        case 'v':
            args.verbose = 1;
            break;
        case 'h':
            usage(argv[0]);
            exit(EXIT_SUCCESS);
        default:
            usage(argv[0]);
            exit(EXIT_FAILURE);
    }
}

Notice that the clone flag constant for the mount namespace does not follow the convention associated with the constants for the other namespace types, i.e. CLONE_NEWTYPE. Instead of CLONE_NEWMNT, it is CLONE_NEWNS, and that’s because it was the first to appear in the kernel, and points to the notion that there may not have been other namespaces planned at that point in time.

The bigger change to the program, is the following addition to the childFunction, which gets called by the child process:

// Mount new proc instance in new mount namespace if and only if
// the child exists in both a new PID and mount namespace
if ((args->flags & CLONE_NEWPID) && (args->flags & CLONE_NEWNS)) {
    if (mount("none", "/proc", "", MS_REC|MS_PRIVATE, NULL) == -1)
        perror(" Child: childFunction: mount");
    if (mount("proc", "/proc", "proc", 0, NULL) == -1)
        perror(" Child: childFunction: mount");
}

This code makes the mount private to the new namespace (systemd makes all mounts ‘shared’ by default), and mounts a new procfs at /proc within the newly created mount namespace. Having compiled the modified code contained in invoke_ns2.c, we can run the program with the additional argument:

$ sudo ./invoke_ns -vpm env PS1="ns # " bash --norc
[sudo] password for wolf: 
Parent: PID of parent is 16179
Parent: PID of child is 16180
 Child: PID of child is 1
 Child: executing command env ...
ns #

On the face of it, nothing is any different to the version we ran when we were looking at PID namespaces. Listing the namespace directories for the parent and child in the global namespace yields:

$ sudo ls -l /proc/16179/ns && sudo ls -l /proc/16180/ns
[sudo] password for wolf: 
total 0
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 uts -> uts:[4026531838]
total 0
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 mnt -> mnt:[4026532118]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 pid -> pid:[4026532173]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Sep  7 13:07 uts -> uts:[4026531838]

As expected, the parent and child processes reside in different PID and mount namespaces. When listing the namespaces from the bash command shell prompt of the child process within the new namespaces, however, we get what we expect this time:

ns # ls -l /proc/$$/ns
total 0
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 mnt -> mnt:[4026532118]
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 pid -> pid:[4026532173]
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Sep  7 13:09 uts -> uts:[4026531838]

PID 1 within the new PID and mount namespaces, reports exactly the same as PID 16180 in the global namespaces; they are a representation of the same process. Additionally, if we do a process listing inside the new namespaces, we get just two processes, the bash command shell and ps itself:

ns # ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 bash
    3 pts/0    00:00:00 ps

In the next article, we’ll take a look at the UTS namespace.