O sistema de arquivos montado sobre “/” está inacessível - então por que ele pode ser desmontado com “umount /”?

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

Esta nova montagem não altera o diretório físico ao qual / se refere. Veja também /proc/self/root . Alterar o diretório raiz por processo é o que faz o chroot . Quando eu acesso / , ele ainda mostra o conteúdo do meu sistema de arquivos raiz ext4, não o 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 /

Exceto que umount opera na montagem tmpfs. Como isso funciona - qual é a diferença entre esses dois tipos de operação?

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

Ambiente

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

Rastreamento de chamada do sistema

Eu tentei repetir isso com umount / executado em strace -f , mas não mostra nada mais esclarecedor. umount apenas chama umount2() e não passa nenhum sinalizador (o segundo parâmetro é 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 +++
    
por sourcejedi 19.07.2018 / 12:15

1 resposta

2

Olhando para o Linux v4.17, acho que podemos dizer que umount on / é equivalente a umount on /.. . E /.. acessa o "topo da pilha de pontos de montagem".

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

Esse comportamento obscuro de .. parece ser permitido pelo POSIX. Apenas diz "Como um caso especial, no diretório raiz, ponto-ponto pode se referir ao próprio diretório raiz." (POSIX.1-2017, secção 4.13 "Resolução do nome do caminho").

Se você deseja generalizar isso e definir o comportamento implementado por umount em outros pontos de montagem, fazer uma comparação com .. não funciona tão bem. O termo "topo da pilha de pontos de montagem" ainda se aplica, embora não soe como uma definição muito formal:).

Código fonte do kernel

link

Veja onde path_mountpoint() chama 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;
}

O comentário para follow_mount() soou como relevante para a pergunta. E menciona follow_dotdot() como o usuário principal, o que sugeriu essa linha de investigação.

/*
 * 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)

Eu estava pensando em como .. ("dotdot") poderia passar de um ponto de montagem para seu pai, por exemplo %código%. Mas eu não tinha considerado anteriormente que isso poderia "pular para o topo da pilha de pontos de montagem". Isso acontece mesmo se a montagem superior não for a que contém o diretório /tmp/.. original!

    
por 19.07.2018 / 13:23

Tags