【问题标题】:'unshare' does not work as expected in C api'unshare' 在 C api 中无法按预期工作
【发布时间】:2016-04-24 16:05:18
【问题描述】:

此命令序列有效:

unshare --fork --pid --mount 
umount /proc
mount -t proc proc /proc
umount /dev/pts
mount -t devpts devpts /dev/pts

但是,相应的 C 程序并没有按预期工作(似乎它没有卸载以前的 /proc,并且它还提供 EBUSY 尝试卸载 devpts):

unshare(CLONE_NEWPID | CLONE_NEWNS );
int pid = fork();
if (pid != 0) {
    int status;
    waitpid(-1, &status, 0);
    return status;
}

printf("My pid: %i\n", getpid()); // It prints 1 as expected

umount("/proc"); // Returns 0

system("mount"); // Should print error on mtab, but it prints the previous mounted filesystems

mount("proc", "/proc", "proc",
      MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_NODEV,
      NULL));  // Returns 0

umount("/dev/pts");  // Returns -1 errno = 0 (??)

mount("devpts", "/dev/pts", "devpts", 
      MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_NODEV,
      NULL) ); // Returns -1 errno = EBUSY

为了便于阅读,我在这里省略了错误检查

我认为 unshare 或 unmount 不能按预期工作:即使它返回零,似乎也不会卸载 /proc(如果我尝试在此之后执行 system("mount"),它会打印已安装的文件系统)。

【问题讨论】:

  • 使用 perror 代替 printf - 它提供有关 ERRNO 的信息
  • 好吧,反正我认为mountdevpts上的EBUSY是由umount/mountproc的“静默失败”引起的
  • 你的umounts 对我来说失败了,所以我用umount2("<mountpoint>", MNT_DETACH) 替换了它们。但是,这并不能完全解决问题:它在全局范围内卸载(并重新安装)/proc 和 /dev/pts!什么鬼?
  • 为什么你认为 mount 应该在 mtab 上打印错误?
  • 因为你已经卸载了/proc,而/etc/mtab/是/proc/self/mounts的链接

标签: c linux linux-namespaces


【解决方案1】:

unshare bash != unshare c

unshare - 使用一些与父级未共享的命名空间运行程序

所以基本上使用 --fork 你是从 /bin/sh 的 /bin/bash (无论你用什么执行脚本)与 --pid 和 --mount 选项分叉。 “fork”后跟“unshare”

unshare - 解除部分流程执行上下文(当前流程)的关联 您正在从 init 中取消共享,然后再分叉。

CLONE_NEWPID 是“克隆”标志而不是“取消共享”

因此,取决于您要实现的目标 - 我假设您正在尝试使“/proc”和“/dev/pts”专用于子进程。

这里有一个 mount --bind 本地文件夹的小例子:

# mkdir mnt point
# touch point/point.txt
# mount --bind point mnt
# ls mnt
point.txt

# ./unshare
My pid: 28377
Child:
point.txt
Parent:

# ls mnt

代码:

#define _GNU_SOURCE
#include <sched.h>

int main(int argc, char *argv[])
{
        /** umount global */
        system("umount mnt/");
        int pid = fork();
        if (pid != 0) {
                int status;
                waitpid(-1, &status, 0);
                printf("Parent:\n");
                /* and here we don't */
                system("ls mnt/");
                return status;
        }
        /* unshare */
        unshare(CLONE_FS | CLONE_NEWNS);
        printf("My pid: %i\n", getpid()); // It prints 1 as expected
        /* mount exclusively */
        system("mount --bind point/ mnt/");
        printf("Child:\n");
        /* here we see it */
        system("ls mnt/"); 

        return 0;
}

还有一个 bash 的好例子: http://karelzak.blogspot.ru/2009/12/unshare1.html

继续:

mount 依赖于 /etc/mtab,它并不总是指向 /proc/mounts 的符号链接

所以用 ls -la 检查 /etc/mtab。

同时检查 /dev/pts 上的 umount 代码:

int ret = umount("/dev/pts");
int errsv = errno;
if(ret == -1) {
  printf("Error on umount: %s\n", strerror(errsv));
}

我很确定它已被使用 - 使用 fuser /dev/pts/ 检查它

** 已编辑 **

最后 - 我不确定你是否只能在命名空间中卸载 procfs(我认为这是不可能的)

但您可以在命名空间中挂载自己的 procfs 副本:

# mount -t proc proc /proc/

现在只有你的进程通过 ps -e 可见。

【讨论】:

  • 为什么要挂载/卸载mnt/?我的问题是/proc。 CLONE_NEWPID 自 Linux 3.8 起也是 unshare 的标志(检查man7.org/linux/man-pages/man2/unshare.2.html)。您的代码不会分离子执行(printf 和 getpid 不返回 1,因为您只分离文件系统)。如问题中所述,bash 代码运行良好,包括unmount /proc。
  • 简单:你不能 umount proc 就是这样。而且您不需要卸载 proc,unshare 根本无法按您的预期工作。问题是您的新命名空间也在 proc 上中继。如果您的问题是关于卸载 proc,则有必要安排合适的标题。
  • 不,先生:1. 可以卸载 /proc(shell 命令有效!)并且这是必要的(否则 /dev/pts 的安装失败) 2. 请重新阅读我的问题,问题是完美运行的 shell 代码和相应的 C API 之间的不同行为。 3.你的C代码不起作用。 4.你问我在你不给我答案的情况下关闭问题,只是为了说明,可以卸载/proc,你自己试试shell代码。
  • 首先,您的标题是“'unshare' 在 C api 中无法按预期工作” - 不,它按预期工作。 2)我不相信你可以在bash中卸载并且不能用c api来做到这一点,那是不可能的 - 在unshare --fork --pid --mount之后提供fuser /proc/的输出 - 我确定你也不能通过控制台命令卸载proc 3)你的意思是我的代码不起作用 - 你不能编译它? 4)您忽略了检查请求 - 您没有检查 mtab 文件或 symvoliv 链接是什么。
  • 根据您的 cmets - 很明显您不了解 bash 和 unshare 机制 - 尝试执行您的 bash 脚本,而不是作为脚本,而是逐步手动执行每个命令,您将了解您的错误。
【解决方案2】:

我认为问题出在系统(“mount”)上,它产生了一个 shell 并且不携带 umount。尝试在 umount 后在 /proc/ 中打开一个文件,看看它是否按预期工作。

看到这个 -

unshare(CLONE_NEWPID | CLONE_NEWNS );
int rc = 0;
int pid = fork();
if (pid != 0) {
        int status;
        waitpid(-1, &status, 0);
        return status;
}

printf(">>> My pid: %d\n", getpid()); // It prints 1 as expected
rc = umount2("/proc", MNT_FORCE); // Returns 0
printf(">>> umount returned %d. errno = %d, desc = (%s)\n", rc, errno, strerror(errno));

rc = open("/proc/cpuinfo", O_RDONLY);
printf(">>> open returned %d. errno = %d, desc = (%s)\n", rc, errno, strerror(errno));

【讨论】:

    【解决方案3】:

    尽管你的评论是

    "sometimes" umount 返回 0 "sometimes" -1,但最后它根本没有卸载 /proc

    ,在您的 pastebin 代码的 10000 次试验中,umount() 总是 对我来说失败了,返回 -1 而不是卸载 /proc。我不愿意相信umount() 会返回0,尽管它未能执行请求的卸载,但如果确实如此,那将构成umount() 中的错误。如果您实际上可以证实这样的错误,那么社区意识的回应将是针对 glibc 提交错误报告。


    然后问题就变成了您的bash 脚本为何以及如何表现不同。然而事实上,它似乎并没有。

    首先,您对unshare(1) 命令的期望是错误的。与unshare(2) 函数不同,unshare 命令不会影响执行它的shell。相反,它会启动一个单独的进程,该进程拥有自己的指定命名空间的私有副本。通常,您会在unshare 命令行上指定启动该进程的命令,实际上程序的手册页表明这样做是强制性的。

    根据经验,我发现如果我没有指定这样的命令——就像你做的那样——那么unshare 会启动一个新的 shell 作为目标进程。特别是当我运行你的脚本时(有足够的权限使用unshare),我立即得到一个新的提示,但它是新shell的提示,在前台运行。这对我来说很明显,因为提示不同(但是,在这些情况下,您的提示可能没有任何不同)。那时没有来自umount 的错误消息等,因为它还没有运行。如果我在 (unshared) 子 shell 中手动尝试 umount proc,它会因“设备忙”而失败——这与您的 C 程序尝试执行的操作类似。

    当我退出子 shell 时,脚本的其余部分运行,umounts 和 mounts 都失败了。这是意料之中的,因为主脚本共享其挂载命名空间。


    /proc 确实很忙,因此无法卸载,即使对于具有挂载命名空间的私有副本的进程,这也是完全合理的。很可能这样的进程本身正在使用其挂载/proc 的私​​有副本。相比之下,我发现我可以在具有非共享挂载命名空间的进程中成功卸载 /dev/pts,但不能在共享该命名空间的系统副本的进程中。

    【讨论】:

    • 我同意你的分析,但是在我的系统中我可以卸载 /proc 并且我不使用脚本来执行该命令:imagebin.ca/v/2Uw7vXG71WhQ
    • 但是,我刚刚看到重新挂载 /dev/pts 会影响所有系统,而不仅仅是私有副本,我不知道为什么。
    • @FedericoReghenzani,正如我在回答中所写的那样,如果我尝试从 unshared 子shell 中 umount /proc 它会失败,就像尝试从C 程序可以。这不依赖于将命令作为脚本运行。这种行为对我来说是一致的,如果它对你来说确实不一致,那么我认为你的发行版中有一个我的发行版中没有的错误。
    • @FedericoReghenzani,同样,在unshared 子shell 中卸载/dev/pts 似乎对我来说完全符合我的预期。文件系统在子 shell 中成功卸载——列出挂载点目录没有显示任何内容——但就所有其他进程而言,它仍然挂载。
    • 好的,我在另一个系统上试过了。尝试(在取消共享之前)卸载(使用 -lf)并重新安装 /proc。现在应该可以在非共享环境中卸载 /proc。然而,unshared 命名空间中的 umount /proc 似乎会影响整个系统。
    【解决方案4】:

    我在检查source code of unshare command 时发现了问题。 /proc 必须使用 MS_PRIVATE | MS_REC 卸载并在没有它们的情况下安装,这实质上是为了确保安装仅在当前(新)命名空间中有效。第二个问题是无法卸载/dev/pts 而不对全局命名空间产生影响(这是由 devpts 驱动程序的内部例程引起的)。要拥有私有的 /dev/pts,唯一的方法是使用专用的 -o newinstance 选项挂载它。最后/dev/ptmx 也应该重新绑定。

    因此,这是预期的 C 工作代码:

    unshare(CLONE_NEWPID | CLONE_NEWNS );
    int pid = fork();
    if (pid != 0) {
        int status;
        waitpid(-1, &status, 0);
        return status;
    }
    
    printf("New PID after unshare is %i", getpid());
    
    if (mount("none", "/proc", NULL, MS_PRIVATE|MS_REC, NULL)) {
        printf("Cannot umount proc! errno=%i", errno);
        exit(1);
    }
    
    if (mount("proc", "/proc", "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL)) {
        printf("Cannot mount proc! errno=%i", errno);
        exit(1);
    }
    
    
    if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC, "newinstance") ) {
        printf("Cannot mount pts! errno=%i", errno);
        exit(1);
    }
    
    if (mount("/dev/pts/ptmx", "/dev/ptmx", NULL, MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | MS_BIND, NULL) ) {
        printf("Cannot mount ptmx! errno=%i", errno);
        exit(1);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-17
      相关资源
      最近更新 更多