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

MNT 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 MNT 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 MNT namespace where the calling process resides. Any subsequent changes to the set of mounts in the 'parent' or 'child' MNT 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 MNT 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 MNT 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 MNT 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 MNT 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 MNT namespace
if ((args->flags & CLONE_NEWPID) && (args->flags & CLONE_NEWNS)) {  
    if (mount("none", "/proc", "", MS_REC|MS_PRIVATE, NULL) == -1)
        perror(" Child: mount");
    if (mount("proc", "/proc", "proc", 0, NULL) == -1)
        perror(" Child: 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 MNT 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 9721  
Parent: PID of child is 9722  
 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/9721/ns && sudo ls -l /proc/9722/ns
[sudo] password for wolf:
total 0  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 ipc -> ipc:[4026531839]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 mnt -> mnt:[4026531840]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 net -> net:[4026531956]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 pid -> pid:[4026531836]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 user -> user:[4026531837]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 uts -> uts:[4026531838]  
total 0  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 ipc -> ipc:[4026531839]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 mnt -> mnt:[4026532430]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 net -> net:[4026531956]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 pid -> pid:[4026532431]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 user -> user:[4026531837]  
lrwxrwxrwx 1 root root 0 Mar 11 15:17 uts -> uts:[4026531838]

As expected, the parent and child processes reside in different PID and MNT 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 Mar 11 15:20 ipc -> ipc:[4026531839]  
lrwxrwxrwx 1 root root 0 Mar 11 15:20 mnt -> mnt:[4026532430]  
lrwxrwxrwx 1 root root 0 Mar 11 15:20 net -> net:[4026531956]  
lrwxrwxrwx 1 root root 0 Mar 11 15:20 pid -> pid:[4026532431]  
lrwxrwxrwx 1 root root 0 Mar 11 15:20 user -> user:[4026531837]  
lrwxrwxrwx 1 root root 0 Mar 11 15:20 uts -> uts:[4026531838]

PID 1 within the new PID and MNT namespaces, reports exactly the same as PID 9722 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/7    00:00:00 bash
    4 pts/7    00:00:00 ps

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