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 +++

1 Answer 1

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!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .