“unshare”在C API中未按预期工作

20
这串命令可以正常运行:
unshare --fork --pid --mount 
umount /proc
mount -t proc proc /proc
umount /dev/pts
mount -t devpts devpts /dev/pts

然而,相应的C程序并没有按预期工作(似乎它没有卸载之前的/proc,并且在尝试卸载devpts时提供了EBUSY错误):
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的信息 - Jasen
好的,无论如何我认为mount devpts上的EBUSY是由umount/ mount proc的“静默失败”引起的。 - ocirocir
1
你的umount对我来说失败了,所以我用umount2("<mountpoint>", MNT_DETACH)替换它们。然而,这并没有完全解决问题:它在全局范围内卸载(和重新挂载)了/proc和/dev/pts!这到底是怎么回事? - user3035772
1
你认为为什么mount应该在mtab上打印错误信息? - nsilent22
请问您能否提供您正在运行的测试程序的完整代码,特别是展示 umount("/proc") 返回成功代码的部分?我不太倾向于相信它真正展示了您描述的 umount()mount() 的行为。当然,我已经试图复制您的代码并没有发现这样的行为。我怀疑程序存在缺陷,但由于您没有展示全部代码,我无法确定。 - John Bollinger
显示剩余6条评论
4个回答

2
尽管您的评论是“有时”umount返回0,“有时”返回-1,但在您过去的代码的10000次尝试中,umount()总是失败,并返回-1而没有卸载/proc。我不愿相信umount()会返回0,尽管它未执行请求的卸载,但如果确实如此,则构成umount()的错误。如果您确实可以证明这样的错误,则社区意识的回应将是针对glibc提出错误报告。
问题变成了为什么以及您的bash脚本如何表现不同。事实上,它似乎并没有这样做。
首先,您对unshare(1)命令的期望是错误的。与unshare(2)函数不同,unshare命令不会影响其执行的shell。相反,它启动了一个具有其自己的私有副本的指定命名空间的独立进程。通常,您将在unshare命令行上指定要启动该进程的命令,并且实际上程序的手册页指出这样做是强制性的。
根据经验,如果我未指定这样的命令(就像您一样),则unshare将启动一个新的shell作为目标进程。特别是,当我运行您的脚本(具有足够的特权使用unshare)时,我立即获得了一个新提示符,但它是在前台运行的新shell的提示符。这对我来说是立即显而易见的,因为提示符不同(然而,在这些情况下,您的提示符可能没有任何区别)。此时没有来自umount的错误消息等,因为它尚未运行。如果我手动尝试在(unshared)子shell中卸载proc,则会失败并显示“设备正忙”-这是您的C程序尝试做的类比。
当我退出子shell时,剩余的脚本运行,两个umount和两个mount都失败。这是可以预期的,因为主脚本共享其挂载命名空间。

完全有可能/proc确实很忙,因此即使是具有专用挂载命名空间副本的进程也无法卸载它。这样的进程很可能正在使用其专用挂载的/proc。相反,我发现在具有未共享挂载命名空间的进程中可以成功卸载/dev/pts,但不能在共享系统命名空间副本的进程中进行。


我同意你的分析,但在我的系统中,我能够卸载/proc,而且我不使用脚本来执行这些命令:http://imagebin.ca/v/2Uw7vXG71WhQ - ocirocir
然而,我刚刚发现重新挂载/dev/pts会影响整个系统,而不仅仅是私有副本,我不知道为什么。 - ocirocir
@FedericoReghenzani,正如我在我的回答中所写的那样,如果我尝试从未共享的子shell中卸载/proc,则会失败,就像从C程序中尝试执行相同操作一样。这不取决于将命令作为脚本运行。对我来说,这种行为是一致的,如果你真的发现它不一致,那么我认为你的发行版中存在一个我没有的错误。 - John Bollinger
@FedericoReghenzani ,此外,在“unshare”的子shell中卸载/dev/pts对我来说似乎可以正常工作。文件系统在子shell中已成功卸载-列出挂载点目录不显示任何内容-但在所有其他进程看来仍然是挂载的。 - John Bollinger
好的,我在另一个系统中尝试了一下。在取消共享之前尝试使用 -lf 卸载 /proc 并重新挂载。现在应该可以在未共享环境中卸载 /proc 了。但是似乎在未共享的命名空间中卸载 /proc 会影响整个系统。 - ocirocir

1
我在检查unshare命令的源代码时发现了问题。必须使用MS_PRIVATE | MS_REC卸载/proc并在没有它们的情况下安装,这基本上是为了确保挂载仅在当前(新)命名空间中生效。第二个问题是无法卸载/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);
}

0

我认为问题在于system("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));

0

unshare bash != unshare c

unshare - 运行程序时某些名称空间与父级不共享

基本上,使用--fork时,您正在从/bin/bash或/bin/sh(无论您使用哪个脚本)中分叉,并使用--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

接下来:

挂载依赖于/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 命令看到。


简单来说,你不能卸载proc。而且你也不需要卸载proc,因为unshare并没有按照你的预期工作。问题在于你的新命名空间也依赖于proc。如果你的问题是关于卸载proc的,那么需要安排一个合适的标题。 - Maquefel
首先,你的标题是“ 'unshare'在C api中无法按预期工作” - 不,它正在按预期工作。2)我不相信你可以在bash中卸载并且不能使用c api这样做,那是不可能的 - 在unshare --fork --pid --mount之后提供fuser / proc /的输出-我确定您也无法通过控制台命令卸载proc 3)你的意思是我的代码不起作用-你无法编译它吗?4)你忽略了检查请求-你没有检查mtab是文件还是符号链接。 - Maquefel
根据您的评论 - 显然您不理解bash和unshare机制 - 尝试逐步手动执行您的bash脚本而不是作为脚本运行,这样您就会理解您的错误。 - Maquefel
你一直在说这是不可能的,但它确实有效...只要试试。你相信我还是想要一个视频?...(不,我没有使用bash脚本-_-)正如我所说,我可以编译你的代码,但它不会分离PID命名空间,所以对于我想尝试的事情毫无用处...mtab通常是指向../proc/self/mounts的链接。 - ocirocir
  1. 那么你为什么要说你无法通过C API卸载?
  2. 只需在unshare调用中添加CLONE_NEWPID标志-这不是显而易见的吗?
  3. 如果您调用unmount而没有使用-n标志,则应收到警告:umount:/etc/mtab:没有那个文件或目录-如果没有,您的umount是别名或您没有使用util-linux umount。
- Maquefel
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接