如何确保子进程与父进程处于同一个cgroup中?
我正在基于bubblewrap和 systemd的 cgroup管理开发一款自己的沙箱工具。我通过DBus API动态创建一个systemd scope,然后启动一个运行bubblewrap沙箱的子进程。bubblewrap进程及其子进程没有被加入到cgroup。
目前我让systemd知道我的包装进程的pidfd(这和systemd-run做的一样),它只把该进程放入cgroup,而不会把它的子进程放进去。我也尝试把子进程放入该作用域,但仍然无果。
这就是cgroup的样子:
app.slice
│ │ │ ├─my-unit.scope
│ │ │ │ └─155454 /usr/bin/bwrap --bind /usr /usr --symlink /usr/lib64 /lib64 --new-session --unshare-all --clearenv --die-with-parent bash
这应该像systemd-run那样的样子:
app.slice
│ │ │ ├─my-unit.scope
│ │ │ │ ├─156845 /usr/bin/bwrap --new-session --die-with-parent --unshare-all --bind /usr /usr --symlink /usr/lib64 /lib64 --clearenv bash
│ │ │ │ ├─156846 /usr/bin/bwrap --new-session --die-with-parent --unshare-all --bind /usr /usr --symlink /usr/lib64 /lib64 --clearenv bash
│ │ │ │ └─156847 bash
这是我当前用Rust编写的代码:
async fn setup_process(profile: Profile, conn: &zbus::Connection) -> Result<Child, Box<dyn Error>> {
let mut command = Command::new("/usr/bin/bwrap");
command.env_clear();
command.kill_on_drop(true);
command.reap_on_drop(true);
for bind in profile.binds.iter() {
let source = format!("{}", bind.source.display());
let target = format!("{}", bind.target.display());
if bind.read_only {
command.arg("--ro-bind").arg(source).arg(target);
} else {
command.arg("--bind").arg(source).arg(target);
}
}
for symlink in profile.symlinks.iter() {
let source = format!("{}", symlink.source.display());
let target = format!("{}", symlink.target.display());
command.arg("--symlink").arg(source).arg(target);
}
command.arg("--new-session");
command.arg("--unshare-all");
command.arg("--clearenv");
command.arg("--die-with-parent");
command.arg(profile.command);
for arg in profile.args.iter() {
command.arg(arg);
}
let pid = nix::unistd::getpid();
let pidfd_raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) as RawFd };
if pidfd_raw < 0 {
return Err(io::Error::new(io::ErrorKind::Unsupported, "unable to open pidfd").into());
}
let pidfd: zvariant::Fd = unsafe { OwnedFd::from_raw_fd(pidfd_raw) }.into();
let proxy = systemd::manager::ManagerProxy::new(&conn).await?;
let unit = systemd::properties::Unit::builder()
.description("My Transient Unit")
.collect_mode("inactive-or-failed")
.build();
let resouces = systemd::properties::Resources::builder()
.slice("app.slice")
.memory_max(20971520)
.memory_swap_max(0)
.build();
let pidfds = &[pidfd];
let scope = systemd::properties::Scope::builder()
.unit(unit)
.resources(resouces)
.pidfds(pidfds)
.build();
let _ = proxy
.start_transient_unit("my-unit.scope", "fail", &Vec::from(scope), &[])
.await?;
let err = command.exec();
Err(err.into())
}
编辑:
看来在Rust实现的Command中似乎并不存在问题,因为调用 let err = nix::unistd::execvpe(cmd.as_ref(), args.as_slice(), env)?;(systemd-run的做法)也会以相同的方式失败。
编辑2:
我发现 这个 看起来是一个类似的问题,但其实并没有解决。
编辑3:
找到了缺失的子进程,出于某种原因它们被放在了我的终端所在的作用域下,仍然不知道为什么
kitty-122960-0.scope
│ │ │ ├─122971 /usr/bin/bash
│ │ │ ├─231054 /usr/bin/bwrap --bind /usr /usr --symlink /usr/lib64 /lib64 --new-session --unshare-all --clearenv --die-with-parent bash
│ │ │ └─231055 bash
解决方案
在阅读了 systemd-run 与 runapp 的源代码(它们做的是同一件事)后,我找到了正确的解决办法。似乎我的exec在作用域实际初始化之前就已经运行,因此子进程在systemd把它们放入正确的作用域之前就已经创建。解决方案是等待systemd确认作用域已经实际创建,然后再执行exec。
在执行exec之前我添加了以下这段:
let mut job_result: String = "".into();
let mut job_removed_stream = proxy.receive_job_removed().await?;
while let Some(msg) = job_removed_stream.next().await {
let args: JobRemovedArgs = msg.args()?;
if args.job.as_str() == job_path.as_str() {
job_result = args.result.into();
break;
}
}
if job_result == "failed" {
return Err(io::Error::new(io::ErrorKind::Other, "failed to start scope").into());
}
if job_result != "done" {
return Err(io::Error::new(io::ErrorKind::Other, job_result.as_str()).into());
}
总的来说,systemd的 DBus API文档非常不完善,希望这个解决方案能帮助后来的人。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。