'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
没有错误消息等,因为 还没有 运行。如果我在 (unshare
d) subshell 中手动尝试 umount
proc,它会因 "device is busy" 而失败——这类似于你的 C 程序试图做的事情.
当我退出 subshell 时,脚本的其余部分 运行s,umount
s 和 mount
s 都失败了。这是意料之中的,因为主脚本共享其挂载命名空间。
完全有可能 /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);
}
此命令序列有效:
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
没有错误消息等,因为 还没有 运行。如果我在 (unshare
d) subshell 中手动尝试 umount
proc,它会因 "device is busy" 而失败——这类似于你的 C 程序试图做的事情.
当我退出 subshell 时,脚本的其余部分 运行s,umount
s 和 mount
s 都失败了。这是意料之中的,因为主脚本共享其挂载命名空间。
完全有可能 /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);
}