Rewrite bwrap code in Rust
I tried to do this incrementally but it snowballed.
This commit is contained in:
parent
40dc281e5e
commit
fa81456cbf
@ -28,8 +28,6 @@ librpmostreepriv_sources = \
|
||||
src/libpriv/rpmostree-core.cxx \
|
||||
src/libpriv/rpmostree-core.h \
|
||||
src/libpriv/rpmostree-core-private.h \
|
||||
src/libpriv/rpmostree-bwrap.cxx \
|
||||
src/libpriv/rpmostree-bwrap.h \
|
||||
src/libpriv/rpmostree-kernel.cxx \
|
||||
src/libpriv/rpmostree-kernel.h \
|
||||
src/libpriv/rpmostree-origin.cxx \
|
||||
|
@ -2,22 +2,269 @@
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
use crate::cxxrsutil::CxxResult;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use crate::cxxrsutil::*;
|
||||
use crate::ffi::BubblewrapMutability;
|
||||
use anyhow::{Context, Result};
|
||||
use fn_error_context::context;
|
||||
use openat_ext::OpenatDirExt;
|
||||
use std::convert::TryInto;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::rc::Rc;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::process::Command;
|
||||
|
||||
// Links in the rootfs to /usr
|
||||
static USR_LINKS: &[&str] = &["lib", "lib32", "lib64", "bin", "sbin"];
|
||||
// This is similar to what systemd does, except we drop /usr/local, since scripts shouldn't see it.
|
||||
static PATH_VAR: &str = "PATH=/usr/sbin:/usr/bin";
|
||||
|
||||
pub(crate) struct Bubblewrap {
|
||||
pub(crate) rootfs_fd: Rc<openat::Dir>,
|
||||
pub(crate) rootfs_fd: openat::Dir,
|
||||
|
||||
executed: bool,
|
||||
argv: Vec<String>,
|
||||
child_argv0: Option<NonZeroUsize>,
|
||||
launcher: gio::SubprocessLauncher, // 🚀
|
||||
|
||||
tempdirs: Vec<tempfile::TempDir>,
|
||||
}
|
||||
|
||||
impl Drop for Bubblewrap {
|
||||
fn drop(&mut self) {
|
||||
for d in self.tempdirs.drain(..) {
|
||||
// We need to unmount the tempdirs, before letting
|
||||
// the drop handler recursively remove them.
|
||||
let success = Command::new("fusermount")
|
||||
.arg("-u")
|
||||
.arg(d.path())
|
||||
.status()
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|status| -> Result<()> {
|
||||
if !status.success() {
|
||||
Err(anyhow::anyhow!("{}", status))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.err()
|
||||
.map(|e| {
|
||||
systemd::journal::print(4, &format!("{}", e));
|
||||
})
|
||||
.is_none();
|
||||
if !success {
|
||||
// If fusermount fails, then we cannot remove it; just leak it.
|
||||
let _ = d.into_path();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nspawn by default doesn't give us CAP_NET_ADMIN; see
|
||||
// https://pagure.io/releng/issue/6602#comment-71214
|
||||
// https://pagure.io/koji/pull-request/344#comment-21060
|
||||
// Theoretically we should do capable(CAP_NET_ADMIN)
|
||||
// but that's a lot of ugly code, and the only known
|
||||
// place we hit this right now is nspawn. Plus
|
||||
// we want to use userns down the line anyways where
|
||||
// we'll regain CAP_NET_ADMIN.
|
||||
fn running_in_nspawn() -> bool {
|
||||
std::env::var_os("container").as_deref() == Some(std::ffi::OsStr::new("systemd-nspawn"))
|
||||
}
|
||||
|
||||
fn setup_rofiles_in(rootfs: &openat::Dir, path: &str) -> Result<tempfile::TempDir> {
|
||||
let path = path.trim_start_matches('/');
|
||||
let tempdir = tempfile::Builder::new()
|
||||
.prefix("rpmostree-rofiles-fuse")
|
||||
.tempdir()?;
|
||||
let status = std::process::Command::new("rofiles-fuse")
|
||||
.arg("--copyup")
|
||||
.arg(path)
|
||||
.arg(tempdir.path())
|
||||
.current_dir(format!("/proc/self/fd/{}", rootfs.as_raw_fd()))
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(anyhow::anyhow!("{}", status));
|
||||
}
|
||||
Ok(tempdir)
|
||||
}
|
||||
|
||||
fn child_wait_check(
|
||||
child: gio::Subprocess,
|
||||
cancellable: Option<&gio::Cancellable>,
|
||||
) -> std::result::Result<(), glib::Error> {
|
||||
match child.wait(cancellable) {
|
||||
Ok(_) => {
|
||||
let estatus = child.get_exit_status();
|
||||
glib::spawn_check_exit_status(estatus)?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
child.force_exit();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bubblewrap {
|
||||
/// Create a new Bubblewrap instance
|
||||
pub(crate) fn new(rootfs_fd: &Rc<openat::Dir>) -> Self {
|
||||
Self {
|
||||
rootfs_fd: Rc::clone(rootfs_fd),
|
||||
pub(crate) fn new(rootfs_fd: &openat::Dir) -> Result<Self> {
|
||||
let rootfs_fd = rootfs_fd.sub_dir(".")?;
|
||||
|
||||
let lang = std::env::var_os("LANG");
|
||||
let lang = lang.as_ref().map(|s| s.to_str()).flatten().unwrap_or("C");
|
||||
let lang_var = format!("LANG={}", lang);
|
||||
let lang_var = Path::new(&lang_var);
|
||||
|
||||
let launcher = gio::SubprocessLauncher::new(gio::SubprocessFlags::NONE);
|
||||
let child_rootfs_fd = rootfs_fd.as_raw_fd();
|
||||
launcher.set_child_setup(move || {
|
||||
nix::unistd::fchdir(child_rootfs_fd).expect("fchdir");
|
||||
});
|
||||
|
||||
let path_var = Path::new(PATH_VAR);
|
||||
launcher.set_environ(&[lang_var, path_var]);
|
||||
|
||||
// ⚠⚠⚠ If you change this, also update scripts/bwrap-script-shell.sh ⚠⚠⚠
|
||||
let mut argv = vec![
|
||||
"bwrap",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dir",
|
||||
"/run",
|
||||
"--dir",
|
||||
"/tmp",
|
||||
"--chdir",
|
||||
"/",
|
||||
"--ro-bind",
|
||||
"/sys/block",
|
||||
"/sys/block",
|
||||
"--ro-bind",
|
||||
"/sys/bus",
|
||||
"/sys/bus",
|
||||
"--ro-bind",
|
||||
"/sys/class",
|
||||
"/sys/class",
|
||||
"--ro-bind",
|
||||
"/sys/dev",
|
||||
"/sys/dev",
|
||||
"--ro-bind",
|
||||
"/sys/devices",
|
||||
"/sys/devices",
|
||||
"--die-with-parent", /* Since 0.1.8 */
|
||||
/* Here we do all namespaces except the user one.
|
||||
* Down the line we want to do a userns too I think,
|
||||
* but it may need some mapping work.
|
||||
*/
|
||||
"--unshare-pid",
|
||||
"--unshare-uts",
|
||||
"--unshare-ipc",
|
||||
"--unshare-cgroup-try",
|
||||
];
|
||||
|
||||
if !running_in_nspawn() {
|
||||
argv.push("--unshare-net");
|
||||
}
|
||||
|
||||
/* Capabilities; this is a subset of the Docker (1.13 at least) default.
|
||||
* Specifically we strip out in addition to that:
|
||||
*
|
||||
* "cap_net_raw" (no use for this in %post, and major source of security vulnerabilities)
|
||||
* "cap_mknod" (%post should not be making devices, it wouldn't be persistent anyways)
|
||||
* "cap_audit_write" (we shouldn't be auditing anything from here)
|
||||
* "cap_net_bind_service" (nothing should be doing IP networking at all)
|
||||
*
|
||||
* But crucially we're dropping a lot of other capabilities like
|
||||
* "cap_sys_admin", "cap_sys_module", etc that Docker also drops by default.
|
||||
* We don't want RPM scripts to be doing any of that. Instead, do it from
|
||||
* systemd unit files.
|
||||
*
|
||||
* Also this way we drop out any new capabilities that appear.
|
||||
*/
|
||||
if nix::unistd::getuid() == nix::unistd::Uid::from_raw(0) {
|
||||
argv.extend(&[
|
||||
"--cap-drop",
|
||||
"ALL",
|
||||
"--cap-add",
|
||||
"cap_chown",
|
||||
"--cap-add",
|
||||
"cap_dac_override",
|
||||
"--cap-add",
|
||||
"cap_fowner",
|
||||
"--cap-add",
|
||||
"cap_fsetid",
|
||||
"--cap-add",
|
||||
"cap_kill",
|
||||
"--cap-add",
|
||||
"cap_setgid",
|
||||
"--cap-add",
|
||||
"cap_setuid",
|
||||
"--cap-add",
|
||||
"cap_setpcap",
|
||||
"--cap-add",
|
||||
"cap_sys_chroot",
|
||||
"--cap-add",
|
||||
"cap_setfcap",
|
||||
]);
|
||||
}
|
||||
|
||||
let mut argv: Vec<_> = argv.into_iter().map(|s| s.to_string()).collect();
|
||||
|
||||
for &name in USR_LINKS.iter() {
|
||||
if let Some(stbuf) = rootfs_fd.metadata_optional(name)? {
|
||||
if !matches!(stbuf.simple_type(), openat::SimpleType::Symlink) {
|
||||
continue;
|
||||
}
|
||||
|
||||
argv.push("--symlink".to_string());
|
||||
argv.push(format!("usr/{}", name));
|
||||
argv.push(format!("/{}", name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
rootfs_fd,
|
||||
executed: false,
|
||||
argv,
|
||||
launcher,
|
||||
child_argv0: None,
|
||||
tempdirs: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_rofiles(&mut self, path: &str) -> Result<()> {
|
||||
let tmpdir = setup_rofiles_in(&self.rootfs_fd, path)?;
|
||||
let tmpdir_path = tmpdir.path().to_str().expect("tempdir str");
|
||||
self.bind_readwrite(tmpdir_path, path);
|
||||
self.tempdirs.push(tmpdir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_mutability(
|
||||
rootfs_fd: &openat::Dir,
|
||||
mutability: BubblewrapMutability,
|
||||
) -> Result<Self> {
|
||||
let mut ret = Self::new(rootfs_fd)?;
|
||||
match mutability {
|
||||
BubblewrapMutability::Immutable => {
|
||||
ret.bind_read("usr", "/usr");
|
||||
ret.bind_read("etc", "/etc");
|
||||
}
|
||||
BubblewrapMutability::RoFiles => {
|
||||
ret.setup_rofiles("/usr")?;
|
||||
ret.setup_rofiles("/etc")?;
|
||||
}
|
||||
BubblewrapMutability::MutateFreely => {
|
||||
ret.bind_readwrite("usr", "/usr");
|
||||
ret.bind_readwrite("etc", "/etc");
|
||||
}
|
||||
o => {
|
||||
panic!("Invalid BubblewrapMutability: {:?}", o);
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn setup_rofiles(&mut self, path: &str) -> Result<()> {
|
||||
@ -32,11 +279,181 @@ impl Bubblewrap {
|
||||
pub(crate) fn get_rootfs_fd(&self) -> i32 {
|
||||
self.rootfs_fd.as_raw_fd()
|
||||
}
|
||||
|
||||
pub(crate) fn append_bwrap_arg(&mut self, arg: &str) {
|
||||
self.append_bwrap_argv(&[arg])
|
||||
}
|
||||
|
||||
pub(crate) fn append_bwrap_argv(&mut self, args: &[&str]) {
|
||||
self.argv.extend(args.iter().map(|s| s.to_string()));
|
||||
}
|
||||
|
||||
pub(crate) fn append_child_arg(&mut self, arg: &str) {
|
||||
self.append_child_argv(&[arg])
|
||||
}
|
||||
|
||||
pub(crate) fn append_child_argv(&mut self, args: &[&str]) {
|
||||
// Record the binary name for later error messages
|
||||
if self.child_argv0.is_none() {
|
||||
self.child_argv0 = Some(self.argv.len().try_into().expect("args"));
|
||||
}
|
||||
self.argv.extend(args.iter().map(|s| s.to_string()));
|
||||
}
|
||||
|
||||
/// Set an environment variable
|
||||
pub(crate) fn setenv(&mut self, k: &str, v: &str) {
|
||||
self.launcher.setenv(k, v, true);
|
||||
}
|
||||
|
||||
/// Take a file descriptor
|
||||
pub(crate) fn take_fd(&mut self, source_fd: i32, target_fd: i32) {
|
||||
self.launcher.take_fd(source_fd, target_fd);
|
||||
}
|
||||
|
||||
/// Inherit stdin
|
||||
pub(crate) fn set_inherit_stdin(&mut self) {
|
||||
self.launcher.set_flags(gio::SubprocessFlags::STDIN_INHERIT);
|
||||
}
|
||||
|
||||
/// Take a file descriptor for stdin
|
||||
pub(crate) fn take_stdin_fd(&mut self, source_fd: i32) {
|
||||
self.launcher.take_stdin_fd(source_fd);
|
||||
}
|
||||
|
||||
/// Take a file descriptor for stdout
|
||||
pub(crate) fn take_stdout_fd(&mut self, source_fd: i32) {
|
||||
self.launcher.take_stdout_fd(source_fd);
|
||||
}
|
||||
|
||||
/// Take a file descriptor for stderr
|
||||
pub(crate) fn take_stderr_fd(&mut self, source_fd: i32) {
|
||||
self.launcher.take_stderr_fd(source_fd);
|
||||
}
|
||||
|
||||
/// Take a file descriptor for stderr
|
||||
pub(crate) fn take_stdout_and_stderr_fd(&mut self, source_fd: i32) {
|
||||
self.launcher.take_stdout_fd(source_fd);
|
||||
self.launcher.set_flags(gio::SubprocessFlags::STDERR_MERGE);
|
||||
}
|
||||
|
||||
/// Bind source to destination in the container (readonly)
|
||||
pub(crate) fn bind_read(&mut self, src: &str, dest: &str) {
|
||||
self.append_bwrap_argv(&["--ro-bind", src, dest]);
|
||||
}
|
||||
|
||||
/// Bind source to destination in the container (read-write)
|
||||
pub(crate) fn bind_readwrite(&mut self, src: &str, dest: &str) {
|
||||
self.append_bwrap_argv(&["--bind", src, dest]);
|
||||
}
|
||||
|
||||
// Set /var to be read-only, but with a transient writable /var/tmp
|
||||
pub(crate) fn var_tmp_tmpfs(&mut self) {
|
||||
self.bind_read("./var", "/var");
|
||||
self.append_bwrap_argv(&["--tmpfs", "/var/tmp"]);
|
||||
}
|
||||
|
||||
fn spawn(&mut self) -> Result<(gio::Subprocess, String)> {
|
||||
assert!(!self.executed);
|
||||
self.executed = true;
|
||||
|
||||
let child_argv0_i: usize = self.child_argv0.expect("child argument").into();
|
||||
let child_argv0 = format!("bwrap({})", self.argv[child_argv0_i].as_str());
|
||||
let argv: Vec<_> = self.argv.iter().map(|s| s.as_ref()).collect();
|
||||
let child = self.launcher.spawnv(&argv)?;
|
||||
Ok((child, child_argv0))
|
||||
}
|
||||
|
||||
/// Execute the container, capturing stdout.
|
||||
fn run_captured(&mut self, cancellable: Option<&gio::Cancellable>) -> Result<glib::Bytes> {
|
||||
self.launcher.set_flags(gio::SubprocessFlags::STDOUT_PIPE);
|
||||
let (child, argv0) = self.spawn()?;
|
||||
let (stdout, stderr) = child.communicate(None, cancellable)?;
|
||||
// we never pipe just stderr, so we don't expect it to be captured
|
||||
assert!(stderr.is_none());
|
||||
let stdout = stdout.expect("stdout");
|
||||
|
||||
child_wait_check(child, cancellable).context(argv0)?;
|
||||
|
||||
Ok(stdout)
|
||||
}
|
||||
|
||||
/// Execute the container. This method uses the normal gtk-rs `Option<T>` for the cancellable.
|
||||
fn run_inner(&mut self, cancellable: Option<&gio::Cancellable>) -> Result<()> {
|
||||
let (child, argv0) = self.spawn()?;
|
||||
child_wait_check(child, cancellable).context(argv0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the instance; requires a cancellable for C++.
|
||||
pub(crate) fn run(
|
||||
&mut self,
|
||||
mut cancellable: Pin<&mut crate::FFIGCancellable>,
|
||||
) -> CxxResult<()> {
|
||||
let cancellable = &cancellable.gobj_wrap();
|
||||
self.run_inner(Some(cancellable))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[context("Creating bwrap instance")]
|
||||
/// Create a new Bubblewrap instance
|
||||
pub(crate) fn bubblewrap_new(rootfs_fd: i32) -> CxxResult<Box<Bubblewrap>> {
|
||||
let fd = Rc::new(openat::Dir::open(format!("/proc/self/fd/{}", rootfs_fd))?);
|
||||
Ok(Box::new(Bubblewrap::new(&fd)))
|
||||
let rootfs_fd = crate::ffiutil::ffi_view_openat_dir(rootfs_fd);
|
||||
Ok(Box::new(Bubblewrap::new(&rootfs_fd)?))
|
||||
}
|
||||
|
||||
pub(crate) fn bubblewrap_new_with_mutability(
|
||||
rootfs_fd: i32,
|
||||
mutability: crate::ffi::BubblewrapMutability,
|
||||
) -> Result<Box<Bubblewrap>> {
|
||||
let rootfs_fd = crate::ffiutil::ffi_view_openat_dir(rootfs_fd);
|
||||
Ok(Box::new(Bubblewrap::new_with_mutability(
|
||||
&rootfs_fd, mutability,
|
||||
)?))
|
||||
}
|
||||
|
||||
/// Synchronously run bubblewrap, allowing mutability, and optionally capturing stdout.
|
||||
pub(crate) fn bubblewrap_run_sync(
|
||||
rootfs_dfd: i32,
|
||||
args: &Vec<String>,
|
||||
capture_stdout: bool,
|
||||
unified_core: bool,
|
||||
) -> CxxResult<Vec<u8>> {
|
||||
let rootfs_dfd = &crate::ffiutil::ffi_view_openat_dir(rootfs_dfd);
|
||||
let tempetc = crate::core::prepare_tempetc_guard(rootfs_dfd.as_raw_fd())?;
|
||||
let mutability = if unified_core {
|
||||
BubblewrapMutability::RoFiles
|
||||
} else {
|
||||
BubblewrapMutability::MutateFreely
|
||||
};
|
||||
let mut bwrap = Bubblewrap::new_with_mutability(rootfs_dfd, mutability)?;
|
||||
|
||||
if unified_core {
|
||||
bwrap.bind_read("var", "/var");
|
||||
} else {
|
||||
bwrap.bind_readwrite("var", "/var")
|
||||
}
|
||||
|
||||
let args: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
|
||||
bwrap.append_child_argv(&args);
|
||||
|
||||
let cancellable = &gio::Cancellable::new();
|
||||
let cancellable = Some(cancellable);
|
||||
if capture_stdout {
|
||||
let buf = bwrap.run_captured(cancellable)?;
|
||||
tempetc.undo()?;
|
||||
Ok(buf.as_ref().iter().map(|&x| x).collect())
|
||||
} else {
|
||||
bwrap.run_inner(cancellable)?;
|
||||
tempetc.undo()?;
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[context("bwrap test failed, see <https://github.com/coreos/rpm-ostree/pull/429>")]
|
||||
/// Create a new Bubblewrap instance
|
||||
pub(crate) fn bubblewrap_selftest() -> CxxResult<()> {
|
||||
let fd = openat::Dir::open("/")?;
|
||||
let _ = bubblewrap_run_sync(fd.as_raw_fd(), &vec!["true".to_string()], false, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
//! a filesystem tree (usually containing mostly RPMs) in
|
||||
//! order to prepare it as an OSTree commit.
|
||||
|
||||
use crate::bwrap;
|
||||
use crate::cxxrsutil::CxxResult;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use fn_error_context::context;
|
||||
@ -194,9 +195,9 @@ fn compose_postprocess_scripts(
|
||||
|
||||
rootfs_dfd.write_file_contents(target_binpath, 0o755, script)?;
|
||||
println!("Executing `postprocess` inline script '{}'", i);
|
||||
let child_argv = vec![binpath.clone()];
|
||||
crate::ffi::bwrap_run_mutable(rootfs_dfd.as_raw_fd(), &binpath, &child_argv, unified_core)?;
|
||||
|
||||
let child_argv = vec![binpath.to_string()];
|
||||
let _ =
|
||||
bwrap::bubblewrap_run_sync(rootfs_dfd.as_raw_fd(), &child_argv, false, unified_core)?;
|
||||
rootfs_dfd.remove_file(target_binpath)?;
|
||||
}
|
||||
|
||||
@ -209,9 +210,14 @@ fn compose_postprocess_scripts(
|
||||
rootfs_dfd.write_file_with(target_binpath, 0o755, |w| std::io::copy(&mut reader, w))?;
|
||||
println!("Executing postprocessing script");
|
||||
|
||||
let child_argv = vec![binpath.to_string()];
|
||||
crate::ffi::bwrap_run_mutable(rootfs_dfd.as_raw_fd(), binpath, &child_argv, unified_core)
|
||||
.context("Executing postprocessing script")?;
|
||||
let child_argv = &vec![binpath.to_string()];
|
||||
let _ = crate::bwrap::bubblewrap_run_sync(
|
||||
rootfs_dfd.as_raw_fd(),
|
||||
child_argv,
|
||||
false,
|
||||
unified_core,
|
||||
)
|
||||
.context("Executing postprocessing script")?;
|
||||
|
||||
rootfs_dfd.remove_file(target_binpath)?;
|
||||
println!("Finished postprocessing script");
|
||||
@ -332,11 +338,8 @@ pub(crate) fn compose_postprocess_mutate_os_release(
|
||||
// find the real path to os-release using bwrap; this is an overkill but safer way
|
||||
// of resolving a symlink relative to a rootfs (see discussions in
|
||||
// https://github.com/projectatomic/rpm-ostree/pull/410/)
|
||||
let argv: Vec<String> = ["realpath", "/etc/os-release"]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let path = crate::ffi::bwrap_run_captured(rootfs_dfd.as_raw_fd(), &argv)
|
||||
let argv = &vec!["realpath".to_string(), "/etc/os-release".to_string()];
|
||||
let path = crate::bwrap::bubblewrap_run_sync(rootfs_dfd.as_raw_fd(), argv, true, true)
|
||||
.context("Running realpath")?;
|
||||
let path = String::from_utf8(path).context("Parsing realpath")?;
|
||||
let path = path.trim_start_matches("/").trim_end();
|
||||
|
@ -63,14 +63,48 @@ pub mod ffi {
|
||||
fn client_handle_fd_argument(arg: &str, arch: &str) -> Result<Vec<i32>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BubblewrapMutability {
|
||||
Immutable,
|
||||
RoFiles,
|
||||
MutateFreely,
|
||||
}
|
||||
|
||||
// bubblewrap.rs
|
||||
extern "Rust" {
|
||||
type Bubblewrap;
|
||||
|
||||
fn bubblewrap_new(rootfs_fd: i32) -> Result<Box<Bubblewrap>>;
|
||||
fn get_rootfs_fd(&self) -> i32;
|
||||
}
|
||||
fn bubblewrap_selftest() -> Result<()>;
|
||||
fn bubblewrap_run_sync(
|
||||
rootfs_dfd: i32,
|
||||
args: &Vec<String>,
|
||||
capture_stdout: bool,
|
||||
unified_core: bool,
|
||||
) -> Result<Vec<u8>>;
|
||||
|
||||
fn bubblewrap_new(rootfs_fd: i32) -> Result<Box<Bubblewrap>>;
|
||||
fn bubblewrap_new_with_mutability(
|
||||
rootfs_fd: i32,
|
||||
mutability: BubblewrapMutability,
|
||||
) -> Result<Box<Bubblewrap>>;
|
||||
fn get_rootfs_fd(&self) -> i32;
|
||||
|
||||
fn append_bwrap_arg(&mut self, arg: &str);
|
||||
fn append_child_arg(&mut self, arg: &str);
|
||||
fn setenv(&mut self, k: &str, v: &str);
|
||||
fn take_fd(&mut self, source_fd: i32, target_fd: i32);
|
||||
fn set_inherit_stdin(&mut self);
|
||||
fn take_stdin_fd(&mut self, source_fd: i32);
|
||||
fn take_stdout_fd(&mut self, source_fd: i32);
|
||||
fn take_stderr_fd(&mut self, source_fd: i32);
|
||||
fn take_stdout_and_stderr_fd(&mut self, source_fd: i32);
|
||||
|
||||
fn bind_read(&mut self, src: &str, dest: &str);
|
||||
fn bind_readwrite(&mut self, src: &str, dest: &str);
|
||||
fn var_tmp_tmpfs(&mut self);
|
||||
|
||||
fn run(&mut self, cancellable: Pin<&mut GCancellable>) -> Result<()>;
|
||||
}
|
||||
|
||||
// builtins/apply_live.rs
|
||||
extern "Rust" {
|
||||
@ -372,17 +406,6 @@ pub mod ffi {
|
||||
fn main_print_error(msg: &str);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("rpmostree-bwrap.h");
|
||||
fn bwrap_run_mutable(
|
||||
rootfs_dfd: i32,
|
||||
binpath: &str,
|
||||
child_argv: &Vec<String>,
|
||||
unified_core_mode: bool,
|
||||
) -> Result<()>;
|
||||
fn bwrap_run_captured(rootfs_dfd: i32, child_argv: &Vec<String>) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("rpmostree-clientlib.h");
|
||||
fn client_require_root() -> Result<()>;
|
||||
|
@ -38,7 +38,6 @@
|
||||
|
||||
#include "rpmostree-compose-builtins.h"
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include "rpmostree-core.h"
|
||||
#include "rpmostree-json-parsing.h"
|
||||
#include "rpmostree-rojig-build.h"
|
||||
@ -236,8 +235,7 @@ rpm_ostree_rojig_compose_new (const char *treefile_path,
|
||||
/* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker
|
||||
* container without --privileged or userns exposed.
|
||||
*/
|
||||
if (!rpmostree_bwrap_selftest (error))
|
||||
return FALSE;
|
||||
rpmostreecxx::bubblewrap_selftest();
|
||||
|
||||
if (opt_cachedir)
|
||||
{
|
||||
|
@ -40,7 +40,6 @@
|
||||
#include "rpmostree-compose-builtins.h"
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-composeutil.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include "rpmostree-core.h"
|
||||
#include "rpmostree-json-parsing.h"
|
||||
#include "rpmostree-rojig-build.h"
|
||||
@ -583,8 +582,8 @@ rpm_ostree_compose_context_new (const char *treefile_pathstr,
|
||||
/* Test whether or not bwrap is going to work - we will fail inside e.g. a Docker
|
||||
* container without --privileged or userns exposed.
|
||||
*/
|
||||
if (!(opt_download_only || opt_download_only_rpms) && !rpmostree_bwrap_selftest (error))
|
||||
return FALSE;
|
||||
if (!(opt_download_only || opt_download_only_rpms))
|
||||
rpmostreecxx::bubblewrap_selftest();
|
||||
|
||||
self->repo = ostree_repo_open_at (AT_FDCWD, opt_repo, cancellable, error);
|
||||
if (!self->repo)
|
||||
|
@ -37,7 +37,6 @@
|
||||
|
||||
#include "rpmostree-composeutil.h"
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include "rpmostree-core.h"
|
||||
#include "rpmostree-json-parsing.h"
|
||||
#include "rpmostree-rojig-build.h"
|
||||
|
@ -1,660 +0,0 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2016 Red Hat, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation; either version 2 of the licence or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <stdio.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-cxxrs.h"
|
||||
#include <utility>
|
||||
|
||||
static void
|
||||
teardown_rofiles (GLnxTmpDir *mnt_tmp);
|
||||
|
||||
void
|
||||
rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *arg;
|
||||
|
||||
va_start (args, argv_array);
|
||||
while ((arg = va_arg (args, char *)))
|
||||
g_ptr_array_add (argv_array, g_strdup (arg));
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
struct RpmOstreeBwrap {
|
||||
guint refcount;
|
||||
|
||||
gboolean executed;
|
||||
|
||||
rust::Box<rpmostreecxx::Bubblewrap> bwrap_rs;
|
||||
GSubprocessLauncher *launcher; /* 🚀 */
|
||||
GPtrArray *argv;
|
||||
const char *child_argv0;
|
||||
GLnxTmpDir rofiles_mnt_usr;
|
||||
GLnxTmpDir rofiles_mnt_etc;
|
||||
|
||||
GSpawnChildSetupFunc child_setup_func;
|
||||
gpointer child_setup_data;
|
||||
};
|
||||
|
||||
RpmOstreeBwrap *
|
||||
rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap)
|
||||
{
|
||||
bwrap->refcount++;
|
||||
return bwrap;
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap)
|
||||
{
|
||||
bwrap->refcount--;
|
||||
if (bwrap->refcount > 0)
|
||||
return;
|
||||
|
||||
teardown_rofiles (&bwrap->rofiles_mnt_usr);
|
||||
teardown_rofiles (&bwrap->rofiles_mnt_etc);
|
||||
|
||||
bwrap->bwrap_rs.~Box();
|
||||
g_clear_object (&bwrap->launcher);
|
||||
g_ptr_array_unref (bwrap->argv);
|
||||
g_free (bwrap);
|
||||
}
|
||||
|
||||
/* Configure the process to inherit stdin */
|
||||
void
|
||||
rpmostree_bwrap_set_inherit_stdin (RpmOstreeBwrap *bwrap)
|
||||
{
|
||||
g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDIN_INHERIT);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *arg;
|
||||
|
||||
g_assert (!bwrap->executed);
|
||||
|
||||
va_start (args, bwrap);
|
||||
while ((arg = va_arg (args, char *)))
|
||||
g_ptr_array_add (bwrap->argv, g_strdup (arg));
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Set /var to be read-only, but with a transient writable /var/tmp */
|
||||
void
|
||||
rpmostree_bwrap_var_tmp_tmpfs (RpmOstreeBwrap *bwrap)
|
||||
{
|
||||
rpmostree_bwrap_bind_read (bwrap, "./var", "/var");
|
||||
rpmostree_bwrap_append_bwrap_argv (bwrap, "--tmpfs", "/var/tmp", NULL);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_bind_read (RpmOstreeBwrap *bwrap, const char *src, const char *dest)
|
||||
{
|
||||
rpmostree_bwrap_append_bwrap_argv (bwrap, "--ro-bind", src, dest, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_bind_readwrite (RpmOstreeBwrap *bwrap, const char *src, const char *dest)
|
||||
{
|
||||
rpmostree_bwrap_append_bwrap_argv (bwrap, "--bind", src, dest, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *arg;
|
||||
|
||||
g_assert (!bwrap->executed);
|
||||
|
||||
va_start (args, bwrap);
|
||||
while ((arg = va_arg (args, char *)))
|
||||
{
|
||||
char *v = g_strdup (arg);
|
||||
g_ptr_array_add (bwrap->argv, v);
|
||||
/* Stash argv0 for error messages */
|
||||
if (!bwrap->child_argv0)
|
||||
bwrap->child_argv0 = v;
|
||||
}
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_append_child_argva (RpmOstreeBwrap *bwrap, int argc, char **argv)
|
||||
{
|
||||
g_assert (!bwrap->executed);
|
||||
g_assert_cmpint (argc, >=, 0);
|
||||
while (argc > 0)
|
||||
{
|
||||
const char *arg = *argv;
|
||||
g_assert (arg);
|
||||
g_ptr_array_add (bwrap->argv, g_strdup (arg));
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
child_setup_fchdir (gpointer user_data)
|
||||
{
|
||||
int fd = GPOINTER_TO_INT (user_data);
|
||||
if (fchdir (fd) < 0)
|
||||
err (1, "fchdir");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
setup_rofiles (RpmOstreeBwrap *bwrap,
|
||||
const char *path,
|
||||
GLnxTmpDir *mnt_tmp,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("rofiles setup", error);
|
||||
const char *relpath = path + strspn (path, "/");
|
||||
const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", relpath, NULL, NULL};
|
||||
|
||||
g_auto(GLnxTmpDir) local_mnt_tmp = { 0, };
|
||||
if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &local_mnt_tmp, error))
|
||||
return FALSE;
|
||||
|
||||
const char *rofiles_mntpath = local_mnt_tmp.path;
|
||||
rofiles_argv[3] = rofiles_mntpath;
|
||||
|
||||
int estatus;
|
||||
if (!g_spawn_sync (NULL, (char**)rofiles_argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
child_setup_fchdir, GINT_TO_POINTER (bwrap->bwrap_rs->get_rootfs_fd()),
|
||||
NULL, NULL, &estatus, error))
|
||||
return FALSE;
|
||||
if (!g_spawn_check_exit_status (estatus, error))
|
||||
return FALSE;
|
||||
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, path);
|
||||
|
||||
/* And transfer ownership of the tmpdir */
|
||||
*mnt_tmp = local_mnt_tmp;
|
||||
local_mnt_tmp.initialized = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* We don't want a failure to unmount to be fatal, so all we do here
|
||||
* is log. Though in practice what we *really* want is for the
|
||||
* fusermount to be in the bwrap namespace, and hence tied by the
|
||||
* kernel to the lifecycle of the container. This would require
|
||||
* special casing for somehow doing FUSE mounts in bwrap. Which
|
||||
* would be hard because NO_NEW_PRIVS turns off the setuid bits for
|
||||
* fuse.
|
||||
*
|
||||
* Or: just hard switch to overlayfs now that it will soon be
|
||||
* available even unprivileged https://lwn.net/Articles/803203/
|
||||
*/
|
||||
static void
|
||||
teardown_rofiles (GLnxTmpDir *mnt_tmp)
|
||||
{
|
||||
g_assert (mnt_tmp);
|
||||
if (!mnt_tmp->initialized)
|
||||
return;
|
||||
|
||||
g_autoptr(GError) tmp_error = NULL;
|
||||
const char *fusermount_argv[] = { "fusermount", "-u", mnt_tmp->path, NULL};
|
||||
int estatus;
|
||||
|
||||
GLNX_AUTO_PREFIX_ERROR ("rofiles teardown", &tmp_error);
|
||||
|
||||
if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL, NULL, &estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
rpmostree_journal_error (tmp_error);
|
||||
return;
|
||||
}
|
||||
if (!g_spawn_check_exit_status (estatus, &tmp_error))
|
||||
{
|
||||
g_prefix_error (&tmp_error, "Executing fusermount: ");
|
||||
rpmostree_journal_error (tmp_error);
|
||||
return;
|
||||
}
|
||||
|
||||
(void)glnx_tmpdir_delete (mnt_tmp, NULL, NULL);
|
||||
}
|
||||
|
||||
/* nspawn by default doesn't give us CAP_NET_ADMIN; see
|
||||
* https://pagure.io/releng/issue/6602#comment-71214
|
||||
* https://pagure.io/koji/pull-request/344#comment-21060
|
||||
*
|
||||
* Theoretically we should do capable(CAP_NET_ADMIN)
|
||||
* but that's a lot of ugly code, and the only known
|
||||
* place we hit this right now is nspawn. Plus
|
||||
* we want to use userns down the line anyways where
|
||||
* we'll regain CAP_NET_ADMIN.
|
||||
*/
|
||||
static gboolean
|
||||
running_in_nspawn (void)
|
||||
{
|
||||
return g_strcmp0 (getenv ("container"), "systemd-nspawn") == 0;
|
||||
}
|
||||
|
||||
RpmOstreeBwrap *
|
||||
rpmostree_bwrap_new_base (int rootfs_fd, GError **error)
|
||||
{
|
||||
g_autoptr(RpmOstreeBwrap) ret = g_new0 (RpmOstreeBwrap, 1);
|
||||
static const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"};
|
||||
|
||||
ret->refcount = 1;
|
||||
ret->bwrap_rs = rpmostreecxx::bubblewrap_new(rootfs_fd);
|
||||
ret->argv = g_ptr_array_new_with_free_func (g_free);
|
||||
ret->launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
|
||||
|
||||
/* Initialize launcher now; it may also be modified by our API methods */
|
||||
const char *current_lang = getenv ("LANG");
|
||||
if (!current_lang)
|
||||
current_lang = "C";
|
||||
|
||||
const char *lang_var = (const char*) glnx_strjoina ("LANG=", current_lang);
|
||||
/* This is similar to what systemd does, except:
|
||||
* - We drop /usr/local, since scripts shouldn't see it.
|
||||
* - We pull in the current process' LANG, since that's what people
|
||||
* have historically expected from RPM scripts.
|
||||
*/
|
||||
const char *bwrap_env[] = {"PATH=/usr/sbin:/usr/bin", lang_var, NULL};
|
||||
g_subprocess_launcher_set_environ (ret->launcher, (char**)bwrap_env);
|
||||
|
||||
/* ⚠⚠⚠ If you change this, also update scripts/bwrap-script-shell.sh ⚠⚠⚠ */
|
||||
rpmostree_bwrap_append_bwrap_argv (ret,
|
||||
"bwrap",
|
||||
"--dev", "/dev",
|
||||
"--proc", "/proc",
|
||||
"--dir", "/run",
|
||||
"--dir", "/tmp",
|
||||
"--chdir", "/",
|
||||
"--ro-bind", "/sys/block", "/sys/block",
|
||||
"--ro-bind", "/sys/bus", "/sys/bus",
|
||||
"--ro-bind", "/sys/class", "/sys/class",
|
||||
"--ro-bind", "/sys/dev", "/sys/dev",
|
||||
"--ro-bind", "/sys/devices", "/sys/devices",
|
||||
"--die-with-parent", /* Since 0.1.8 */
|
||||
/* Here we do all namespaces except the user one.
|
||||
* Down the line we want to do a userns too I think,
|
||||
* but it may need some mapping work.
|
||||
*/
|
||||
"--unshare-pid",
|
||||
"--unshare-uts",
|
||||
"--unshare-ipc",
|
||||
"--unshare-cgroup-try",
|
||||
NULL);
|
||||
|
||||
if (!running_in_nspawn ())
|
||||
rpmostree_bwrap_append_bwrap_argv (ret, "--unshare-net", NULL);
|
||||
|
||||
/* Capabilities; this is a subset of the Docker (1.13 at least) default.
|
||||
* Specifically we strip out in addition to that:
|
||||
*
|
||||
* "cap_net_raw" (no use for this in %post, and major source of security vulnerabilities)
|
||||
* "cap_mknod" (%post should not be making devices, it wouldn't be persistent anyways)
|
||||
* "cap_audit_write" (we shouldn't be auditing anything from here)
|
||||
* "cap_net_bind_service" (nothing should be doing IP networking at all)
|
||||
*
|
||||
* But crucially we're dropping a lot of other capabilities like
|
||||
* "cap_sys_admin", "cap_sys_module", etc that Docker also drops by default.
|
||||
* We don't want RPM scripts to be doing any of that. Instead, do it from
|
||||
* systemd unit files.
|
||||
*
|
||||
* Also this way we drop out any new capabilities that appear.
|
||||
*/
|
||||
if (getuid () == 0)
|
||||
rpmostree_bwrap_append_bwrap_argv (ret, "--cap-drop", "ALL",
|
||||
"--cap-add", "cap_chown",
|
||||
"--cap-add", "cap_dac_override",
|
||||
"--cap-add", "cap_fowner",
|
||||
"--cap-add", "cap_fsetid",
|
||||
"--cap-add", "cap_kill",
|
||||
"--cap-add", "cap_setgid",
|
||||
"--cap-add", "cap_setuid",
|
||||
"--cap-add", "cap_setpcap",
|
||||
"--cap-add", "cap_sys_chroot",
|
||||
"--cap-add", "cap_setfcap",
|
||||
NULL);
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (usr_links); i++)
|
||||
{
|
||||
const char *subdir = usr_links[i];
|
||||
struct stat stbuf;
|
||||
g_autofree char *srcpath = NULL;
|
||||
g_autofree char *destpath = NULL;
|
||||
|
||||
if (!glnx_fstatat_allow_noent (rootfs_fd, subdir, &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
if (errno == ENOENT || !S_ISLNK (stbuf.st_mode))
|
||||
continue;
|
||||
|
||||
srcpath = g_strconcat ("usr/", subdir, NULL);
|
||||
destpath = g_strconcat ("/", subdir, NULL);
|
||||
rpmostree_bwrap_append_bwrap_argv (ret, "--symlink", srcpath, destpath, NULL);
|
||||
}
|
||||
|
||||
return util::move_nullify (ret);
|
||||
}
|
||||
|
||||
RpmOstreeBwrap *
|
||||
rpmostree_bwrap_new (int rootfs_fd,
|
||||
RpmOstreeBwrapMutability is_mutable,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(RpmOstreeBwrap) ret = rpmostree_bwrap_new_base (rootfs_fd, error);
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
|
||||
switch (is_mutable)
|
||||
{
|
||||
case RPMOSTREE_BWRAP_IMMUTABLE:
|
||||
rpmostree_bwrap_bind_read (ret, "usr", "/usr");
|
||||
rpmostree_bwrap_bind_read (ret, "etc", "/etc");
|
||||
break;
|
||||
case RPMOSTREE_BWRAP_MUTATE_ROFILES:
|
||||
if (!setup_rofiles (ret, "/usr",
|
||||
&ret->rofiles_mnt_usr, error))
|
||||
return NULL;
|
||||
if (!setup_rofiles (ret, "/etc",
|
||||
&ret->rofiles_mnt_etc, error))
|
||||
return NULL;
|
||||
break;
|
||||
case RPMOSTREE_BWRAP_MUTATE_FREELY:
|
||||
rpmostree_bwrap_bind_readwrite (ret, "usr", "/usr");
|
||||
rpmostree_bwrap_bind_readwrite (ret, "etc", "/etc");
|
||||
break;
|
||||
}
|
||||
|
||||
return util::move_nullify (ret);
|
||||
}
|
||||
|
||||
static void
|
||||
bwrap_child_setup (gpointer data)
|
||||
{
|
||||
RpmOstreeBwrap *bwrap = (RpmOstreeBwrap*)data;
|
||||
|
||||
if (fchdir (bwrap->bwrap_rs->get_rootfs_fd()) < 0)
|
||||
err (1, "fchdir");
|
||||
|
||||
if (bwrap->child_setup_func)
|
||||
bwrap->child_setup_func (bwrap->child_setup_data);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap,
|
||||
GSpawnChildSetupFunc func,
|
||||
gpointer data)
|
||||
{
|
||||
g_assert (!bwrap->executed);
|
||||
bwrap->child_setup_func = func;
|
||||
bwrap->child_setup_data = data;
|
||||
}
|
||||
|
||||
|
||||
/* Set an environment variable in the child process */
|
||||
void
|
||||
rpmostree_bwrap_setenv (RpmOstreeBwrap *bwrap, const char *name, const char *value)
|
||||
{
|
||||
g_subprocess_launcher_setenv (bwrap->launcher, name, value, TRUE);
|
||||
}
|
||||
|
||||
/* Transfer ownership of @source_fd to child at @target_fd */
|
||||
void
|
||||
rpmostree_bwrap_take_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd,
|
||||
int target_fd)
|
||||
{
|
||||
g_subprocess_launcher_take_fd (bwrap->launcher, source_fd, target_fd);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_take_stdin_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd)
|
||||
{
|
||||
g_subprocess_launcher_take_stdin_fd (bwrap->launcher, source_fd);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_take_stdout_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd)
|
||||
{
|
||||
g_subprocess_launcher_take_stdout_fd (bwrap->launcher, source_fd);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_take_stderr_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd)
|
||||
{
|
||||
g_subprocess_launcher_take_stderr_fd (bwrap->launcher, source_fd);
|
||||
}
|
||||
|
||||
void
|
||||
rpmostree_bwrap_take_stdout_and_stderr_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd)
|
||||
{
|
||||
g_subprocess_launcher_take_stdout_fd (bwrap->launcher, source_fd);
|
||||
g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDERR_MERGE);
|
||||
}
|
||||
|
||||
/* Execute @bwrap, optionally capturing stdout or stderr. Must have been configured. After
|
||||
* executing this method, the @bwrap instance cannot be run again.
|
||||
*/
|
||||
gboolean
|
||||
rpmostree_bwrap_run_captured (RpmOstreeBwrap *bwrap,
|
||||
GBytes **stdout_buf,
|
||||
GBytes **stderr_buf,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (stdout_buf)
|
||||
g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE);
|
||||
if (stderr_buf)
|
||||
g_subprocess_launcher_set_flags (bwrap->launcher, G_SUBPROCESS_FLAGS_STDERR_PIPE);
|
||||
|
||||
const char *errmsg = (const char*) glnx_strjoina ("Executing bwrap(", (char*)bwrap->child_argv0, ")");
|
||||
GLNX_AUTO_PREFIX_ERROR (errmsg, error);
|
||||
|
||||
g_autoptr(GSubprocess) subproc = rpmostree_bwrap_execute (bwrap, error);
|
||||
if (!subproc)
|
||||
return FALSE;
|
||||
|
||||
if (stdout_buf || stderr_buf)
|
||||
{
|
||||
if (!g_subprocess_communicate (subproc, NULL, cancellable,
|
||||
stdout_buf, stderr_buf, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!g_subprocess_wait (subproc, cancellable, error))
|
||||
{
|
||||
/* Now, it's possible @cancellable has been set, which means the process
|
||||
* hasn't terminated yet. AFAIK that should be the only cause for the
|
||||
* process not having exited now, but we just kill the process regardless
|
||||
* on error here. The GSubprocess code ignores the request if we've
|
||||
* already reaped it.
|
||||
*
|
||||
* Right now we run bwrap --die-with-parent, but until we do the whole txn
|
||||
* as a subprocess, the script would leak until rpm-ostreed exited.
|
||||
*/
|
||||
g_subprocess_force_exit (subproc);
|
||||
return FALSE;
|
||||
}
|
||||
int estatus = g_subprocess_get_exit_status (subproc);
|
||||
if (!g_spawn_check_exit_status (estatus, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Execute @bwrap. Must have been configured. After executing this method, the @bwrap
|
||||
* instance cannot be run again.
|
||||
*/
|
||||
gboolean
|
||||
rpmostree_bwrap_run (RpmOstreeBwrap *bwrap,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
return rpmostree_bwrap_run_captured (bwrap, NULL, NULL, cancellable, error);
|
||||
}
|
||||
|
||||
GSubprocess *
|
||||
rpmostree_bwrap_execute (RpmOstreeBwrap *bwrap, GError **error)
|
||||
{
|
||||
g_autoptr(GSubprocessLauncher) launcher = util::move_nullify (bwrap->launcher);
|
||||
g_assert (!bwrap->executed);
|
||||
bwrap->executed = TRUE;
|
||||
|
||||
/* Add the final NULL */
|
||||
g_ptr_array_add (bwrap->argv, NULL);
|
||||
|
||||
g_subprocess_launcher_set_child_setup (launcher, bwrap_child_setup, bwrap, NULL);
|
||||
return g_subprocess_launcher_spawnv (launcher, (const char *const*)bwrap->argv->pdata, error);
|
||||
}
|
||||
|
||||
/* Execute /bin/true inside a bwrap container on the host */
|
||||
gboolean
|
||||
rpmostree_bwrap_selftest (GError **error)
|
||||
{
|
||||
glnx_autofd int host_root_dfd = -1;
|
||||
g_autoptr(RpmOstreeBwrap) bwrap = NULL;
|
||||
|
||||
if (!glnx_opendirat (AT_FDCWD, "/", TRUE, &host_root_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
bwrap = rpmostree_bwrap_new (host_root_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error);
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "true", NULL);
|
||||
|
||||
if (!rpmostree_bwrap_run (bwrap, NULL, error))
|
||||
{
|
||||
g_prefix_error (error, "bwrap test failed, see <https://github.com/projectatomic/rpm-ostree/pull/429>: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
namespace rpmostreecxx {
|
||||
|
||||
// High level interface to bwrap, currently used by the postprocess code.
|
||||
// This deals with renaming `/etc` to `/usr/etc` for example.
|
||||
void
|
||||
bwrap_run_mutable(int32_t rootfs_fd, rust::Str binpath,
|
||||
const rust::Vec<rust::String> &child_argv,
|
||||
bool unified_core_mode)
|
||||
{
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
/* For scripts, it's /etc, not /usr/etc */
|
||||
if (!glnx_fstatat_allow_noent (rootfs_fd, "etc", NULL, 0, &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
const bool renamed_usretc = (errno == ENOENT);
|
||||
if (renamed_usretc)
|
||||
{
|
||||
if (!glnx_renameat (rootfs_fd, "usr/etc", rootfs_fd, "etc", &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
/* But leave a compat symlink, as we used to bind mount, so scripts
|
||||
* could still use that too.
|
||||
*/
|
||||
if (symlinkat ("../etc", rootfs_fd, "usr/etc") < 0)
|
||||
{
|
||||
(void)glnx_throw_errno_prefix (&local_error, "symlinkat");
|
||||
util::throw_gerror(local_error);
|
||||
}
|
||||
}
|
||||
|
||||
RpmOstreeBwrapMutability mut =
|
||||
unified_core_mode ? RPMOSTREE_BWRAP_MUTATE_ROFILES : RPMOSTREE_BWRAP_MUTATE_FREELY;
|
||||
g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mut, &local_error);
|
||||
if (!bwrap)
|
||||
util::throw_gerror(local_error);
|
||||
|
||||
if (unified_core_mode)
|
||||
rpmostree_bwrap_bind_read (bwrap, "var", "/var");
|
||||
else
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var");
|
||||
|
||||
{ auto binpath_s = std::string(binpath);
|
||||
rpmostree_bwrap_append_child_argv (bwrap, binpath_s.c_str(), NULL);
|
||||
}
|
||||
|
||||
/* https://github.com/projectatomic/bubblewrap/issues/91 */
|
||||
{ bool first = true;
|
||||
for (auto &elt : child_argv)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
{
|
||||
auto s = std::string(elt); // NUL terminate
|
||||
rpmostree_bwrap_append_child_argv (bwrap, s.c_str(), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rpmostree_bwrap_run (bwrap, NULL, &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
|
||||
/* Remove the symlink and swap back */
|
||||
if (renamed_usretc)
|
||||
{
|
||||
if (!glnx_unlinkat (rootfs_fd, "usr/etc", 0, &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
if (!glnx_renameat (rootfs_fd, "etc", rootfs_fd, "usr/etc", &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
}
|
||||
}
|
||||
|
||||
rust::Vec<uint8_t>
|
||||
bwrap_run_captured(int32_t rootfs_dfd,
|
||||
const rust::Vec<rust::String> &child_argv)
|
||||
{
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
g_autoptr(RpmOstreeBwrap) bwrap =
|
||||
rpmostree_bwrap_new (rootfs_dfd, RPMOSTREE_BWRAP_IMMUTABLE, &local_error);
|
||||
if (!bwrap)
|
||||
util::throw_gerror(local_error);
|
||||
|
||||
for (const auto & arg : child_argv)
|
||||
{
|
||||
auto arg_c = std::string(arg);
|
||||
rpmostree_bwrap_append_child_argv (bwrap, arg_c.c_str(), NULL);
|
||||
}
|
||||
|
||||
g_autoptr(GBytes) out = NULL;
|
||||
if (!rpmostree_bwrap_run_captured (bwrap, &out, NULL, NULL, &local_error))
|
||||
util::throw_gerror(local_error);
|
||||
gsize len;
|
||||
auto cbuf = (const guint8*)g_bytes_get_data(out, &len);
|
||||
rust::Vec<uint8_t> r;
|
||||
r.reserve(len);
|
||||
for (auto i = 0; i < len; i++)
|
||||
r.push_back(cbuf[i]);
|
||||
return r;
|
||||
}
|
||||
|
||||
} /* namespace */
|
@ -1,105 +0,0 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2016 Colin Walters <walters@verbum.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published
|
||||
* by the Free Software Foundation; either version 2 of the licence or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostree.h>
|
||||
|
||||
#include "libglnx.h"
|
||||
|
||||
#include "rust/cxx.h"
|
||||
|
||||
namespace rpmostreecxx {
|
||||
|
||||
rust::Vec<uint8_t>
|
||||
bwrap_run_captured(int32_t rootfs_dfd,
|
||||
const rust::Vec<rust::String> &child_argv);
|
||||
|
||||
void bwrap_run_mutable(int32_t rootfs_dfd, rust::Str binpath,
|
||||
const rust::Vec<rust::String> &child_argv,
|
||||
bool unified_core_mode);
|
||||
|
||||
}
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
RPMOSTREE_BWRAP_IMMUTABLE = 0,
|
||||
RPMOSTREE_BWRAP_MUTATE_ROFILES,
|
||||
RPMOSTREE_BWRAP_MUTATE_FREELY
|
||||
} RpmOstreeBwrapMutability;
|
||||
|
||||
typedef struct RpmOstreeBwrap RpmOstreeBwrap;
|
||||
RpmOstreeBwrap *rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap);
|
||||
void rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(RpmOstreeBwrap, rpmostree_bwrap_unref)
|
||||
|
||||
/* TODO - move this utility elsewhere */
|
||||
void rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
RpmOstreeBwrap *rpmostree_bwrap_new_base (int rootfs, GError **error);
|
||||
|
||||
RpmOstreeBwrap *rpmostree_bwrap_new (int rootfs,
|
||||
RpmOstreeBwrapMutability is_mutable,
|
||||
GError **error);
|
||||
|
||||
void rpmostree_bwrap_set_inherit_stdin (RpmOstreeBwrap *bwrap);
|
||||
void rpmostree_bwrap_var_tmp_tmpfs (RpmOstreeBwrap *bwrap);
|
||||
void rpmostree_bwrap_bind_read (RpmOstreeBwrap *bwrap, const char *src, const char *dest);
|
||||
void rpmostree_bwrap_bind_readwrite (RpmOstreeBwrap *bwrap, const char *src, const char *dest);
|
||||
void rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED;
|
||||
void rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED;
|
||||
void rpmostree_bwrap_append_child_argva (RpmOstreeBwrap *bwrap, int argc, char **argv);
|
||||
|
||||
void rpmostree_bwrap_setenv (RpmOstreeBwrap *bwrap, const char *name, const char *value);
|
||||
|
||||
void rpmostree_bwrap_take_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd,
|
||||
int target_fd);
|
||||
|
||||
void rpmostree_bwrap_take_stdin_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd);
|
||||
void rpmostree_bwrap_take_stdout_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd);
|
||||
void rpmostree_bwrap_take_stderr_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd);
|
||||
|
||||
void rpmostree_bwrap_take_stdout_and_stderr_fd (RpmOstreeBwrap *bwrap,
|
||||
int source_fd);
|
||||
|
||||
void rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap,
|
||||
GSpawnChildSetupFunc func,
|
||||
gpointer data);
|
||||
|
||||
gboolean rpmostree_bwrap_run_captured (RpmOstreeBwrap *bwrap,
|
||||
GBytes **stdout_buf,
|
||||
GBytes **stderr_buf,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
GSubprocess * rpmostree_bwrap_execute (RpmOstreeBwrap *bwrap, GError **error);
|
||||
|
||||
gboolean rpmostree_bwrap_run (RpmOstreeBwrap *bwrap,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean rpmostree_bwrap_selftest (GError **error);
|
||||
|
||||
G_END_DECLS
|
@ -36,7 +36,6 @@
|
||||
|
||||
#include "rpmostree-core.h"
|
||||
#include "rpmostree-kernel.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include "rpmostree-cxxrs.h"
|
||||
#include "rpmostree-util.h"
|
||||
|
||||
@ -510,7 +509,6 @@ rpmostree_run_dracut (int rootfs_dfd,
|
||||
"mkdir -p /tmp/dracut && dracut $extra_argv -v --add ostree --tmpdir=/tmp/dracut -f /tmp/initramfs.img \"$@\"\n"
|
||||
"cat /tmp/initramfs.img >/proc/self/fd/3\n",
|
||||
destdir.c_str());
|
||||
g_autoptr(RpmOstreeBwrap) bwrap = NULL;
|
||||
g_autoptr(GPtrArray) rebuild_argv = NULL;
|
||||
g_auto(GLnxTmpfile) tmpf = { 0, };
|
||||
|
||||
@ -567,42 +565,39 @@ rpmostree_run_dracut (int rootfs_dfd,
|
||||
&tmpf, error))
|
||||
return FALSE;
|
||||
|
||||
auto bwrap = rpmostreecxx::bubblewrap_new(rootfs_dfd);
|
||||
if (use_root_etc)
|
||||
{
|
||||
bwrap = rpmostree_bwrap_new_base (rootfs_dfd, error);
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
rpmostree_bwrap_bind_read (bwrap, "/etc", "/etc");
|
||||
rpmostree_bwrap_bind_read (bwrap, "usr", "/usr");
|
||||
bwrap->bind_read("/etc", "/etc");
|
||||
bwrap->bind_read("usr", "/usr");
|
||||
}
|
||||
else
|
||||
{
|
||||
bwrap = rpmostree_bwrap_new_base (rootfs_dfd, error);
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
rpmostree_bwrap_bind_read (bwrap, "usr/etc", "/etc");
|
||||
rpmostree_bwrap_bind_read (bwrap, "usr", "/usr");
|
||||
bwrap->bind_read("usr/etc", "/etc");
|
||||
bwrap->bind_read("usr", "/usr");
|
||||
}
|
||||
|
||||
if (dracut_host_tmpdir)
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, dracut_host_tmpdir->path, "/tmp/dracut");
|
||||
bwrap->bind_readwrite(dracut_host_tmpdir->path, "/tmp/dracut");
|
||||
|
||||
/* Set up argv and run */
|
||||
rpmostree_bwrap_append_child_argv (bwrap, (char*)glnx_basename (rpmostree_dracut_wrapper_path), NULL);
|
||||
bwrap->append_child_arg((const char*)glnx_basename (rpmostree_dracut_wrapper_path));
|
||||
for (char **iter = (char**)argv; iter && *iter; iter++)
|
||||
rpmostree_bwrap_append_child_argv (bwrap, *iter, NULL);
|
||||
bwrap->append_child_arg(*iter);
|
||||
|
||||
if (kver)
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "--kver", kver, NULL);
|
||||
{
|
||||
bwrap->append_child_arg("--kver");
|
||||
bwrap->append_child_arg(kver);
|
||||
}
|
||||
|
||||
// Pass the tempfile to the child as fd 3
|
||||
glnx_autofd int tmpf_child = fcntl (tmpf.fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (tmpf_child < 0)
|
||||
return glnx_throw_errno_prefix (error, "fnctl");
|
||||
rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&tmpf_child), 3);
|
||||
bwrap->take_fd (glnx_steal_fd (&tmpf_child), 3);
|
||||
|
||||
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
|
||||
return FALSE;
|
||||
bwrap->run(*cancellable);
|
||||
|
||||
/* For FIPS mode we need /dev/urandom pre-created because the FIPS
|
||||
* standards authors require that randomness is tested in a
|
||||
|
@ -42,7 +42,6 @@
|
||||
|
||||
#include "rpmostree-postprocess.h"
|
||||
#include "rpmostree-kernel.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include "rpmostree-output.h"
|
||||
#include "rpmostree-rpm-util.h"
|
||||
#include "rpmostree-core.h"
|
||||
@ -213,7 +212,7 @@ rpmostree_postprocess_run_depmod (int rootfs_dfd,
|
||||
GError **error)
|
||||
{
|
||||
rust::Vec child_argv = { rust::String("depmod"), rust::String("-a"), rust::String(kver) };
|
||||
rpmostreecxx::bwrap_run_mutable (rootfs_dfd, "depmod", child_argv, (bool)unified_core_mode);
|
||||
rpmostreecxx::bubblewrap_run_sync(rootfs_dfd, child_argv, false, (bool)unified_core_mode);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -822,7 +821,7 @@ rpmostree_postprocess_final (int rootfs_dfd,
|
||||
/* Now regenerate SELinux policy so that postprocess scripts from users and from us
|
||||
* (e.g. the /etc/default/useradd incision) that affect it are baked in. */
|
||||
rust::Vec child_argv = { rust::String("semodule"), rust::String("-nB") };
|
||||
rpmostreecxx::bwrap_run_mutable (rootfs_dfd, "semodule", child_argv, (bool)unified_core_mode);
|
||||
rpmostreecxx::bubblewrap_run_sync (rootfs_dfd, child_argv, false, (bool)unified_core_mode);
|
||||
}
|
||||
|
||||
gboolean container = FALSE;
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include "rpmostree-output.h"
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-cxxrs.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include <err.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
#include "libglnx.h"
|
||||
@ -341,33 +340,37 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd,
|
||||
*/
|
||||
gboolean is_glibc_locales = strcmp (pkg_script, "glibc-all-langpacks.posttrans") == 0 ||
|
||||
strcmp (pkg_script, "glibc-common.post") == 0;
|
||||
RpmOstreeBwrapMutability mutability =
|
||||
(is_glibc_locales || !enable_fuse) ? RPMOSTREE_BWRAP_MUTATE_FREELY : RPMOSTREE_BWRAP_MUTATE_ROFILES;
|
||||
g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mutability, error);
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
rpmostreecxx::BubblewrapMutability mutability =
|
||||
(is_glibc_locales || !enable_fuse) ? rpmostreecxx::BubblewrapMutability::MutateFreely : rpmostreecxx::BubblewrapMutability::RoFiles;
|
||||
auto bwrap = rpmostreecxx::bubblewrap_new_with_mutability (rootfs_fd, mutability);
|
||||
/* Scripts can see a /var with compat links like alternatives */
|
||||
rpmostree_bwrap_var_tmp_tmpfs (bwrap);
|
||||
bwrap->var_tmp_tmpfs();
|
||||
|
||||
struct stat stbuf;
|
||||
if (glnx_fstatat (rootfs_fd, "usr/lib/opt", &stbuf, AT_SYMLINK_NOFOLLOW, NULL) && S_ISDIR(stbuf.st_mode))
|
||||
rpmostree_bwrap_append_bwrap_argv (bwrap, "--symlink", "usr/lib/opt", "/opt", NULL);
|
||||
{
|
||||
bwrap->append_bwrap_arg("--symlink");
|
||||
bwrap->append_bwrap_arg("usr/lib/opt");
|
||||
bwrap->append_bwrap_arg("/opt");
|
||||
}
|
||||
|
||||
/* Don't let scripts see the base rpm database by default */
|
||||
rpmostree_bwrap_bind_read (bwrap, "usr/share/empty", "usr/share/rpm");
|
||||
bwrap->bind_read("usr/share/empty", "usr/share/rpm");
|
||||
|
||||
/* Add ostree-booted API; some scriptlets may work differently on OSTree systems; e.g.
|
||||
* akmods. Just create it manually; /run is usually tmpfs, but scriptlets shouldn't be
|
||||
* adding stuff there anyway. */
|
||||
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "run", 0755, cancellable, error))
|
||||
return FALSE;
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, "./run", "/run");
|
||||
bwrap->bind_readwrite("./run", "/run");
|
||||
|
||||
rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&devnull_fd), devnull_target_fd);
|
||||
rpmostree_bwrap_append_bwrap_argv (bwrap, "--ro-bind-data", bwrap_devnull_fd, "/run/ostree-booted", NULL);
|
||||
bwrap->take_fd(glnx_steal_fd (&devnull_fd), devnull_target_fd);
|
||||
bwrap->append_bwrap_arg("--ro-bind-data");
|
||||
bwrap->append_bwrap_arg(bwrap_devnull_fd);
|
||||
bwrap->append_bwrap_arg("/run/ostree-booted");
|
||||
|
||||
if (var_lib_rpm_statedir)
|
||||
rpmostree_bwrap_bind_readwrite (bwrap, var_lib_rpm_statedir->path, "/var/lib/rpm-state");
|
||||
bwrap->bind_readwrite(var_lib_rpm_statedir->path, "/var/lib/rpm-state");
|
||||
|
||||
gboolean debugging_script = g_strcmp0 (g_getenv ("RPMOSTREE_SCRIPT_DEBUG"), pkg_script) == 0;
|
||||
|
||||
@ -375,7 +378,7 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd,
|
||||
* "systemctl,verbs: Introduce SYSTEMD_OFFLINE environment variable"
|
||||
* https://github.com/systemd/systemd/commit/f38951a62837a00a0b1ff42d007e9396b347742d
|
||||
*/
|
||||
rpmostree_bwrap_setenv (bwrap, "SYSTEMD_OFFLINE", "1");
|
||||
bwrap->setenv("SYSTEMD_OFFLINE", "1");
|
||||
|
||||
/* FDs that need to be held open until we exec; they're
|
||||
* owned by the GSubprocessLauncher instance.
|
||||
@ -390,15 +393,15 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd,
|
||||
stdin_fd = fcntl (provided_stdin_fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (stdin_fd == -1)
|
||||
return glnx_throw_errno_prefix (error, "fcntl");
|
||||
rpmostree_bwrap_take_stdin_fd (bwrap, stdin_fd);
|
||||
bwrap->take_stdin_fd(stdin_fd);
|
||||
}
|
||||
|
||||
GLnxTmpfile buffered_output = { 0, };
|
||||
g_auto(GLnxTmpfile) buffered_output = { 0, };
|
||||
const char *id = glnx_strjoina ("rpm-ostree(", pkg_script, ")");
|
||||
if (debugging_script || stdin_fd == STDIN_FILENO)
|
||||
{
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/bash", NULL);
|
||||
rpmostree_bwrap_set_inherit_stdin (bwrap);
|
||||
bwrap->append_child_arg ("/usr/bin/bash");
|
||||
bwrap->set_inherit_stdin();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -416,12 +419,12 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd,
|
||||
stdout_fd = sd_journal_stream_fd (id, LOG_INFO, 0);
|
||||
if (stdout_fd < 0)
|
||||
return glnx_prefix_error (error, "While creating stdout stream fd");
|
||||
rpmostree_bwrap_take_stdout_fd (bwrap, stdout_fd);
|
||||
bwrap->take_stdout_fd(stdout_fd);
|
||||
|
||||
stderr_fd = sd_journal_stream_fd (id, LOG_ERR, 0);
|
||||
if (stderr_fd < 0)
|
||||
return glnx_prefix_error (error, "While creating stderr stream fd");
|
||||
rpmostree_bwrap_take_stderr_fd (bwrap, stderr_fd);
|
||||
bwrap->take_stderr_fd(stderr_fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -431,46 +434,31 @@ rpmostree_run_script_in_bwrap_container (int rootfs_fd,
|
||||
buffered_output_fd_child = fcntl (buffered_output.fd, F_DUPFD_CLOEXEC, 3);
|
||||
if (buffered_output_fd_child < 0)
|
||||
return glnx_throw_errno_prefix (error, "fcntl");
|
||||
rpmostree_bwrap_take_stdout_and_stderr_fd (bwrap, buffered_output_fd_child);
|
||||
}
|
||||
|
||||
const char *script_trace = g_getenv ("RPMOSTREE_SCRIPT_TRACE");
|
||||
if (script_trace)
|
||||
{
|
||||
int trace_argc = 0;
|
||||
char **trace_argv = NULL;
|
||||
if (!g_shell_parse_argv (script_trace, &trace_argc, &trace_argv, error))
|
||||
return glnx_prefix_error (error, "Parsing '%s'", script_trace);
|
||||
rpmostree_bwrap_append_child_argva (bwrap, trace_argc, trace_argv);
|
||||
g_strfreev (trace_argv);
|
||||
bwrap->take_stdout_and_stderr_fd(buffered_output_fd_child);
|
||||
}
|
||||
|
||||
const int script_child_fd = 5;
|
||||
rpmostree_bwrap_take_fd (bwrap, glnx_steal_fd (&script_memfd), script_child_fd);
|
||||
bwrap->take_fd(glnx_steal_fd (&script_memfd), script_child_fd);
|
||||
g_autofree char *procpath = g_strdup_printf ("/proc/self/fd/%d", script_child_fd);
|
||||
rpmostree_bwrap_append_child_argv (bwrap,
|
||||
interp,
|
||||
procpath,
|
||||
script_arg,
|
||||
NULL);
|
||||
bwrap->append_child_arg(interp);
|
||||
bwrap->append_child_arg(procpath);
|
||||
if (script_arg != nullptr)
|
||||
bwrap->append_child_arg(script_arg);
|
||||
}
|
||||
|
||||
|
||||
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
|
||||
{
|
||||
dump_buffered_output_noerr (pkg_script, &buffered_output);
|
||||
try {
|
||||
g_assert(cancellable);
|
||||
bwrap->run(*cancellable);
|
||||
} catch (std::exception&e) {
|
||||
dump_buffered_output_noerr(pkg_script, &buffered_output);
|
||||
auto msg = e.what();
|
||||
/* If errors go to the journal, help the user/admin find them there */
|
||||
if (error && rpmostreecxx::running_in_systemd())
|
||||
{
|
||||
g_assert (*error);
|
||||
g_autofree char *errmsg = (*error)->message;
|
||||
(*error)->message =
|
||||
g_strdup_printf ("%s; run `journalctl -t '%s'` for more information", errmsg, id);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
dump_buffered_output_noerr (pkg_script, &buffered_output);
|
||||
if (rpmostreecxx::running_in_systemd())
|
||||
return glnx_throw (error, "%s; run `journalctl -t '%s'` for more information", msg, id);
|
||||
else
|
||||
return glnx_throw (error, "%s", msg);
|
||||
}
|
||||
dump_buffered_output_noerr(pkg_script, &buffered_output);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@ -971,16 +959,14 @@ rpmostree_deployment_sanitycheck_true (int rootfs_fd,
|
||||
if (getenv ("RPMOSTREE_SKIP_SANITYCHECK"))
|
||||
return TRUE;
|
||||
|
||||
GLNX_AUTO_PREFIX_ERROR ("Sanity-checking final rootfs", error);
|
||||
g_autoptr(RpmOstreeBwrap) bwrap =
|
||||
rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_IMMUTABLE, error);
|
||||
|
||||
if (!bwrap)
|
||||
return FALSE;
|
||||
rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL);
|
||||
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
g_assert(cancellable);
|
||||
auto bwrap = rpmostreecxx::bubblewrap_new_with_mutability(rootfs_fd, rpmostreecxx::BubblewrapMutability::Immutable);
|
||||
bwrap->append_child_arg("/usr/bin/true");
|
||||
try {
|
||||
bwrap->run(*cancellable);
|
||||
} catch (std::exception& e) {
|
||||
util::rethrow_prefixed(e, "Sanity-checking final rootfs");
|
||||
}
|
||||
sd_journal_print (LOG_INFO, "sanitycheck(/usr/bin/true) successful");
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ if vm_rpmostree install rmrf 2>err.txt; then
|
||||
fi
|
||||
vm_cmd test -f /home/core/somedata -a -f /etc/passwd -a -f /tmp/sometmpfile -a -f /var/tmp/sometmpfile
|
||||
# This is the error today, we may improve it later
|
||||
assert_file_has_content err.txt 'error: Sanity-checking final rootfs: Executing bwrap(/usr/bin/true)'
|
||||
assert_file_has_content err.txt 'error: Sanity-checking final rootfs:.*bwrap(/usr/bin/true)'
|
||||
echo "ok impervious to rm -rf post"
|
||||
|
||||
cursor=$(vm_get_journal_cursor)
|
||||
|
Loading…
Reference in New Issue
Block a user