'unshare' 在 C api 中无法按预期工作

'unshare' does not work as expected in C api

此命令序列有效:

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 没有按预期工作:即使它 returns 为零,似乎也没有卸载 /proc(如果我在那之后尝试执行 system("mount"),它打印已安装的文件系统)。

取消分享 bash !=取消分享 c

unshare - 运行 一些命名空间从父级取消共享的程序

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

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

CLONE_NEWPID 是 "clone" 标志而不是 "unshare"

因此,取决于您要实现的目标 - 我假设您正在尝试使“/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 这并不总是符号 link 到 /proc/mounts

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

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

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.

看到

我认为问题出在系统 ("mount") 上,它产生了一个 shell 并且没有进行卸载。尝试在 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));

尽管您评论说

"sometimes" umount returns 0 "sometimes" -1, but in the end it does not unmount /proc at all

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


接下来的问题是您的 bash 脚本为何以及如何表现不同。然而事实上,似乎并没有。

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

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

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


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

我在检查 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);
}