1
$ unshare -rm
# mount --bind /tmp /

# awk '{ if ($2 == "/") print $0; }' < /proc/mounts
/dev/mapper/alan_dell_2016-fedora / ext4 rw,seclabel,relatime 0 0
tmpfs / tmpfs rw,seclabel,nosuid,nodev 0 0

This new mount does not change the physical directory which / refers to. See also /proc/self/root. Changing the per-process root directory is what what chroot does. When I access /, it still shows the contents of my ext4 root filesystem, not the tmpfs:

# stat -f /tmp --format %T
tmpfs
# stat -f / --format %T
ext2/ext3
# ls -l -d -i /tmp
22161 drwxrwxrwt. 44 nfsnobody nfsnobody 1000 Jul 19 09:49 /tmp
# ls -l -d -i /
2 dr-xr-xr-x. 19 nfsnobody nfsnobody 4096 Jul  7 09:21 /

Except that umount operates on the tmpfs mount. How does this work - what is the difference between these two types of operation?

# umount /
# awk '{ if ($2 == "/") print $0; }' < /proc/mounts
/dev/mapper/alan_dell_2016-fedora / ext4 rw,seclabel,relatime 0 0

Environment

$ uname -r  # Kernel version
4.17.3-200.fc28.x86_64

System call trace

I tried repeating this with umount / run under strace -f, but it doesn't show anything more illuminating. umount just calls umount2(), and it doesn't pass any flags (the second parameter is zero).

# strace -f umount /
...
statfs("/", {f_type=EXT2_SUPER_MAGIC, f_bsize=4096, f_blocks=10288440, f_bfree=2384614, f_bavail=1856230, f_files=2621440, f_ffree=2253065, f_fsid={val=[1557883181, 1665775425]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_RELATIME}) = 0
stat("/sbin/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
stat("/sbin/fs.d/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
stat("/sbin/fs/umount.ext4", 0x7ffd79ccbb40) = -1 ENOENT (No such file or directory)
umount2("/", 0)                         = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
2

Looking inside Linux v4.17, I think we can say umount on / is equivalent to umount on /... And /.. accesses the "top of [the] mountpoint pile".

# stat -f / --format %T
ext2/ext3
# stat -f /.. --format %T
tmpfs

This obscure behaviour of .. seems to be permitted by POSIX. It only says "As a special case, in the root directory, dot-dot may refer to the root directory itself." (POSIX.1-2017, section 4.13 "Pathname Resolution").

If you want to generalize this and define the behaviour implemented by umount on other mount points, making a comparison with .. does not work so well. The term "top of [the] mountpoint pile" still applies, although it doesn't sound like a very formal definition :).

Kernel source code

https://elixir.bootlin.com/linux/v4.17/source/fs/namei.c

See where path_mountpoint() calls follow_mount().

/**
 * path_mountpoint - look up a path to be umounted
 * @nd:     lookup context
 * @flags:  lookup flags
 * @path:   pointer to container for result
 *
 * Look up the given name, but don't attempt to revalidate the last component.
 * Returns 0 and "path" will be valid on success; Returns error otherwise.
 */
static int
path_mountpoint(struct nameidata *nd, unsigned flags, struct path *path)
{
    const char *s = path_init(nd, flags);
    int err;
    if (IS_ERR(s))
        return PTR_ERR(s);
    while (!(err = link_path_walk(s, nd)) &&
        (err = mountpoint_last(nd)) > 0) {
        s = trailing_symlink(nd);
        if (IS_ERR(s)) {
            err = PTR_ERR(s);
            break;
        }
    }
    if (!err) {
        *path = nd->path;
        nd->path.mnt = NULL;
        nd->path.dentry = NULL;
        follow_mount(path);
    }
    terminate_walk(nd);
    return err;
}

The comment for follow_mount() sounded like it was relevant to the question. And it mentions follow_dotdot() as the main user, which suggested this line of investigation.

/*
 * Skip to top of mountpoint pile in refwalk mode for follow_dotdot()
 */
static void follow_mount(struct path *path)
{
    while (d_mountpoint(path->dentry)) {
        struct vfsmount *mounted = lookup_mnt(path);
        if (!mounted)
            break;
        dput(path->dentry);
        mntput(path->mnt);
        path->mnt = mounted;
        path->dentry = dget(mounted->mnt_root);
    }
}

static int follow_dotdot(struct nameidata *nd)

I had been thinking about how .. ("dotdot") could traverse from a mount point to its parent, e.g. /tmp/... But I hadn't previously considered that doing so might "skip to [the] top of [the] mountpoint pile". This happens even if the top mount isn't the one that contains the original /tmp directory!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.