Rewrite livefs
Now always based on an overlayfs:
f2773c1b55
This fixes a whole swath of problems with the previous design,
including the danger in replacing `/usr/lib/ostree-boot` which
broke booting for some people.
Further, we don't need to push a rollback deployment; the livefs
changes are always transient. So now we store livefs state
in `/run` instead of in the origin file.
Since we're doing a rewrite, it's now in Rust for much more safety.
We also always work in terms of incremental diffs between commits;
the previous huge hammer of swapping `/usr` was way too dangerous.
This commit is contained in:
parent
213d8f0aa2
commit
a76ddf0cef
@ -35,7 +35,6 @@ subprocess = "0.2.6"
|
|||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
libdnf-sys = { path = "libdnf-sys", version = "0.1.0" }
|
libdnf-sys = { path = "libdnf-sys", version = "0.1.0" }
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rpmostree_rust"
|
name = "rpmostree_rust"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
@ -8,7 +8,6 @@ NOTICE: The C header definitions are canonical, please update those first
|
|||||||
then synchronize the entries here.
|
then synchronize the entries here.
|
||||||
!*/
|
!*/
|
||||||
|
|
||||||
use crate::syscore::ffi::RpmOstreeOrigin;
|
|
||||||
use libdnf_sys::DnfPackage;
|
use libdnf_sys::DnfPackage;
|
||||||
|
|
||||||
// From `libpriv/rpmostree-rpm-util.h`.
|
// From `libpriv/rpmostree-rpm-util.h`.
|
||||||
@ -19,12 +18,3 @@ extern "C" {
|
|||||||
gerror: *mut *mut glib_sys::GError,
|
gerror: *mut *mut glib_sys::GError,
|
||||||
) -> libc::c_int;
|
) -> libc::c_int;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From `libpriv/rpmostree-origin.h`.
|
|
||||||
extern "C" {
|
|
||||||
pub(crate) fn rpmostree_origin_get_live_state(
|
|
||||||
origin: *mut RpmOstreeOrigin,
|
|
||||||
out_inprogress: *mut *mut libc::c_char,
|
|
||||||
out_livereplaced: *mut *mut libc::c_char,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -20,10 +20,12 @@ mod initramfs;
|
|||||||
pub use self::initramfs::ffi::*;
|
pub use self::initramfs::ffi::*;
|
||||||
mod lockfile;
|
mod lockfile;
|
||||||
pub use self::lockfile::*;
|
pub use self::lockfile::*;
|
||||||
|
mod livefs;
|
||||||
|
pub use self::livefs::*;
|
||||||
|
mod ostree_diff;
|
||||||
|
mod ostree_utils;
|
||||||
mod progress;
|
mod progress;
|
||||||
pub use self::progress::*;
|
pub use self::progress::*;
|
||||||
mod syscore;
|
|
||||||
pub use self::syscore::ffi::*;
|
|
||||||
mod testutils;
|
mod testutils;
|
||||||
pub use self::testutils::*;
|
pub use self::testutils::*;
|
||||||
mod treefile;
|
mod treefile;
|
||||||
|
499
rust/src/livefs.rs
Normal file
499
rust/src/livefs.rs
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
//! Core implementation logic for "livefs" which applies
|
||||||
|
//! changes to an overlayfs on top of `/usr` in the booted
|
||||||
|
//! deployment.
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use nix::sys::statvfs;
|
||||||
|
use openat_ext::OpenatDirExt;
|
||||||
|
use ostree::DeploymentUnlockedState;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// The directory where ostree stores transient per-deployment state.
|
||||||
|
/// This is currently semi-private to ostree; we should add an API to
|
||||||
|
/// access it.
|
||||||
|
const OSTREE_RUNSTATE_DIR: &str = "/run/ostree/deployment-state";
|
||||||
|
/// Filename we use for serialized state, stored in the above directory.
|
||||||
|
const LIVEFS_STATE_NAME: &str = "rpmostree-livefs-state.json";
|
||||||
|
|
||||||
|
/// The model for livefs state.
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
struct LiveFsState {
|
||||||
|
/// The OSTree commit that the running root filesystem is using,
|
||||||
|
/// as distinct from the one it was booted with.
|
||||||
|
commit: Option<String>,
|
||||||
|
/// Set when a livefs operation is in progress; if the process
|
||||||
|
/// is interrupted, some files from this commit may exist
|
||||||
|
/// on disk but in an incomplete state.
|
||||||
|
inprogress_commit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the transient state directory for a deployment; TODO
|
||||||
|
/// upstream this into libostree.
|
||||||
|
fn get_runstate_dir(deploy: &ostree::Deployment) -> PathBuf {
|
||||||
|
format!(
|
||||||
|
"{}/{}.{}",
|
||||||
|
OSTREE_RUNSTATE_DIR,
|
||||||
|
deploy.get_csum().expect("csum"),
|
||||||
|
deploy.get_deployserial()
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the livefs state
|
||||||
|
fn get_livefs_state(deploy: &ostree::Deployment) -> Result<Option<LiveFsState>> {
|
||||||
|
let root = openat::Dir::open("/")?;
|
||||||
|
if let Some(f) = root.open_file_optional(&get_runstate_dir(deploy).join(LIVEFS_STATE_NAME))? {
|
||||||
|
let s: LiveFsState = serde_json::from_reader(std::io::BufReader::new(f))?;
|
||||||
|
Ok(Some(s))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write new livefs state
|
||||||
|
fn write_livefs_state(deploy: &ostree::Deployment, state: &LiveFsState) -> Result<()> {
|
||||||
|
let rundir = get_runstate_dir(deploy);
|
||||||
|
let rundir = openat::Dir::open(&rundir)?;
|
||||||
|
rundir.write_file_with(LIVEFS_STATE_NAME, 0o644, |w| -> Result<_> {
|
||||||
|
Ok(serde_json::to_writer(w, state)?)
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the relative parent directory of a path
|
||||||
|
fn relpath_dir(p: &Path) -> Result<&Path> {
|
||||||
|
Ok(p.strip_prefix("/")?.parent().expect("parent"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a path buffer we can provide to libostree for checkout
|
||||||
|
fn subpath(diff: &crate::ostree_diff::FileTreeDiff, p: &Path) -> Option<PathBuf> {
|
||||||
|
if let Some(ref d) = diff.subdir {
|
||||||
|
let p = p.strip_prefix("/").expect("prefix");
|
||||||
|
Some(Path::new(d).join(p))
|
||||||
|
} else {
|
||||||
|
Some(p.to_path_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a diff, apply it to the target directory, which should be a checkout of the source commit.
|
||||||
|
fn apply_diff(
|
||||||
|
repo: &ostree::Repo,
|
||||||
|
diff: &crate::ostree_diff::FileTreeDiff,
|
||||||
|
commit: &str,
|
||||||
|
destdir: &openat::Dir,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !diff.changed_dirs.is_empty() {
|
||||||
|
anyhow::bail!("Changed directories are not supported yet");
|
||||||
|
}
|
||||||
|
let cancellable = gio::NONE_CANCELLABLE;
|
||||||
|
// This applies to all added/changed content, we just
|
||||||
|
// overwrite `subpath` in each run.
|
||||||
|
let mut opts = ostree::RepoCheckoutAtOptions {
|
||||||
|
overwrite_mode: ostree::RepoCheckoutOverwriteMode::UnionFiles,
|
||||||
|
force_copy: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
// Check out new directories and files
|
||||||
|
for d in diff.added_dirs.iter().map(Path::new) {
|
||||||
|
opts.subpath = subpath(diff, &d);
|
||||||
|
let t = d.strip_prefix("/")?;
|
||||||
|
repo.checkout_at(Some(&opts), destdir.as_raw_fd(), t, commit, cancellable)
|
||||||
|
.with_context(|| format!("Checking out added dir {:?}", d))?;
|
||||||
|
}
|
||||||
|
for d in diff.added_files.iter().map(Path::new) {
|
||||||
|
opts.subpath = subpath(diff, &d);
|
||||||
|
repo.checkout_at(
|
||||||
|
Some(&opts),
|
||||||
|
destdir.as_raw_fd(),
|
||||||
|
relpath_dir(d)?,
|
||||||
|
commit,
|
||||||
|
cancellable,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Checking out added file {:?}", d))?;
|
||||||
|
}
|
||||||
|
// Changed files in existing directories
|
||||||
|
for d in diff.changed_files.iter().map(Path::new) {
|
||||||
|
opts.subpath = subpath(diff, &d);
|
||||||
|
repo.checkout_at(
|
||||||
|
Some(&opts),
|
||||||
|
destdir.as_raw_fd(),
|
||||||
|
relpath_dir(d)?,
|
||||||
|
commit,
|
||||||
|
cancellable,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Checking out changed file {:?}", d))?;
|
||||||
|
}
|
||||||
|
assert!(diff.changed_dirs.is_empty());
|
||||||
|
|
||||||
|
// Finally clean up removed directories and files together. We use
|
||||||
|
// rayon here just because we can.
|
||||||
|
diff.removed_files
|
||||||
|
.par_iter()
|
||||||
|
.chain(diff.removed_dirs.par_iter())
|
||||||
|
.try_for_each(|d| -> Result<()> {
|
||||||
|
let d = d.strip_prefix("/").expect("prefix");
|
||||||
|
destdir
|
||||||
|
.remove_all(d)
|
||||||
|
.with_context(|| format!("Failed to remove {:?}", d))?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Special handling for `/etc` - we currently just add new default files/directories.
|
||||||
|
/// We don't try to delete anything yet, because doing so could mess up the actual
|
||||||
|
/// `/etc` merge on reboot between the real deployment. Much of the logic here
|
||||||
|
/// is similar to what libostree core does for `/etc` on upgrades. If we ever
|
||||||
|
/// push livefs down into libostree, this logic could be shared.
|
||||||
|
fn update_etc(
|
||||||
|
repo: &ostree::Repo,
|
||||||
|
diff: &crate::ostree_diff::FileTreeDiff,
|
||||||
|
sepolicy: &ostree::SePolicy,
|
||||||
|
commit: &str,
|
||||||
|
destdir: &openat::Dir,
|
||||||
|
) -> Result<()> {
|
||||||
|
let expected_subpath = "/usr";
|
||||||
|
// We stripped both /usr and /etc, we need to readd them both
|
||||||
|
// for the checkout.
|
||||||
|
fn filtermap_paths(s: &String) -> Option<(Option<PathBuf>, &Path)> {
|
||||||
|
s.strip_prefix("/etc").map(|p| {
|
||||||
|
let p = Path::new(p).strip_prefix("/").expect("prefix");
|
||||||
|
(Some(Path::new("/usr/etc").join(p)), p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// For some reason in Rust the `parent()` of `foo` is just the empty string `""`; we
|
||||||
|
// need it to be the self-link `.` path.
|
||||||
|
fn canonicalized_parent(p: &Path) -> &Path {
|
||||||
|
match p.parent() {
|
||||||
|
Some(p) if p.as_os_str().len() == 0 => Path::new("."),
|
||||||
|
Some(p) => p,
|
||||||
|
None => Path::new("."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic apply_diff() above in theory could work anywhere.
|
||||||
|
// But this code is only designed for /etc.
|
||||||
|
assert_eq!(diff.subdir.as_ref().expect("subpath"), expected_subpath);
|
||||||
|
if !diff.changed_dirs.is_empty() {
|
||||||
|
anyhow::bail!("Changed directories are not supported yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancellable = gio::NONE_CANCELLABLE;
|
||||||
|
// This applies to all added/changed content, we just
|
||||||
|
// overwrite `subpath` in each run.
|
||||||
|
let mut opts = ostree::RepoCheckoutAtOptions {
|
||||||
|
overwrite_mode: ostree::RepoCheckoutOverwriteMode::UnionFiles,
|
||||||
|
force_copy: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
// The labels for /etc and /usr/etc may differ; ensure that we label
|
||||||
|
// the files with the /etc target, even though we're checking out
|
||||||
|
// from /usr/etc. This is the same as what libostree does.
|
||||||
|
if sepolicy.get_name().is_some() {
|
||||||
|
opts.sepolicy = Some(sepolicy.clone());
|
||||||
|
}
|
||||||
|
// Added directories and files
|
||||||
|
for (subpath, target) in diff.added_dirs.iter().filter_map(filtermap_paths) {
|
||||||
|
opts.subpath = subpath;
|
||||||
|
repo.checkout_at(
|
||||||
|
Some(&opts),
|
||||||
|
destdir.as_raw_fd(),
|
||||||
|
target,
|
||||||
|
commit,
|
||||||
|
cancellable,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Checking out added /etc dir {:?}", (&opts.subpath, target)))?;
|
||||||
|
}
|
||||||
|
for (subpath, target) in diff.added_files.iter().filter_map(filtermap_paths) {
|
||||||
|
opts.subpath = subpath;
|
||||||
|
repo.checkout_at(
|
||||||
|
Some(&opts),
|
||||||
|
destdir.as_raw_fd(),
|
||||||
|
canonicalized_parent(target),
|
||||||
|
commit,
|
||||||
|
cancellable,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Checking out added /etc file {:?}", (&opts.subpath, target)))?;
|
||||||
|
}
|
||||||
|
// Now changed files
|
||||||
|
for (subpath, target) in diff.changed_files.iter().filter_map(filtermap_paths) {
|
||||||
|
opts.subpath = subpath;
|
||||||
|
repo.checkout_at(
|
||||||
|
Some(&opts),
|
||||||
|
destdir.as_raw_fd(),
|
||||||
|
canonicalized_parent(target),
|
||||||
|
commit,
|
||||||
|
cancellable,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Checking out changed /etc file {:?}",
|
||||||
|
(&opts.subpath, target)
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
assert!(diff.changed_dirs.is_empty());
|
||||||
|
|
||||||
|
// And finally clean up removed files and directories.
|
||||||
|
diff.removed_files
|
||||||
|
.par_iter()
|
||||||
|
.chain(diff.removed_dirs.par_iter())
|
||||||
|
.filter_map(filtermap_paths)
|
||||||
|
.try_for_each(|(_, target)| -> Result<()> {
|
||||||
|
destdir
|
||||||
|
.remove_all(target)
|
||||||
|
.with_context(|| format!("Failed to remove {:?}", target))?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our main process uses MountFlags=slave set up by systemd;
|
||||||
|
// this is what allows us to e.g. remount /sysroot writable
|
||||||
|
// just inside our mount namespace. However, in this case
|
||||||
|
// we actually need to escape our mount namespace and affect
|
||||||
|
// the "main" mount namespace so that other processes will
|
||||||
|
// see the overlayfs.
|
||||||
|
fn unlock_transient(sysroot: &ostree::Sysroot) -> Result<()> {
|
||||||
|
// Temporarily drop the lock
|
||||||
|
sysroot.unlock();
|
||||||
|
let status = Command::new("systemd-run")
|
||||||
|
.args(&[
|
||||||
|
"-u",
|
||||||
|
"rpm-ostree-unlock",
|
||||||
|
"--wait",
|
||||||
|
"--",
|
||||||
|
"ostree",
|
||||||
|
"admin",
|
||||||
|
"unlock",
|
||||||
|
"--transient",
|
||||||
|
])
|
||||||
|
.status();
|
||||||
|
sysroot.lock()?;
|
||||||
|
let status = status?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!("Failed to unlock --transient");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `systemd-tmpfiles` via `systemd-run` so we escape our mount namespace.
|
||||||
|
/// This allows our `ProtectHome=` in the unit file to work.
|
||||||
|
fn rerun_tmpfiles() -> Result<()> {
|
||||||
|
for prefix in &["/run", "/var"] {
|
||||||
|
let status = Command::new("systemd-run")
|
||||||
|
.args(&[
|
||||||
|
"-u",
|
||||||
|
"rpm-ostree-tmpfiles",
|
||||||
|
"--wait",
|
||||||
|
"--",
|
||||||
|
"systemd-tmpfiles",
|
||||||
|
"--create",
|
||||||
|
"--prefix",
|
||||||
|
prefix,
|
||||||
|
])
|
||||||
|
.status()?;
|
||||||
|
if !status.success() {
|
||||||
|
bail!("Failed to invoke systemd-tmpfiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of `rpm-ostree ex livefs`.
|
||||||
|
fn livefs(sysroot: &ostree::Sysroot, target: Option<&str>) -> Result<()> {
|
||||||
|
let repo = sysroot.repo().expect("repo");
|
||||||
|
let repo = &repo;
|
||||||
|
|
||||||
|
let booted = if let Some(b) = sysroot.get_booted_deployment() {
|
||||||
|
b
|
||||||
|
} else {
|
||||||
|
bail!("Not booted into an OSTree system")
|
||||||
|
};
|
||||||
|
let osname = booted.get_osname().expect("osname");
|
||||||
|
let booted_commit = booted.get_csum().expect("csum");
|
||||||
|
let booted_commit = booted_commit.as_str();
|
||||||
|
|
||||||
|
let target_commit = if let Some(t) = target {
|
||||||
|
Cow::Borrowed(t)
|
||||||
|
} else {
|
||||||
|
match crate::ostree_utils::sysroot_query_deployments_for(sysroot, osname.as_str()) {
|
||||||
|
(Some(pending), _) => {
|
||||||
|
let pending_commit = pending.get_csum().expect("csum");
|
||||||
|
let pending_commit = pending_commit.as_str();
|
||||||
|
Cow::Owned(pending_commit.to_string())
|
||||||
|
}
|
||||||
|
(None, _) => {
|
||||||
|
anyhow::bail!("No target commit specified and no pending deployment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = get_livefs_state(&booted)?;
|
||||||
|
if state.is_none() {
|
||||||
|
match booted.get_unlocked() {
|
||||||
|
DeploymentUnlockedState::None => {
|
||||||
|
unlock_transient(sysroot)?;
|
||||||
|
}
|
||||||
|
DeploymentUnlockedState::Transient | DeploymentUnlockedState::Development => {}
|
||||||
|
s => {
|
||||||
|
bail!("livefs is incompatible with unlock state: {}", s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
match booted.get_unlocked() {
|
||||||
|
DeploymentUnlockedState::Transient | DeploymentUnlockedState::Development => {}
|
||||||
|
s => {
|
||||||
|
bail!("deployment not unlocked, is in state: {}", s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// In the transient mode, remount writable - this affects just the rpm-ostreed
|
||||||
|
// mount namespace. In the future it'd be nicer to run transactions as subprocesses
|
||||||
|
// so we don't lift the writable protection for the main rpm-ostree process.
|
||||||
|
if statvfs::statvfs("/usr")?
|
||||||
|
.flags()
|
||||||
|
.contains(statvfs::FsFlags::ST_RDONLY)
|
||||||
|
{
|
||||||
|
use nix::mount::MsFlags;
|
||||||
|
let none: Option<&str> = None;
|
||||||
|
nix::mount::mount(
|
||||||
|
none,
|
||||||
|
"/usr",
|
||||||
|
none,
|
||||||
|
MsFlags::MS_REMOUNT | MsFlags::MS_SILENT,
|
||||||
|
none,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref state) = state {
|
||||||
|
if let Some(ref inprogress) = state.inprogress_commit {
|
||||||
|
if inprogress.as_str() != target_commit {
|
||||||
|
bail!(
|
||||||
|
"Previously interrupted while targeting commit {}, cannot change target to {}",
|
||||||
|
inprogress,
|
||||||
|
target_commit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_commit = state
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.commit.as_ref().map(|s| s.as_str()))
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(booted_commit);
|
||||||
|
let diff = crate::ostree_diff::diff(repo, source_commit, &target_commit, Some("/usr"))
|
||||||
|
.context("Failed computing diff")?;
|
||||||
|
|
||||||
|
let mut state = state.unwrap_or_default();
|
||||||
|
|
||||||
|
let rootfs_dfd = openat::Dir::open("/")?;
|
||||||
|
let sepolicy = ostree::SePolicy::new_at(rootfs_dfd.as_raw_fd(), gio::NONE_CANCELLABLE)?;
|
||||||
|
|
||||||
|
// Record that we're targeting this commit
|
||||||
|
state.inprogress_commit = Some(target_commit.to_string());
|
||||||
|
write_livefs_state(&booted, &state)?;
|
||||||
|
|
||||||
|
// The heart of things: updating the overlayfs on /usr
|
||||||
|
apply_diff(repo, &diff, &target_commit, &openat::Dir::open("/usr")?)?;
|
||||||
|
|
||||||
|
// The other important bits are /etc and /var
|
||||||
|
update_etc(
|
||||||
|
repo,
|
||||||
|
&diff,
|
||||||
|
&sepolicy,
|
||||||
|
&target_commit,
|
||||||
|
&openat::Dir::open("/etc")?,
|
||||||
|
)?;
|
||||||
|
rerun_tmpfiles()?;
|
||||||
|
|
||||||
|
// Success! Update the recorded state.
|
||||||
|
state.commit = Some(target_commit.to_string());
|
||||||
|
state.inprogress_commit = None;
|
||||||
|
write_livefs_state(&booted, &state)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_subpath() {
|
||||||
|
let d = crate::ostree_diff::FileTreeDiff {
|
||||||
|
subdir: Some("/usr".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let s = subpath(&d, Path::new("/foo"));
|
||||||
|
assert_eq!(s.as_ref().map(|s| s.as_path()), Some(Path::new("/usr/foo")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ffi {
|
||||||
|
use super::*;
|
||||||
|
use glib;
|
||||||
|
use glib::translate::*;
|
||||||
|
use glib::GString;
|
||||||
|
use glib_sys;
|
||||||
|
use libc;
|
||||||
|
|
||||||
|
use crate::ffiutil::*;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn ror_livefs_get_state(
|
||||||
|
sysroot: *mut ostree_sys::OstreeSysroot,
|
||||||
|
deployment: *mut ostree_sys::OstreeDeployment,
|
||||||
|
out_inprogress: *mut *mut libc::c_char,
|
||||||
|
out_replaced: *mut *mut libc::c_char,
|
||||||
|
gerror: *mut *mut glib_sys::GError,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let _sysroot: ostree::Sysroot = unsafe { from_glib_none(sysroot) };
|
||||||
|
let deployment: ostree::Deployment = unsafe { from_glib_none(deployment) };
|
||||||
|
match get_livefs_state(&deployment) {
|
||||||
|
Ok(Some(state)) => {
|
||||||
|
unsafe {
|
||||||
|
if let Some(c) = state.inprogress_commit {
|
||||||
|
*out_inprogress = c.to_glib_full();
|
||||||
|
}
|
||||||
|
if let Some(c) = state.commit {
|
||||||
|
*out_replaced = c.to_glib_full();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
Ok(None) => 1,
|
||||||
|
Err(ref e) => {
|
||||||
|
error_to_glib(e, gerror);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn ror_transaction_livefs(
|
||||||
|
sysroot: *mut ostree_sys::OstreeSysroot,
|
||||||
|
target: *const libc::c_char,
|
||||||
|
gerror: *mut *mut glib_sys::GError,
|
||||||
|
) -> libc::c_int {
|
||||||
|
let sysroot: ostree::Sysroot = unsafe { from_glib_none(sysroot) };
|
||||||
|
let target: Borrowed<Option<GString>> = unsafe { from_glib_borrow(target) };
|
||||||
|
// The reference hole goes deep
|
||||||
|
let target = target.as_ref().as_ref().map(|s| s.as_str());
|
||||||
|
int_glib_error(livefs(&sysroot, target), gerror)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub use self::ffi::*;
|
170
rust/src/ostree_diff.rs
Normal file
170
rust/src/ostree_diff.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Red Hat, Inc.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use gio::prelude::*;
|
||||||
|
use ostree::RepoFileExt;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
/// Like `g_file_query_info()`, but return None if the target doesn't exist.
|
||||||
|
fn query_info_optional(
|
||||||
|
f: &gio::File,
|
||||||
|
queryattrs: &str,
|
||||||
|
queryflags: gio::FileQueryInfoFlags,
|
||||||
|
) -> Result<Option<gio::FileInfo>> {
|
||||||
|
let cancellable = gio::NONE_CANCELLABLE;
|
||||||
|
match f.query_info(queryattrs, queryflags, cancellable) {
|
||||||
|
Ok(i) => Ok(Some(i)),
|
||||||
|
Err(e) => {
|
||||||
|
if let Some(ref e2) = e.kind::<gio::IOErrorEnum>() {
|
||||||
|
match e2 {
|
||||||
|
gio::IOErrorEnum::NotFound => Ok(None),
|
||||||
|
_ => return Err(e.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type FileSet = BTreeSet<String>;
|
||||||
|
|
||||||
|
/// Diff between two ostree commits.
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct FileTreeDiff {
|
||||||
|
/// The prefix passed for diffing, e.g. /usr
|
||||||
|
pub(crate) subdir: Option<String>,
|
||||||
|
/// Files that are new in an existing directory
|
||||||
|
pub(crate) added_files: FileSet,
|
||||||
|
/// New directories
|
||||||
|
pub(crate) added_dirs: FileSet,
|
||||||
|
/// Files removed
|
||||||
|
pub(crate) removed_files: FileSet,
|
||||||
|
/// Directories removed (recursively)
|
||||||
|
pub(crate) removed_dirs: FileSet,
|
||||||
|
/// Files that changed (in any way, metadata or content)
|
||||||
|
pub(crate) changed_files: FileSet,
|
||||||
|
/// Directories that changed mode/permissions
|
||||||
|
pub(crate) changed_dirs: FileSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff_recurse(
|
||||||
|
prefix: &str,
|
||||||
|
diff: &mut FileTreeDiff,
|
||||||
|
from: &ostree::RepoFile,
|
||||||
|
to: &ostree::RepoFile,
|
||||||
|
) -> Result<()> {
|
||||||
|
let cancellable = gio::NONE_CANCELLABLE;
|
||||||
|
let queryattrs = "standard::name,standard::type";
|
||||||
|
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
|
||||||
|
let from_iter = from.enumerate_children(queryattrs, queryflags, cancellable)?;
|
||||||
|
|
||||||
|
// Iterate over the source (from) directory, and compare with the
|
||||||
|
// target (to) directory. This generates removals and changes.
|
||||||
|
while let Some(from_info) = from_iter.next_file(cancellable)? {
|
||||||
|
let from_child = from_iter.get_child(&from_info).expect("file");
|
||||||
|
let name = from_info.get_name().expect("name");
|
||||||
|
let name = name.to_str().expect("UTF-8 ostree name");
|
||||||
|
let path = format!("{}{}", prefix, name);
|
||||||
|
let to_child = to.get_child(&name).expect("child");
|
||||||
|
let to_info = query_info_optional(&to_child, queryattrs, queryflags)
|
||||||
|
.context("querying optional to")?;
|
||||||
|
let is_dir = match from_info.get_file_type() {
|
||||||
|
gio::FileType::Directory => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if to_info.is_some() {
|
||||||
|
let to_child = to_child.downcast::<ostree::RepoFile>().expect("downcast");
|
||||||
|
to_child.ensure_resolved()?;
|
||||||
|
let from_child = from_child.downcast::<ostree::RepoFile>().expect("downcast");
|
||||||
|
from_child.ensure_resolved()?;
|
||||||
|
|
||||||
|
if is_dir {
|
||||||
|
let from_contents_checksum =
|
||||||
|
from_child.tree_get_contents_checksum().expect("checksum");
|
||||||
|
let to_contents_checksum = to_child.tree_get_contents_checksum().expect("checksum");
|
||||||
|
if from_contents_checksum != to_contents_checksum {
|
||||||
|
let subpath = format!("{}/", path);
|
||||||
|
diff_recurse(&subpath, diff, &from_child, &to_child)?;
|
||||||
|
}
|
||||||
|
let from_meta_checksum = from_child.tree_get_metadata_checksum().expect("checksum");
|
||||||
|
let to_meta_checksum = to_child.tree_get_metadata_checksum().expect("checksum");
|
||||||
|
if from_meta_checksum != to_meta_checksum {
|
||||||
|
diff.changed_dirs.insert(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let from_checksum = from_child.get_checksum().expect("checksum");
|
||||||
|
let to_checksum = to_child.get_checksum().expect("checksum");
|
||||||
|
if from_checksum != to_checksum {
|
||||||
|
diff.changed_files.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if is_dir {
|
||||||
|
diff.removed_dirs.insert(path);
|
||||||
|
} else {
|
||||||
|
diff.removed_files.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Iterate over the target (to) directory, and find any
|
||||||
|
// files/directories which were not present in the source.
|
||||||
|
let to_iter = to.enumerate_children(queryattrs, queryflags, cancellable)?;
|
||||||
|
while let Some(to_info) = to_iter.next_file(cancellable)? {
|
||||||
|
let name = to_info.get_name().expect("name");
|
||||||
|
let name = name.to_str().expect("UTF-8 ostree name");
|
||||||
|
let path = format!("{}{}", prefix, name);
|
||||||
|
let from_child = from.get_child(name).expect("child");
|
||||||
|
let from_info = query_info_optional(&from_child, queryattrs, queryflags)
|
||||||
|
.context("querying optional from")?;
|
||||||
|
if from_info.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let is_dir = match to_info.get_file_type() {
|
||||||
|
gio::FileType::Directory => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if is_dir {
|
||||||
|
diff.added_dirs.insert(path);
|
||||||
|
} else {
|
||||||
|
diff.added_files.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given two ostree commits, compute the diff between them.
|
||||||
|
pub(crate) fn diff<P: AsRef<str>>(
|
||||||
|
repo: &ostree::Repo,
|
||||||
|
from: &str,
|
||||||
|
to: &str,
|
||||||
|
subdir: Option<P>,
|
||||||
|
) -> Result<FileTreeDiff> {
|
||||||
|
let subdir = subdir.as_ref();
|
||||||
|
let subdir = subdir.map(|s| s.as_ref());
|
||||||
|
let (fromroot, _) = repo.read_commit(from, gio::NONE_CANCELLABLE)?;
|
||||||
|
let (toroot, _) = repo.read_commit(to, gio::NONE_CANCELLABLE)?;
|
||||||
|
let (fromroot, toroot) = if let Some(subdir) = subdir {
|
||||||
|
(
|
||||||
|
fromroot.resolve_relative_path(subdir).expect("path"),
|
||||||
|
toroot.resolve_relative_path(subdir).expect("path"),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(fromroot, toroot)
|
||||||
|
};
|
||||||
|
let fromroot = fromroot.downcast::<ostree::RepoFile>().expect("downcast");
|
||||||
|
fromroot.ensure_resolved()?;
|
||||||
|
let toroot = toroot.downcast::<ostree::RepoFile>().expect("downcast");
|
||||||
|
toroot.ensure_resolved()?;
|
||||||
|
let mut diff = FileTreeDiff {
|
||||||
|
subdir: subdir.map(|s| s.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
diff_recurse("/", &mut diff, &fromroot, &toroot)?;
|
||||||
|
Ok(diff)
|
||||||
|
}
|
20
rust/src/ostree_utils.rs
Normal file
20
rust/src/ostree_utils.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//! Utility helpers or workarounds for incorrectly bound things in ostree-rs
|
||||||
|
use glib::translate::*;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
pub(crate) fn sysroot_query_deployments_for(
|
||||||
|
sysroot: &ostree::Sysroot,
|
||||||
|
osname: &str,
|
||||||
|
) -> (Option<ostree::Deployment>, Option<ostree::Deployment>) {
|
||||||
|
unsafe {
|
||||||
|
let mut out_pending = ptr::null_mut();
|
||||||
|
let mut out_rollback = ptr::null_mut();
|
||||||
|
ostree_sys::ostree_sysroot_query_deployments_for(
|
||||||
|
sysroot.to_glib_none().0,
|
||||||
|
osname.to_glib_none().0,
|
||||||
|
&mut out_pending,
|
||||||
|
&mut out_rollback,
|
||||||
|
);
|
||||||
|
(from_glib_full(out_pending), from_glib_full(out_rollback))
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
use self::ffi::RpmOstreeOrigin;
|
|
||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
/// Reference to an `RpmOstreeOrigin`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OriginRef {
|
|
||||||
origin: NonNull<RpmOstreeOrigin>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OriginRef {
|
|
||||||
/// Build a reference object from a C pointer.
|
|
||||||
fn from_ffi_ptr(oref: *mut RpmOstreeOrigin) -> Self {
|
|
||||||
Self {
|
|
||||||
origin: NonNull::new(oref).expect("NULL RpmOstreeOrigin"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get `livefs` details for this deployment origin.
|
|
||||||
pub(crate) fn get_live_state<'o>(&'o self) -> OriginLiveState<'o> {
|
|
||||||
use crate::includes::rpmostree_origin_get_live_state;
|
|
||||||
use glib::translate::from_glib_full;
|
|
||||||
|
|
||||||
let mut out_inprogress: *mut libc::c_char = std::ptr::null_mut();
|
|
||||||
let mut out_livereplaced: *mut libc::c_char = std::ptr::null_mut();
|
|
||||||
unsafe {
|
|
||||||
rpmostree_origin_get_live_state(
|
|
||||||
self.origin.as_ptr(),
|
|
||||||
&mut out_inprogress,
|
|
||||||
&mut out_livereplaced,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let in_progress = unsafe { from_glib_full(out_inprogress) };
|
|
||||||
let replaced = unsafe { from_glib_full(out_livereplaced) };
|
|
||||||
|
|
||||||
OriginLiveState {
|
|
||||||
_origin: self,
|
|
||||||
in_progress,
|
|
||||||
replaced,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `livefs` state and details for a given deployment origin.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct OriginLiveState<'o> {
|
|
||||||
/// Underlying deployment origin.
|
|
||||||
_origin: &'o OriginRef,
|
|
||||||
/// Checksum for the in-progress livefs.
|
|
||||||
pub in_progress: Option<String>,
|
|
||||||
/// Checksum for the underlying replaced commit.
|
|
||||||
pub replaced: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'o> OriginLiveState<'o> {
|
|
||||||
/// Return whether the given deployment is live-modified.
|
|
||||||
pub(crate) fn is_live(self) -> bool {
|
|
||||||
self.in_progress.is_some() || self.replaced.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod ffi {
|
|
||||||
use super::OriginRef;
|
|
||||||
|
|
||||||
/// Opaque type for C interop: RpmOstreeOrigin.
|
|
||||||
pub enum RpmOstreeOrigin {}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn ror_origin_is_live(origin_ptr: *mut RpmOstreeOrigin) -> libc::c_int {
|
|
||||||
let origin = OriginRef::from_ffi_ptr(origin_ptr);
|
|
||||||
let livestate = origin.get_live_state();
|
|
||||||
livestate.is_live().into()
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,15 +29,10 @@
|
|||||||
|
|
||||||
#include <libglnx.h>
|
#include <libglnx.h>
|
||||||
|
|
||||||
static gboolean opt_dry_run;
|
static char *opt_target;
|
||||||
static gboolean opt_replace;
|
|
||||||
static gboolean opt_consented;
|
|
||||||
|
|
||||||
static GOptionEntry option_entries[] = {
|
static GOptionEntry option_entries[] = {
|
||||||
{ "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Only perform analysis, do not make changes", NULL },
|
{ "target", 0, 0, G_OPTION_ARG_NONE, &opt_target, "Target provided commit instead of pending deployment", NULL },
|
||||||
{ "i-like-danger", 0, 0, G_OPTION_ARG_NONE, &opt_consented, "Consent to the dangers that livefs may pose", NULL },
|
|
||||||
/* Known broken with kernel updates; see https://github.com/projectatomic/rpm-ostree/issues/1495 */
|
|
||||||
{ "dangerous-do-not-use-replace", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_replace, "Completely replace all files in /usr (known broken)", NULL },
|
|
||||||
{ NULL }
|
{ NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,8 +42,8 @@ get_args_variant (void)
|
|||||||
GVariantDict dict;
|
GVariantDict dict;
|
||||||
|
|
||||||
g_variant_dict_init (&dict, NULL);
|
g_variant_dict_init (&dict, NULL);
|
||||||
g_variant_dict_insert (&dict, "dry-run", "b", opt_dry_run);
|
if (opt_target)
|
||||||
g_variant_dict_insert (&dict, "replace", "b", opt_replace);
|
g_variant_dict_insert (&dict, "target", "s", opt_target);
|
||||||
|
|
||||||
return g_variant_dict_end (&dict);
|
return g_variant_dict_end (&dict);
|
||||||
}
|
}
|
||||||
@ -75,10 +70,6 @@ rpmostree_ex_builtin_livefs (int argc,
|
|||||||
error))
|
error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (!opt_consented)
|
|
||||||
return glnx_throw (error, "livefs is currently considered dangerous; "
|
|
||||||
"pass --i-like-danger to override");
|
|
||||||
|
|
||||||
glnx_unref_object RPMOSTreeOS *os_proxy = NULL;
|
glnx_unref_object RPMOSTreeOS *os_proxy = NULL;
|
||||||
glnx_unref_object RPMOSTreeOSExperimental *osexperimental_proxy = NULL;
|
glnx_unref_object RPMOSTreeOSExperimental *osexperimental_proxy = NULL;
|
||||||
if (!rpmostree_load_os_proxies (sysroot_proxy, NULL,
|
if (!rpmostree_load_os_proxies (sysroot_proxy, NULL,
|
||||||
|
@ -308,10 +308,31 @@ rpmostree_syscore_cleanup (OstreeSysroot *sysroot,
|
|||||||
cancellable, error))
|
cancellable, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
|
OstreeDeployment *booted = ostree_sysroot_get_booted_deployment (sysroot);
|
||||||
|
g_autofree char *live_inprogress = NULL;
|
||||||
|
g_autofree char *live_replaced = NULL;
|
||||||
|
if (booted)
|
||||||
|
{
|
||||||
|
if (!ror_livefs_get_state (sysroot, booted, &live_inprogress, &live_replaced, error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* And do a prune */
|
/* And do a prune */
|
||||||
guint64 freed_space;
|
guint64 freed_space;
|
||||||
gint n_objects_total, n_objects_pruned;
|
gint n_objects_total, n_objects_pruned;
|
||||||
{ g_autoptr(GHashTable) reachable = ostree_repo_traverse_new_reachable ();
|
{ g_autoptr(GHashTable) reachable = ostree_repo_traverse_new_reachable ();
|
||||||
|
|
||||||
|
/* We don't currently write refs for these since the content can be
|
||||||
|
* ephemeral; add them to the strong set */
|
||||||
|
if (live_inprogress &&
|
||||||
|
!ostree_repo_traverse_commit_union (repo, live_inprogress, 0, reachable,
|
||||||
|
cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
if (live_replaced &&
|
||||||
|
!ostree_repo_traverse_commit_union (repo, live_replaced, 0, reachable,
|
||||||
|
cancellable, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
OstreeRepoPruneOptions opts = { OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, reachable };
|
OstreeRepoPruneOptions opts = { OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, reachable };
|
||||||
if (!ostree_sysroot_cleanup_prune_repo (sysroot, &opts, &n_objects_total,
|
if (!ostree_sysroot_cleanup_prune_repo (sysroot, &opts, &n_objects_total,
|
||||||
&n_objects_pruned, &freed_space,
|
&n_objects_pruned, &freed_space,
|
||||||
@ -351,6 +372,17 @@ rpmostree_syscore_get_origin_merge_deployment (OstreeSysroot *self, const char *
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return TRUE in *out_is_live if the target deployment has a live overlay */
|
||||||
|
gboolean
|
||||||
|
rpmostree_syscore_livefs_query (OstreeSysroot *self, OstreeDeployment *deployment, gboolean *out_is_live, GError **error)
|
||||||
|
{
|
||||||
|
g_autofree char *live_inprogress = NULL;
|
||||||
|
g_autofree char *live_replaced = NULL;
|
||||||
|
if (!ror_livefs_get_state (self, deployment, &live_inprogress, &live_replaced, error))
|
||||||
|
return FALSE;
|
||||||
|
*out_is_live = (live_inprogress != NULL) || (live_replaced != NULL);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy of currently private _ostree_sysroot_bump_mtime()
|
/* Copy of currently private _ostree_sysroot_bump_mtime()
|
||||||
* until we decide to either formalize that, or have a method
|
* until we decide to either formalize that, or have a method
|
||||||
@ -446,7 +478,7 @@ rpmostree_syscore_write_deployment (OstreeSysroot *sysroot,
|
|||||||
if (booted)
|
if (booted)
|
||||||
{
|
{
|
||||||
gboolean is_live;
|
gboolean is_live;
|
||||||
if (!rpmostree_syscore_deployment_is_live (booted, &is_live, error))
|
if (!rpmostree_syscore_livefs_query (sysroot, booted, &is_live, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (is_live)
|
if (is_live)
|
||||||
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
|
flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK;
|
||||||
@ -463,33 +495,3 @@ rpmostree_syscore_write_deployment (OstreeSysroot *sysroot,
|
|||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load the checksums that describe the "livefs" state of the given
|
|
||||||
* deployment.
|
|
||||||
*/
|
|
||||||
gboolean
|
|
||||||
rpmostree_syscore_deployment_get_live (OstreeDeployment *deployment,
|
|
||||||
char **out_inprogress_checksum,
|
|
||||||
char **out_livereplaced_checksum,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error);
|
|
||||||
if (!origin)
|
|
||||||
return FALSE;
|
|
||||||
rpmostree_origin_get_live_state (origin, out_inprogress_checksum, out_livereplaced_checksum);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set @out_is_live to %TRUE if the deployment is live-modified */
|
|
||||||
gboolean
|
|
||||||
rpmostree_syscore_deployment_is_live (OstreeDeployment *deployment,
|
|
||||||
gboolean *out_is_live,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error);
|
|
||||||
if (!origin)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
*out_is_live = ror_origin_is_live(origin);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
@ -45,17 +45,7 @@ OstreeDeployment *rpmostree_syscore_get_origin_merge_deployment (OstreeSysroot *
|
|||||||
|
|
||||||
gboolean rpmostree_syscore_bump_mtime (OstreeSysroot *self, GError **error);
|
gboolean rpmostree_syscore_bump_mtime (OstreeSysroot *self, GError **error);
|
||||||
|
|
||||||
#define RPMOSTREE_LIVE_INPROGRESS_XATTR "user.rpmostree-live-inprogress"
|
gboolean rpmostree_syscore_livefs_query (OstreeSysroot *self, OstreeDeployment *deployment, gboolean *out_is_live, GError **error);
|
||||||
#define RPMOSTREE_LIVE_REPLACED_XATTR "user.rpmostree-live-replaced"
|
|
||||||
|
|
||||||
gboolean rpmostree_syscore_deployment_get_live (OstreeDeployment *deployment,
|
|
||||||
char **out_inprogress_checksum,
|
|
||||||
char **out_livereplaced_checksum,
|
|
||||||
GError **error);
|
|
||||||
|
|
||||||
gboolean rpmostree_syscore_deployment_is_live (OstreeDeployment *deployment,
|
|
||||||
gboolean *out_is_live,
|
|
||||||
GError **error);
|
|
||||||
|
|
||||||
GPtrArray *rpmostree_syscore_filter_deployments (OstreeSysroot *sysroot,
|
GPtrArray *rpmostree_syscore_filter_deployments (OstreeSysroot *sysroot,
|
||||||
const char *osname,
|
const char *osname,
|
||||||
|
@ -593,7 +593,7 @@ try_load_base_rsack_from_pending (RpmOstreeSysrootUpgrader *self,
|
|||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
gboolean is_live;
|
gboolean is_live;
|
||||||
if (!rpmostree_syscore_deployment_is_live (self->origin_merge_deployment, &is_live, error))
|
if (!rpmostree_syscore_livefs_query (self->sysroot, self->origin_merge_deployment, &is_live, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
/* livefs invalidates the deployment */
|
/* livefs invalidates the deployment */
|
||||||
|
@ -261,6 +261,8 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot,
|
|||||||
if (!origin)
|
if (!origin)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
const gboolean is_booted = g_strcmp0 (booted_id, id) == 0;
|
||||||
|
|
||||||
RpmOstreeRefspecType refspec_type;
|
RpmOstreeRefspecType refspec_type;
|
||||||
g_autofree char *refspec = rpmostree_origin_get_full_refspec (origin, &refspec_type);
|
g_autofree char *refspec = rpmostree_origin_get_full_refspec (origin, &refspec_type);
|
||||||
|
|
||||||
@ -364,16 +366,19 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_autofree char *live_inprogress = NULL;
|
|
||||||
g_autofree char *live_replaced = NULL;
|
|
||||||
if (!rpmostree_syscore_deployment_get_live (deployment, &live_inprogress,
|
|
||||||
&live_replaced, error))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (live_inprogress)
|
if (is_booted)
|
||||||
g_variant_dict_insert (&dict, "live-inprogress", "s", live_inprogress);
|
{
|
||||||
if (live_replaced)
|
g_autofree char *live_inprogress = NULL;
|
||||||
g_variant_dict_insert (&dict, "live-replaced", "s", live_replaced);
|
g_autofree char *live_replaced = NULL;
|
||||||
|
if (!ror_livefs_get_state (sysroot, deployment, &live_inprogress, &live_replaced, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (live_inprogress)
|
||||||
|
g_variant_dict_insert (&dict, "live-inprogress", "s", live_inprogress);
|
||||||
|
if (live_replaced)
|
||||||
|
g_variant_dict_insert (&dict, "live-replaced", "s", live_replaced);
|
||||||
|
}
|
||||||
|
|
||||||
if (ostree_deployment_is_staged (deployment))
|
if (ostree_deployment_is_staged (deployment))
|
||||||
{
|
{
|
||||||
@ -416,7 +421,7 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot,
|
|||||||
rpmostree_origin_get_initramfs_etc_files (origin));
|
rpmostree_origin_get_initramfs_etc_files (origin));
|
||||||
|
|
||||||
if (booted_id != NULL)
|
if (booted_id != NULL)
|
||||||
g_variant_dict_insert (&dict, "booted", "b", g_strcmp0 (booted_id, id) == 0);
|
g_variant_dict_insert (&dict, "booted", "b", is_booted);
|
||||||
|
|
||||||
return g_variant_dict_end (&dict);
|
return g_variant_dict_end (&dict);
|
||||||
}
|
}
|
||||||
|
@ -115,23 +115,6 @@ osexperimental_handle_moo (RPMOSTreeOSExperimental *interface,
|
|||||||
rpmostree_osexperimental_complete_moo (interface, invocation, result);
|
rpmostree_osexperimental_complete_moo (interface, invocation, result);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
static RpmOstreeTransactionLiveFsFlags
|
|
||||||
livefs_flags_from_options (GVariant *options)
|
|
||||||
{
|
|
||||||
RpmOstreeTransactionLiveFsFlags ret = 0;
|
|
||||||
GVariantDict options_dict;
|
|
||||||
gboolean opt = FALSE;
|
|
||||||
|
|
||||||
g_variant_dict_init (&options_dict, options);
|
|
||||||
if (g_variant_dict_lookup (&options_dict, "dry-run", "b", &opt) && opt)
|
|
||||||
ret |= RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN;
|
|
||||||
if (g_variant_dict_lookup (&options_dict, "replace", "b", &opt) && opt)
|
|
||||||
ret |= RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE;
|
|
||||||
|
|
||||||
g_variant_dict_clear (&options_dict);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
osexperimental_handle_live_fs (RPMOSTreeOSExperimental *interface,
|
osexperimental_handle_live_fs (RPMOSTreeOSExperimental *interface,
|
||||||
@ -159,7 +142,7 @@ osexperimental_handle_live_fs (RPMOSTreeOSExperimental *interface,
|
|||||||
|
|
||||||
transaction = rpmostreed_transaction_new_livefs (invocation,
|
transaction = rpmostreed_transaction_new_livefs (invocation,
|
||||||
ot_sysroot,
|
ot_sysroot,
|
||||||
livefs_flags_from_options (arg_options),
|
arg_options,
|
||||||
cancellable,
|
cancellable,
|
||||||
&local_error);
|
&local_error);
|
||||||
if (transaction == NULL)
|
if (transaction == NULL)
|
||||||
|
@ -36,12 +36,9 @@
|
|||||||
#include "rpmostree-core.h"
|
#include "rpmostree-core.h"
|
||||||
#include "rpmostreed-utils.h"
|
#include "rpmostreed-utils.h"
|
||||||
|
|
||||||
#define RPMOSTREE_MESSAGE_LIVEFS_BEGIN SD_ID128_MAKE(30,60,1f,0b,bb,fe,4c,bd,a7,87,23,53,a2,ed,75,81)
|
|
||||||
#define RPMOSTREE_MESSAGE_LIVEFS_END SD_ID128_MAKE(d6,8a,b4,d9,d1,32,4a,32,8f,f8,c6,24,1c,6e,b3,c3)
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
RpmostreedTransaction parent;
|
RpmostreedTransaction parent;
|
||||||
RpmOstreeTransactionLiveFsFlags flags;
|
GVariant *options;
|
||||||
} LiveFsTransaction;
|
} LiveFsTransaction;
|
||||||
|
|
||||||
typedef RpmostreedTransactionClass LiveFsTransactionClass;
|
typedef RpmostreedTransactionClass LiveFsTransactionClass;
|
||||||
@ -58,839 +55,11 @@ livefs_transaction_finalize (GObject *object)
|
|||||||
G_GNUC_UNUSED LiveFsTransaction *self;
|
G_GNUC_UNUSED LiveFsTransaction *self;
|
||||||
|
|
||||||
self = (LiveFsTransaction *) object;
|
self = (LiveFsTransaction *) object;
|
||||||
|
g_variant_unref (self->options);
|
||||||
|
|
||||||
G_OBJECT_CLASS (livefs_transaction_parent_class)->finalize (object);
|
G_OBJECT_CLASS (livefs_transaction_parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
COMMIT_DIFF_FLAGS_ETC = (1<< 0), /* Change in /usr/etc */
|
|
||||||
COMMIT_DIFF_FLAGS_BOOT = (1<< 1), /* Change in /boot */
|
|
||||||
COMMIT_DIFF_FLAGS_ROOTFS = (1 << 2), /* Change in / */
|
|
||||||
COMMIT_DIFF_FLAGS_REPLACEMENT = (1 << 3) /* Files in /usr were replaced */
|
|
||||||
} CommitDiffFlags;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
guint refcount;
|
|
||||||
CommitDiffFlags flags;
|
|
||||||
guint n_usretc;
|
|
||||||
guint n_tmpfilesd;
|
|
||||||
|
|
||||||
char *from;
|
|
||||||
char *to;
|
|
||||||
|
|
||||||
/* Files */
|
|
||||||
GPtrArray *added; /* Set<GFile> */
|
|
||||||
GPtrArray *modified; /* Set<OstreeDiffItem> */
|
|
||||||
GPtrArray *removed; /* Set<GFile> */
|
|
||||||
|
|
||||||
/* Package view */
|
|
||||||
GPtrArray *removed_pkgs;
|
|
||||||
GPtrArray *added_pkgs;
|
|
||||||
GPtrArray *modified_pkgs_old;
|
|
||||||
GPtrArray *modified_pkgs_new;
|
|
||||||
} CommitDiff;
|
|
||||||
|
|
||||||
static void
|
|
||||||
commit_diff_unref (CommitDiff *diff)
|
|
||||||
{
|
|
||||||
diff->refcount--;
|
|
||||||
if (diff->refcount > 0)
|
|
||||||
return;
|
|
||||||
g_free (diff->from);
|
|
||||||
g_free (diff->to);
|
|
||||||
g_clear_pointer (&diff->added, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->modified, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->removed, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->removed_pkgs, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->added_pkgs, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->modified_pkgs_old, g_ptr_array_unref);
|
|
||||||
g_clear_pointer (&diff->modified_pkgs_new, g_ptr_array_unref);
|
|
||||||
}
|
|
||||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(CommitDiff, commit_diff_unref);
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
path_is_boot (const char *path)
|
|
||||||
{
|
|
||||||
return g_str_has_prefix (path, "/boot/") ||
|
|
||||||
g_str_has_prefix (path, "/usr/lib/ostree-boot/");
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
path_is_usretc (const char *path)
|
|
||||||
{
|
|
||||||
return g_str_has_prefix (path, "/usr/etc/");
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
path_is_rpmdb (const char *path)
|
|
||||||
{
|
|
||||||
return g_str_has_prefix (path, "/" RPMOSTREE_RPMDB_LOCATION "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
path_is_rootfs (const char *path)
|
|
||||||
{
|
|
||||||
return !g_str_has_prefix (path, "/usr/");
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
path_is_ignored_for_diff (const char *path)
|
|
||||||
{
|
|
||||||
/* /proc SELinux labeling is broken, ignore it
|
|
||||||
* https://github.com/ostreedev/ostree/pull/768
|
|
||||||
*/
|
|
||||||
return strcmp (path, "/proc") == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
FILE_DIFF_RESULT_KEEP,
|
|
||||||
FILE_DIFF_RESULT_OMIT,
|
|
||||||
} FileDiffResult;
|
|
||||||
|
|
||||||
/* Given a file path, update @diff's global flags which track high level
|
|
||||||
* modifications, and return whether or not a change to this file should be
|
|
||||||
* ignored.
|
|
||||||
*/
|
|
||||||
static FileDiffResult
|
|
||||||
diff_one_path (CommitDiff *diff,
|
|
||||||
const char *path)
|
|
||||||
{
|
|
||||||
if (path_is_ignored_for_diff (path) ||
|
|
||||||
path_is_rpmdb (path))
|
|
||||||
return FILE_DIFF_RESULT_OMIT;
|
|
||||||
else if (path_is_usretc (path))
|
|
||||||
{
|
|
||||||
diff->flags |= COMMIT_DIFF_FLAGS_ETC;
|
|
||||||
diff->n_usretc++;
|
|
||||||
}
|
|
||||||
else if (g_str_has_prefix (path, "/usr/lib/tmpfiles.d"))
|
|
||||||
diff->n_tmpfilesd++;
|
|
||||||
else if (path_is_boot (path))
|
|
||||||
diff->flags |= COMMIT_DIFF_FLAGS_BOOT;
|
|
||||||
else if (path_is_rootfs (path))
|
|
||||||
diff->flags |= COMMIT_DIFF_FLAGS_ROOTFS;
|
|
||||||
return FILE_DIFF_RESULT_KEEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
copy_new_config_files (OstreeRepo *repo,
|
|
||||||
OstreeDeployment *merge_deployment,
|
|
||||||
int new_deployment_dfd,
|
|
||||||
OstreeSePolicy *sepolicy,
|
|
||||||
CommitDiff *diff,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
g_auto(RpmOstreeProgress) task = { 0, };
|
|
||||||
rpmostree_output_task_begin (&task, "Copying new config files");
|
|
||||||
|
|
||||||
/* Initialize checkout options; we want to make copies, and don't replace any
|
|
||||||
* existing files.
|
|
||||||
*/
|
|
||||||
OstreeRepoCheckoutAtOptions etc_co_opts = { .force_copy = TRUE,
|
|
||||||
.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES };
|
|
||||||
/* Use SELinux policy if it's initialized */
|
|
||||||
if (ostree_sepolicy_get_name (sepolicy) != NULL)
|
|
||||||
etc_co_opts.sepolicy = sepolicy;
|
|
||||||
|
|
||||||
glnx_autofd int deployment_etc_dfd = -1;
|
|
||||||
if (!glnx_opendirat (new_deployment_dfd, "etc", TRUE, &deployment_etc_dfd, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
guint n_added = 0;
|
|
||||||
/* Avoid checking out added subdirs recursively */
|
|
||||||
g_autoptr(GPtrArray) added_subdirs = g_ptr_array_new_with_free_func (g_free);
|
|
||||||
for (guint i = 0; i < diff->added->len; i++)
|
|
||||||
{
|
|
||||||
GFile *added_f = diff->added->pdata[i];
|
|
||||||
const char *path = gs_file_get_path_cached (added_f);
|
|
||||||
if (!g_str_has_prefix (path, "/usr/etc/"))
|
|
||||||
continue;
|
|
||||||
const char *etc_path = path + strlen ("/usr");
|
|
||||||
|
|
||||||
etc_co_opts.subpath = path;
|
|
||||||
/* Strip off /usr for selinux labeling */
|
|
||||||
etc_co_opts.sepolicy_prefix = etc_path;
|
|
||||||
|
|
||||||
const char *sub_etc_relpath = etc_path + strlen ("/etc/");
|
|
||||||
/* We keep track of added subdirectories and skip children of it, since
|
|
||||||
* both the diff and checkout are recursive, but we only need to checkout
|
|
||||||
* the directory, which will get all children. To do better I'd say we
|
|
||||||
* should add an option to ostree_repo_diff() to avoid recursing into
|
|
||||||
* changed subdirectories. But at the scale we're dealing with here the
|
|
||||||
* constants for this O(N²) algorithm are tiny.
|
|
||||||
*/
|
|
||||||
if (rpmostree_str_has_prefix_in_ptrarray (sub_etc_relpath, added_subdirs))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
g_autoptr(GFileInfo) finfo = g_file_query_info (added_f, "standard::type",
|
|
||||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
|
||||||
cancellable, error);
|
|
||||||
if (!finfo)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* If this is a directory, add it to our "added subdirs" set. See above. Add
|
|
||||||
* a trailing / to ensure we don't match other file prefixes.
|
|
||||||
*/
|
|
||||||
const gboolean is_dir = g_file_info_get_file_type (finfo) == G_FILE_TYPE_DIRECTORY;
|
|
||||||
if (is_dir)
|
|
||||||
g_ptr_array_add (added_subdirs, g_strconcat (sub_etc_relpath, "/", NULL));
|
|
||||||
|
|
||||||
/* And now, to deal with ostree semantics around subpath checkouts,
|
|
||||||
* we want '.' for files, otherwise get the real dir name. See also
|
|
||||||
* the comment in ostree-repo-checkout.c:checkout_tree_at().
|
|
||||||
*/
|
|
||||||
/* First, get the destination parent dfd */
|
|
||||||
glnx_autofd int dest_dfd = -1;
|
|
||||||
g_autofree char *dnbuf = g_strdup (sub_etc_relpath);
|
|
||||||
const char *dn = dirname (dnbuf);
|
|
||||||
if (!glnx_opendirat (deployment_etc_dfd, dn, TRUE, &dest_dfd, error))
|
|
||||||
return FALSE;
|
|
||||||
const char *dest_path;
|
|
||||||
/* Is it a non-directory? OK, we check out into the parent directly */
|
|
||||||
if (!is_dir)
|
|
||||||
{
|
|
||||||
dest_path = ".";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* For directories, we need to match the target's name and hence
|
|
||||||
* create a new directory. */
|
|
||||||
dest_path = glnx_basename (sub_etc_relpath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ostree_repo_checkout_at (repo, &etc_co_opts,
|
|
||||||
dest_dfd, dest_path,
|
|
||||||
ostree_deployment_get_csum (merge_deployment),
|
|
||||||
cancellable, error))
|
|
||||||
return g_prefix_error (error, "Copying %s: ", path), FALSE;
|
|
||||||
n_added++;
|
|
||||||
}
|
|
||||||
rpmostree_output_progress_end_msg (&task, "%u", n_added);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generate a CommitDiff */
|
|
||||||
static gboolean
|
|
||||||
analyze_commit_diff (OstreeRepo *repo,
|
|
||||||
const char *from_rev,
|
|
||||||
const char *to_rev,
|
|
||||||
CommitDiff **out_diff,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
g_autoptr(CommitDiff) diff = g_new0(CommitDiff, 1);
|
|
||||||
diff->refcount = 1;
|
|
||||||
|
|
||||||
diff->from = g_strdup (from_rev);
|
|
||||||
diff->to = g_strdup (to_rev);
|
|
||||||
|
|
||||||
/* Read the "from" and "to" commits */
|
|
||||||
glnx_unref_object GFile *from_tree = NULL;
|
|
||||||
if (!ostree_repo_read_commit (repo, from_rev, &from_tree, NULL,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
glnx_unref_object GFile *to_tree = NULL;
|
|
||||||
if (!ostree_repo_read_commit (repo, to_rev, &to_tree, NULL,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Diff the two commits at the filesystem level */
|
|
||||||
g_autoptr(GPtrArray) modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
|
|
||||||
g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
||||||
g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
||||||
if (!ostree_diff_dirs (0, from_tree, to_tree, modified, removed, added,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* We'll filter these arrays below. */
|
|
||||||
diff->modified = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
||||||
diff->removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
||||||
diff->added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
|
||||||
|
|
||||||
/* Analyze the differences */
|
|
||||||
for (guint i = 0; i < modified->len; i++)
|
|
||||||
{
|
|
||||||
OstreeDiffItem *diffitem = modified->pdata[i];
|
|
||||||
const char *path = gs_file_get_path_cached (diffitem->src);
|
|
||||||
|
|
||||||
if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP)
|
|
||||||
g_ptr_array_add (diff->modified, g_object_ref (diffitem->src));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (guint i = 0; i < removed->len; i++)
|
|
||||||
{
|
|
||||||
GFile *gfpath = removed->pdata[i];
|
|
||||||
const char *path = gs_file_get_path_cached (gfpath);
|
|
||||||
|
|
||||||
if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP)
|
|
||||||
g_ptr_array_add (diff->removed, g_object_ref (gfpath));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (guint i = 0; i < added->len; i++)
|
|
||||||
{
|
|
||||||
GFile *added_f = added->pdata[i];
|
|
||||||
const char *path = gs_file_get_path_cached (added_f);
|
|
||||||
|
|
||||||
if (diff_one_path (diff, path) == FILE_DIFF_RESULT_KEEP)
|
|
||||||
g_ptr_array_add (diff->added, g_object_ref (added_f));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* And gather the RPM level changes */
|
|
||||||
if (!rpm_ostree_db_diff (repo, from_rev, to_rev,
|
|
||||||
&diff->removed_pkgs, &diff->added_pkgs,
|
|
||||||
&diff->modified_pkgs_old, &diff->modified_pkgs_new,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
g_assert (diff->modified_pkgs_old->len == diff->modified_pkgs_new->len);
|
|
||||||
|
|
||||||
*out_diff = g_steal_pointer (&diff);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
print_commit_diff (CommitDiff *diff)
|
|
||||||
{
|
|
||||||
/* Print out the results of the two diffs */
|
|
||||||
rpmostree_output_message ("Diff Analysis: %s => %s", diff->from, diff->to);
|
|
||||||
rpmostree_output_message ("Files: modified: %u removed: %u added: %u",
|
|
||||||
diff->modified->len, diff->removed->len, diff->added->len);
|
|
||||||
rpmostree_output_message ("Packages: modified: %u removed: %u added: %u",
|
|
||||||
diff->modified_pkgs_new->len,
|
|
||||||
diff->removed_pkgs->len,
|
|
||||||
diff->added_pkgs->len);
|
|
||||||
|
|
||||||
if (diff->flags & COMMIT_DIFF_FLAGS_ETC)
|
|
||||||
{
|
|
||||||
rpmostree_output_message ("* Configuration changed in /etc");
|
|
||||||
}
|
|
||||||
if (diff->flags & COMMIT_DIFF_FLAGS_ROOTFS)
|
|
||||||
{
|
|
||||||
rpmostree_output_message ("* Content outside of /usr and /etc is modified");
|
|
||||||
}
|
|
||||||
if (diff->flags & COMMIT_DIFF_FLAGS_BOOT)
|
|
||||||
{
|
|
||||||
rpmostree_output_message ("* Kernel/initramfs changed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We want to ensure the rollback deployment matches our booted checksum. If it
|
|
||||||
* doesn't, we'll push a new one, and GC the previous one(s).
|
|
||||||
*/
|
|
||||||
static OstreeDeployment *
|
|
||||||
get_rollback_deployment (OstreeSysroot *sysroot,
|
|
||||||
OstreeDeployment *booted)
|
|
||||||
{
|
|
||||||
const char *booted_csum = ostree_deployment_get_csum (booted);
|
|
||||||
|
|
||||||
g_autoptr(OstreeDeployment) rollback_deployment = NULL;
|
|
||||||
ostree_sysroot_query_deployments_for (sysroot, ostree_deployment_get_osname (booted),
|
|
||||||
NULL, &rollback_deployment);
|
|
||||||
/* If no rollback found, we're done */
|
|
||||||
if (!rollback_deployment)
|
|
||||||
return NULL;
|
|
||||||
/* We found a rollback, but it needs to match our checksum */
|
|
||||||
const char *csum = ostree_deployment_get_csum (rollback_deployment);
|
|
||||||
if (strcmp (csum, booted_csum) == 0)
|
|
||||||
return g_object_ref (rollback_deployment);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
prepare_rollback_deployment (OstreeSysroot *sysroot,
|
|
||||||
OstreeRepo *repo,
|
|
||||||
OstreeDeployment *booted_deployment,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
glnx_unref_object OstreeDeployment *new_deployment = NULL;
|
|
||||||
OstreeBootconfigParser *original_bootconfig = ostree_deployment_get_bootconfig (booted_deployment);
|
|
||||||
glnx_unref_object OstreeBootconfigParser *new_bootconfig = ostree_bootconfig_parser_clone (original_bootconfig);
|
|
||||||
|
|
||||||
/* Ensure we have a clean slate */
|
|
||||||
if (!ostree_sysroot_prepare_cleanup (sysroot, cancellable, error))
|
|
||||||
return g_prefix_error (error, "Performing initial cleanup: "), FALSE;
|
|
||||||
|
|
||||||
rpmostree_output_message ("Preparing new rollback matching currently booted deployment");
|
|
||||||
|
|
||||||
if (!ostree_sysroot_deploy_tree (sysroot,
|
|
||||||
ostree_deployment_get_osname (booted_deployment),
|
|
||||||
ostree_deployment_get_csum (booted_deployment),
|
|
||||||
ostree_deployment_get_origin (booted_deployment),
|
|
||||||
booted_deployment,
|
|
||||||
NULL,
|
|
||||||
&new_deployment,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Inherit kernel arguments */
|
|
||||||
ostree_deployment_set_bootconfig (new_deployment, new_bootconfig);
|
|
||||||
|
|
||||||
if (!rpmostree_syscore_write_deployment (sysroot, new_deployment, booted_deployment,
|
|
||||||
TRUE, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
checkout_add_usr (OstreeRepo *repo,
|
|
||||||
int deployment_dfd,
|
|
||||||
CommitDiff *diff,
|
|
||||||
const char *target_csum,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
OstreeRepoCheckoutAtOptions usr_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE,
|
|
||||||
.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES,
|
|
||||||
.no_copy_fallback = TRUE,
|
|
||||||
.subpath = "/usr" };
|
|
||||||
|
|
||||||
if (!ostree_repo_checkout_at (repo, &usr_checkout_opts, deployment_dfd, "usr",
|
|
||||||
target_csum, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Even when doing a pure "add" there are some things we need to actually
|
|
||||||
* replace:
|
|
||||||
*
|
|
||||||
* - rpm database
|
|
||||||
* - /usr/lib/{passwd,group}
|
|
||||||
*
|
|
||||||
* This function can swap in a new file/directory.
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
replace_subpath (OstreeRepo *repo,
|
|
||||||
int deployment_dfd,
|
|
||||||
GLnxTmpDir *tmpdir,
|
|
||||||
const char *target_csum,
|
|
||||||
const char *subpath,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
/* The subpath for ostree_repo_checkout_at() must be absolute, but
|
|
||||||
* our real filesystem paths must be relative.
|
|
||||||
*/
|
|
||||||
g_assert_cmpint (*subpath, ==, '/');
|
|
||||||
const char *relsubpath = subpath += strspn (subpath, "/");
|
|
||||||
const char *bname = glnx_basename (relsubpath);
|
|
||||||
|
|
||||||
/* See if it exists, if it does gather stat info; we need to handle
|
|
||||||
* directories differently from non-dirs.
|
|
||||||
*/
|
|
||||||
struct stat stbuf;
|
|
||||||
if (!glnx_fstatat_allow_noent (deployment_dfd, relsubpath, &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
|
||||||
return FALSE;
|
|
||||||
if (errno == ENOENT)
|
|
||||||
return TRUE; /* Do nothing if the path doesn't exist */
|
|
||||||
|
|
||||||
OstreeRepoCheckoutAtOptions replace_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE,
|
|
||||||
.no_copy_fallback = TRUE,
|
|
||||||
.subpath = subpath };
|
|
||||||
/* We need to differentiate nondirs vs dirs; for two reasons. First,
|
|
||||||
* see the /etc path code - ostree_repo_checkout_at() has
|
|
||||||
* some legacy bits around checking out individual files.
|
|
||||||
*
|
|
||||||
* Second, for directories we want RENAME_EXCHANGE if at all possible, but for
|
|
||||||
* non-dirs there's no reason not to just use plain old renameat() which will
|
|
||||||
* also work atomically even on old kernels (e.g. CentOS7). For some critical
|
|
||||||
* files like /usr/lib/passwd we really do want atomicity.
|
|
||||||
*/
|
|
||||||
const gboolean target_is_nondirectory = !S_ISDIR (stbuf.st_mode);
|
|
||||||
if (target_is_nondirectory)
|
|
||||||
{
|
|
||||||
if (!ostree_repo_checkout_at (repo, &replace_checkout_opts, tmpdir->fd, ".",
|
|
||||||
target_csum, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
if (!glnx_renameat (tmpdir->fd, bname, deployment_dfd, relsubpath, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!ostree_repo_checkout_at (repo, &replace_checkout_opts, tmpdir->fd, bname,
|
|
||||||
target_csum, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (glnx_renameat2_exchange (tmpdir->fd, bname, deployment_dfd, relsubpath) < 0)
|
|
||||||
return glnx_throw_errno_prefix (error, "rename(..., RENAME_EXCHANGE) for %s", subpath);
|
|
||||||
/* And nuke the old one */
|
|
||||||
if (!glnx_shutil_rm_rf_at (tmpdir->fd, bname, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The sledgehammer 🔨 approach. Because /usr is a mount point, we can't replace
|
|
||||||
* all of it. We could do a diff, but doing that precisely and quickly depends
|
|
||||||
* on https://github.com/ostreedev/ostree/issues/1224
|
|
||||||
*
|
|
||||||
* In this approach, we iterate over and exchange just the subdirectories of
|
|
||||||
* /usr (not recursively, as exchanging the toplevels accomplishes that). Some
|
|
||||||
* issues here are processes that hold directory fds open will have those
|
|
||||||
* invalidated, even if they shouldn't otherwise be affected. Another issue is
|
|
||||||
* that if the kernel is too old to have `RENAME_EXCHANGE`, we'll have e.g.
|
|
||||||
* `/usr/bin` be temporarily broken.
|
|
||||||
*
|
|
||||||
* Yet another issue is that doing things all at once makes it much more likely
|
|
||||||
* that we'll e.g. replace code for a program before updating a shared library
|
|
||||||
* it depends on. There's really no fixing that problem in general, but we could
|
|
||||||
* minimize the race window in the same way package systems tend to do by doing
|
|
||||||
* the filesystem tree replacements in package reverse dependency order.
|
|
||||||
*
|
|
||||||
* On the other hand, this handles tricky cases like replacing a directory with
|
|
||||||
* a regfile or symlink.
|
|
||||||
*
|
|
||||||
* Probably what we really want is to have a lightweight replacement path that
|
|
||||||
* handles simple updates (e.g. 1-5 packages which just change file content).
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
replace_usr (OstreeRepo *repo,
|
|
||||||
int deployment_dfd,
|
|
||||||
GLnxTmpDir *tmpdir,
|
|
||||||
CommitDiff *diff,
|
|
||||||
const char *target_csum,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
/* Grab a reference to the current /usr */
|
|
||||||
glnx_autofd int deployment_usr_dfd = -1;
|
|
||||||
if (!glnx_opendirat (deployment_dfd, "usr", TRUE, &deployment_usr_dfd, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Check out our new /usr */
|
|
||||||
OstreeRepoCheckoutAtOptions usr_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE,
|
|
||||||
.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_NONE,
|
|
||||||
.no_copy_fallback = TRUE,
|
|
||||||
.subpath = "/usr" };
|
|
||||||
|
|
||||||
if (!ostree_repo_checkout_at (repo, &usr_checkout_opts, tmpdir->fd, "usr",
|
|
||||||
target_csum, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Iterate over new /usr, see whether the context exists. If not, just
|
|
||||||
* rename(). If so, `RENAME_EXCHANGE`; the old content will be rm-rf'd since
|
|
||||||
* it will be moved to the tmpdir.
|
|
||||||
*/
|
|
||||||
g_autoptr(GHashTable) seen_new_children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
||||||
g_auto(GLnxDirFdIterator) dfd_iter = { FALSE, };
|
|
||||||
if (!glnx_dirfd_iterator_init_at (tmpdir->fd, "usr", TRUE, &dfd_iter, error))
|
|
||||||
return FALSE;
|
|
||||||
while (TRUE)
|
|
||||||
{
|
|
||||||
struct dirent *dent = NULL;
|
|
||||||
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
if (dent == NULL)
|
|
||||||
break;
|
|
||||||
const char *name = dent->d_name;
|
|
||||||
/* Keep track of what entries are in the new /usr */
|
|
||||||
g_hash_table_add (seen_new_children, g_strdup (name));
|
|
||||||
if (!glnx_fstatat_allow_noent (deployment_usr_dfd, name, NULL, AT_SYMLINK_NOFOLLOW, error))
|
|
||||||
return FALSE;
|
|
||||||
if (errno == ENOENT)
|
|
||||||
{
|
|
||||||
if (!glnx_renameat (dfd_iter.fd, name, deployment_usr_dfd, name, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (glnx_renameat2_exchange (dfd_iter.fd, name, deployment_usr_dfd, name) < 0)
|
|
||||||
return glnx_throw_errno_prefix (error, "rename(..., RENAME_EXCHANGE) for %s", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iterate over the old /usr, and delete anything that isn't in the new dir */
|
|
||||||
glnx_dirfd_iterator_clear (&dfd_iter);
|
|
||||||
if (!glnx_dirfd_iterator_init_at (deployment_usr_dfd, ".", TRUE, &dfd_iter, error))
|
|
||||||
return FALSE;
|
|
||||||
while (TRUE)
|
|
||||||
{
|
|
||||||
struct dirent *dent = NULL;
|
|
||||||
if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
if (dent == NULL)
|
|
||||||
break;
|
|
||||||
const char *name = dent->d_name;
|
|
||||||
/* See whether this was in the new /usr */
|
|
||||||
if (g_hash_table_contains (seen_new_children, name))
|
|
||||||
continue;
|
|
||||||
/* If not, delete it */
|
|
||||||
if (!glnx_shutil_rm_rf_at (dfd_iter.fd, name, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update the origin for @booted with new livefs state */
|
|
||||||
static gboolean
|
|
||||||
write_livefs_state (OstreeSysroot *sysroot,
|
|
||||||
OstreeDeployment *booted,
|
|
||||||
const char *live_inprogress,
|
|
||||||
const char *live,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
g_autoptr(RpmOstreeOrigin) new_origin = rpmostree_origin_parse_deployment (booted, error);
|
|
||||||
if (!new_origin)
|
|
||||||
return FALSE;
|
|
||||||
rpmostree_origin_set_live_state (new_origin, live_inprogress, live);
|
|
||||||
g_autoptr(GKeyFile) kf = rpmostree_origin_dup_keyfile (new_origin);
|
|
||||||
if (!ostree_sysroot_write_origin_file (sysroot, booted, kf, NULL, error))
|
|
||||||
return FALSE;
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
livefs_transaction_execute_inner (LiveFsTransaction *self,
|
|
||||||
OstreeSysroot *sysroot,
|
|
||||||
GCancellable *cancellable,
|
|
||||||
GError **error)
|
|
||||||
{
|
|
||||||
/* Initial setup - load sysroot, repo, and booted deployment */
|
|
||||||
glnx_unref_object OstreeRepo *repo = NULL;
|
|
||||||
if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (sysroot);
|
|
||||||
if (!booted_deployment)
|
|
||||||
return glnx_throw (error, "Not currently booted into an OSTree system");
|
|
||||||
|
|
||||||
/* Overlayfs doesn't support mutation of the lowerdir. And broadly speaking,
|
|
||||||
* our medium term goal here is to obviate most of the unlock usage.
|
|
||||||
*/
|
|
||||||
OstreeDeploymentUnlockedState unlockstate = ostree_deployment_get_unlocked (booted_deployment);
|
|
||||||
if (unlockstate != OSTREE_DEPLOYMENT_UNLOCKED_NONE)
|
|
||||||
return glnx_throw (error, "livefs is incompatible with unlocked state");
|
|
||||||
|
|
||||||
/* Find the source for /etc - either booted or pending, but down below we
|
|
||||||
require pending */
|
|
||||||
OstreeDeployment *origin_merge_deployment =
|
|
||||||
rpmostree_syscore_get_origin_merge_deployment (sysroot,
|
|
||||||
ostree_deployment_get_osname (booted_deployment));
|
|
||||||
g_assert (origin_merge_deployment);
|
|
||||||
const char *booted_csum = ostree_deployment_get_csum (booted_deployment);
|
|
||||||
const char *target_csum = ostree_deployment_get_csum (origin_merge_deployment);
|
|
||||||
|
|
||||||
/* Require a pending deployment to use as a source - perhaps in the future we
|
|
||||||
* handle direct live overlays.
|
|
||||||
*/
|
|
||||||
if (origin_merge_deployment == booted_deployment)
|
|
||||||
return glnx_throw (error, "No pending deployment");
|
|
||||||
if (!ostree_deployment_is_staged (origin_merge_deployment))
|
|
||||||
return glnx_throw (error, "livefs requires staged deployments");
|
|
||||||
|
|
||||||
/* Open a fd for the booted deployment */
|
|
||||||
g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, booted_deployment);
|
|
||||||
glnx_autofd int deployment_dfd = -1;
|
|
||||||
if (!glnx_opendirat (ostree_sysroot_get_fd (sysroot), deployment_path, TRUE,
|
|
||||||
&deployment_dfd, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Find out whether we already have a live overlay */
|
|
||||||
g_autofree char *live_inprogress = NULL;
|
|
||||||
g_autofree char *live_replaced = NULL;
|
|
||||||
if (!rpmostree_syscore_deployment_get_live (booted_deployment, &live_inprogress,
|
|
||||||
&live_replaced, error))
|
|
||||||
return FALSE;
|
|
||||||
const char *resuming_overlay = NULL;
|
|
||||||
if (live_inprogress != NULL)
|
|
||||||
{
|
|
||||||
if (strcmp (live_inprogress, target_csum) == 0)
|
|
||||||
resuming_overlay = target_csum;
|
|
||||||
}
|
|
||||||
const char *replacing_overlay = NULL;
|
|
||||||
if (live_replaced != NULL)
|
|
||||||
{
|
|
||||||
if (strcmp (live_replaced, target_csum) == 0)
|
|
||||||
return glnx_throw (error, "Current overlay is already %s", target_csum);
|
|
||||||
replacing_overlay = live_replaced;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resuming_overlay)
|
|
||||||
rpmostree_output_message ("Note: Resuming interrupted overlay of %s", target_csum);
|
|
||||||
if (replacing_overlay)
|
|
||||||
rpmostree_output_message ("Note: Previous overlay: %s", replacing_overlay);
|
|
||||||
|
|
||||||
/* Look at the difference between the two commits - we could also walk the
|
|
||||||
* filesystem, but doing it at the ostree level is potentially faster, since
|
|
||||||
* we know when two directories are the same.
|
|
||||||
*/
|
|
||||||
g_autoptr(CommitDiff) diff = NULL;
|
|
||||||
if (!analyze_commit_diff (repo, booted_csum, target_csum,
|
|
||||||
&diff, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
print_commit_diff (diff);
|
|
||||||
|
|
||||||
const gboolean replacing = (self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE) > 0;
|
|
||||||
const gboolean requires_etc_merge = (diff->flags & COMMIT_DIFF_FLAGS_ETC) > 0;
|
|
||||||
const gboolean adds_packages = diff->added_pkgs->len > 0;
|
|
||||||
const gboolean modifies_packages = diff->removed_pkgs->len > 0 || diff->modified_pkgs_new->len > 0;
|
|
||||||
if ((diff->flags & COMMIT_DIFF_FLAGS_ROOTFS) > 0)
|
|
||||||
return glnx_throw (error, "livefs update would modify non-/usr content");
|
|
||||||
/* Is this a dry run? */
|
|
||||||
/* Error out in various cases if we're not doing a replacement */
|
|
||||||
if (!replacing)
|
|
||||||
{
|
|
||||||
if (!adds_packages)
|
|
||||||
return glnx_throw (error, "No packages added; cannot apply");
|
|
||||||
if (modifies_packages)
|
|
||||||
return glnx_throw (error, "livefs update modifies/replaces packages; cannot apply");
|
|
||||||
else if ((diff->flags & COMMIT_DIFF_FLAGS_REPLACEMENT) > 0)
|
|
||||||
return glnx_throw (error, "livefs update would replace files in /usr; cannot apply");
|
|
||||||
}
|
|
||||||
if ((self->flags & RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN) > 0)
|
|
||||||
{
|
|
||||||
rpmostree_output_message ("livefs OK (dry run)");
|
|
||||||
/* Note early return */
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_autoptr(GString) journal_msg = g_string_new ("");
|
|
||||||
g_string_append_printf (journal_msg, "Starting livefs for commit %s", target_csum);
|
|
||||||
if (resuming_overlay)
|
|
||||||
g_string_append (journal_msg, " (resuming)");
|
|
||||||
|
|
||||||
if (replacing)
|
|
||||||
g_string_append_printf (journal_msg, " replacement; %u/%u/%u pkgs (added, removed, modified); %u/%u/%u files",
|
|
||||||
diff->added_pkgs->len, diff->removed_pkgs->len, diff->modified_pkgs_old->len,
|
|
||||||
diff->added->len, diff->removed->len, diff->modified->len);
|
|
||||||
else
|
|
||||||
g_string_append_printf (journal_msg, " addition; %u pkgs, %u files",
|
|
||||||
diff->added_pkgs->len, diff->added->len);
|
|
||||||
|
|
||||||
if (replacing_overlay)
|
|
||||||
g_string_append_printf (journal_msg, "; replacing %s", replacing_overlay);
|
|
||||||
sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_LIVEFS_BEGIN),
|
|
||||||
"MESSAGE=%s", journal_msg->str,
|
|
||||||
"RESUMING=%s", resuming_overlay ?: "",
|
|
||||||
"REPLACING=%s", replacing_overlay ?: "",
|
|
||||||
"BOOTED_COMMIT=%s", booted_csum,
|
|
||||||
"TARGET_COMMIT=%s", target_csum,
|
|
||||||
NULL);
|
|
||||||
g_string_truncate (journal_msg, 0);
|
|
||||||
|
|
||||||
/* Ensure that we have a rollback deployment that matches our booted checksum,
|
|
||||||
* so that if something goes wrong, the user can get to it. If we have an
|
|
||||||
* older rollback, that gets GC'd.
|
|
||||||
*/
|
|
||||||
OstreeDeployment *rollback_deployment = get_rollback_deployment (sysroot, booted_deployment);
|
|
||||||
if (!rollback_deployment)
|
|
||||||
{
|
|
||||||
if (!prepare_rollback_deployment (sysroot, repo, booted_deployment, cancellable, error))
|
|
||||||
return g_prefix_error (error, "Preparing rollback: "), FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reload this, the sysroot may have changed it */
|
|
||||||
booted_deployment = ostree_sysroot_get_booted_deployment (sysroot);
|
|
||||||
|
|
||||||
/* Load SELinux policy for making changes to /etc */
|
|
||||||
g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new_at (deployment_dfd, cancellable, error);
|
|
||||||
if (!sepolicy)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Note we inherit the previous value of `live_replaced` (which may be NULL) */
|
|
||||||
if (!write_livefs_state (sysroot, booted_deployment, target_csum, live_replaced, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
g_auto(GLnxTmpDir) replace_tmpdir = { 0, };
|
|
||||||
if (!glnx_mkdtempat (ostree_repo_get_dfd (repo), "tmp/rpmostree-livefs.XXXXXX", 0700,
|
|
||||||
&replace_tmpdir, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
if (!replacing)
|
|
||||||
{
|
|
||||||
g_auto(RpmOstreeProgress) task = { 0, };
|
|
||||||
rpmostree_output_task_begin (&task, "Overlaying /usr");
|
|
||||||
if (!checkout_add_usr (repo, deployment_dfd, diff, target_csum, cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* And files that we always need to replace; the rpmdb, /usr/lib/passwd. We
|
|
||||||
* make a tmpdir just for this since it's a more convenient place to put
|
|
||||||
* temporary files/dirs without generating tempnames.
|
|
||||||
*/
|
|
||||||
const char *replace_paths[] = { "/" RPMOSTREE_RPMDB_LOCATION, "/usr/lib/passwd", "/usr/lib/group" };
|
|
||||||
for (guint i = 0; i < G_N_ELEMENTS(replace_paths); i++)
|
|
||||||
{
|
|
||||||
const char *replace_path = replace_paths[i];
|
|
||||||
if (!replace_subpath (repo, deployment_dfd, &replace_tmpdir,
|
|
||||||
target_csum, replace_path,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Hold my beer 🍺, we're going loop over /usr and RENAME_EXCHANGE things
|
|
||||||
* that were modified.
|
|
||||||
*/
|
|
||||||
g_auto(RpmOstreeProgress) task = { 0, };
|
|
||||||
rpmostree_output_task_begin (&task, "Replacing /usr");
|
|
||||||
if (!replace_usr (repo, deployment_dfd, &replace_tmpdir,
|
|
||||||
diff, target_csum,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff->n_tmpfilesd > 0)
|
|
||||||
{
|
|
||||||
const char *tmpfiles_prefixes[] = { "/run/", "/var/"};
|
|
||||||
const char *tmpfiles_argv[] = { "systemd-tmpfiles", "--create",
|
|
||||||
"--prefix", NULL, NULL };
|
|
||||||
|
|
||||||
g_auto(RpmOstreeProgress) task = { 0, };
|
|
||||||
rpmostree_output_task_begin (&task, "Running systemd-tmpfiles for /run,/var");
|
|
||||||
|
|
||||||
for (guint i = 0; i < G_N_ELEMENTS (tmpfiles_prefixes); i++)
|
|
||||||
{
|
|
||||||
const char *prefix = tmpfiles_prefixes[i];
|
|
||||||
GLNX_AUTO_PREFIX_ERROR ("Executing systemd-tmpfiles", error);
|
|
||||||
tmpfiles_argv[G_N_ELEMENTS(tmpfiles_argv)-2] = prefix;
|
|
||||||
g_autoptr(GSubprocess) subproc = g_subprocess_newv ((const char *const*)tmpfiles_argv,
|
|
||||||
G_SUBPROCESS_FLAGS_STDERR_PIPE |
|
|
||||||
G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
|
|
||||||
error);
|
|
||||||
if (!subproc)
|
|
||||||
return FALSE;
|
|
||||||
g_autofree char *stderr_buf = NULL;
|
|
||||||
if (!g_subprocess_communicate_utf8 (subproc, NULL, cancellable,
|
|
||||||
NULL, &stderr_buf, error))
|
|
||||||
return FALSE;
|
|
||||||
int estatus = g_subprocess_get_exit_status (subproc);
|
|
||||||
if (!g_spawn_check_exit_status (estatus, error))
|
|
||||||
{
|
|
||||||
/* Only dump stderr if it actually failed; otherwise we know
|
|
||||||
* there are (harmless) warnings, no need to bother everyone.
|
|
||||||
*/
|
|
||||||
sd_journal_print (LOG_ERR, "systemd-tmpfiles failed: %s", stderr_buf);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requires_etc_merge)
|
|
||||||
{
|
|
||||||
if (!copy_new_config_files (repo, origin_merge_deployment,
|
|
||||||
deployment_dfd, sepolicy, diff,
|
|
||||||
cancellable, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write out the origin as having completed this */
|
|
||||||
if (!write_livefs_state (sysroot, booted_deployment, NULL, target_csum, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_LIVEFS_END),
|
|
||||||
"MESSAGE=Completed livefs for commit %s", target_csum,
|
|
||||||
"BOOTED_COMMIT=%s", booted_csum,
|
|
||||||
"TARGET_COMMIT=%s", target_csum,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
livefs_transaction_execute (RpmostreedTransaction *transaction,
|
livefs_transaction_execute (RpmostreedTransaction *transaction,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
@ -899,8 +68,13 @@ livefs_transaction_execute (RpmostreedTransaction *transaction,
|
|||||||
LiveFsTransaction *self = (LiveFsTransaction *) transaction;
|
LiveFsTransaction *self = (LiveFsTransaction *) transaction;
|
||||||
OstreeSysroot *sysroot = rpmostreed_transaction_get_sysroot (transaction);
|
OstreeSysroot *sysroot = rpmostreed_transaction_get_sysroot (transaction);
|
||||||
|
|
||||||
|
g_auto(GVariantDict) options_dict;
|
||||||
|
g_variant_dict_init (&options_dict, self->options);
|
||||||
|
const char *target = NULL;
|
||||||
|
(void) g_variant_dict_lookup (&options_dict, "target", "&s", &target);
|
||||||
|
|
||||||
/* Run the transaction */
|
/* Run the transaction */
|
||||||
gboolean ret = livefs_transaction_execute_inner (self, sysroot, cancellable, error);
|
gboolean ret = ror_transaction_livefs (sysroot, target, error);
|
||||||
|
|
||||||
/* We use this to notify ourselves of changes, which is a bit silly, but it
|
/* We use this to notify ourselves of changes, which is a bit silly, but it
|
||||||
* keeps things consistent if `ostree admin` is invoked directly. Always
|
* keeps things consistent if `ostree admin` is invoked directly. Always
|
||||||
@ -911,7 +85,6 @@ livefs_transaction_execute (RpmostreedTransaction *transaction,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
livefs_transaction_class_init (LiveFsTransactionClass *class)
|
livefs_transaction_class_init (LiveFsTransactionClass *class)
|
||||||
{
|
{
|
||||||
@ -931,7 +104,7 @@ livefs_transaction_init (LiveFsTransaction *self)
|
|||||||
RpmostreedTransaction *
|
RpmostreedTransaction *
|
||||||
rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation,
|
rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation,
|
||||||
OstreeSysroot *sysroot,
|
OstreeSysroot *sysroot,
|
||||||
RpmOstreeTransactionLiveFsFlags flags,
|
GVariant *options,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
@ -948,7 +121,7 @@ rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation,
|
|||||||
|
|
||||||
if (self != NULL)
|
if (self != NULL)
|
||||||
{
|
{
|
||||||
self->flags = flags;
|
self->options = g_variant_ref (options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (RpmostreedTransaction *) self;
|
return (RpmostreedTransaction *) self;
|
||||||
|
@ -1232,7 +1232,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction,
|
|||||||
rpmostree_sysroot_upgrader_get_merge_deployment (upgrader);
|
rpmostree_sysroot_upgrader_get_merge_deployment (upgrader);
|
||||||
|
|
||||||
gboolean is_live;
|
gboolean is_live;
|
||||||
if (!rpmostree_syscore_deployment_is_live (deployment, &is_live, error))
|
if (!rpmostree_syscore_livefs_query (sysroot, deployment, &is_live, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (is_live)
|
if (is_live)
|
||||||
|
@ -114,15 +114,10 @@ rpmostreed_transaction_new_cleanup (GDBusMethodInvocation *invocation,
|
|||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
RPMOSTREE_TRANSACTION_LIVEFS_FLAG_DRY_RUN = (1 << 0),
|
|
||||||
RPMOSTREE_TRANSACTION_LIVEFS_FLAG_REPLACE = (1 << 1),
|
|
||||||
} RpmOstreeTransactionLiveFsFlags;
|
|
||||||
|
|
||||||
RpmostreedTransaction *
|
RpmostreedTransaction *
|
||||||
rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation,
|
rpmostreed_transaction_new_livefs (GDBusMethodInvocation *invocation,
|
||||||
OstreeSysroot *sysroot,
|
OstreeSysroot *sysroot,
|
||||||
RpmOstreeTransactionLiveFsFlags flags,
|
GVariant *options,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
|
@ -195,9 +195,6 @@ rpmostree_origin_remove_transient_state (RpmOstreeOrigin *origin)
|
|||||||
|
|
||||||
/* this is already covered by the above, but the below also updates the cached value */
|
/* this is already covered by the above, but the below also updates the cached value */
|
||||||
rpmostree_origin_set_override_commit (origin, NULL, NULL);
|
rpmostree_origin_set_override_commit (origin, NULL, NULL);
|
||||||
|
|
||||||
/* then rpm-ostree specific things */
|
|
||||||
rpmostree_origin_set_live_state (origin, NULL, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
@ -363,17 +360,6 @@ rpmostree_origin_may_require_local_assembly (RpmOstreeOrigin *origin)
|
|||||||
(g_hash_table_size (origin->cached_overrides_remove) > 0);
|
(g_hash_table_size (origin->cached_overrides_remove) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
rpmostree_origin_get_live_state (RpmOstreeOrigin *origin,
|
|
||||||
char **out_inprogress,
|
|
||||||
char **out_live)
|
|
||||||
{
|
|
||||||
if (out_inprogress)
|
|
||||||
*out_inprogress = g_key_file_get_string (origin->kf, "rpmostree-ex-live", "inprogress", NULL);
|
|
||||||
if (out_live)
|
|
||||||
*out_live = g_key_file_get_string (origin->kf, "rpmostree-ex-live", "commit", NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
GKeyFile *
|
GKeyFile *
|
||||||
rpmostree_origin_dup_keyfile (RpmOstreeOrigin *origin)
|
rpmostree_origin_dup_keyfile (RpmOstreeOrigin *origin)
|
||||||
{
|
{
|
||||||
@ -639,33 +625,6 @@ rpmostree_origin_set_rebase (RpmOstreeOrigin *origin,
|
|||||||
return rpmostree_origin_set_rebase_custom (origin, new_refspec, NULL, NULL, error);
|
return rpmostree_origin_set_rebase_custom (origin, new_refspec, NULL, NULL, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Like g_key_file_set_string(), but remove the key if @value is NULL */
|
|
||||||
static void
|
|
||||||
set_or_unset_str (GKeyFile *kf,
|
|
||||||
const char *group,
|
|
||||||
const char *key,
|
|
||||||
const char *value)
|
|
||||||
{
|
|
||||||
if (!value)
|
|
||||||
(void) g_key_file_remove_key (kf, group, key, NULL);
|
|
||||||
else
|
|
||||||
(void) g_key_file_set_string (kf, group, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
rpmostree_origin_set_live_state (RpmOstreeOrigin *origin,
|
|
||||||
const char *inprogress,
|
|
||||||
const char *live)
|
|
||||||
{
|
|
||||||
if (!inprogress && !live)
|
|
||||||
(void) g_key_file_remove_group (origin->kf, "rpmostree-ex-live", NULL);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
set_or_unset_str (origin->kf, "rpmostree-ex-live", "inprogress", inprogress);
|
|
||||||
set_or_unset_str (origin->kf, "rpmostree-ex-live", "commit", live);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
update_keyfile_pkgs_from_cache (RpmOstreeOrigin *origin,
|
update_keyfile_pkgs_from_cache (RpmOstreeOrigin *origin,
|
||||||
const char *group,
|
const char *group,
|
||||||
|
@ -110,13 +110,6 @@ rpmostree_origin_get_unconfigured_state (RpmOstreeOrigin *origin);
|
|||||||
gboolean
|
gboolean
|
||||||
rpmostree_origin_may_require_local_assembly (RpmOstreeOrigin *origin);
|
rpmostree_origin_may_require_local_assembly (RpmOstreeOrigin *origin);
|
||||||
|
|
||||||
// WARNING: This prototype is also redefined in Rust, if changing this
|
|
||||||
// please also update `includes.rs`.
|
|
||||||
void
|
|
||||||
rpmostree_origin_get_live_state (RpmOstreeOrigin *origin,
|
|
||||||
char **out_inprogress,
|
|
||||||
char **out_live);
|
|
||||||
|
|
||||||
char *
|
char *
|
||||||
rpmostree_origin_get_string (RpmOstreeOrigin *origin,
|
rpmostree_origin_get_string (RpmOstreeOrigin *origin,
|
||||||
const char *section,
|
const char *section,
|
||||||
@ -207,8 +200,3 @@ gboolean
|
|||||||
rpmostree_origin_remove_all_overrides (RpmOstreeOrigin *origin,
|
rpmostree_origin_remove_all_overrides (RpmOstreeOrigin *origin,
|
||||||
gboolean *out_changed,
|
gboolean *out_changed,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
void
|
|
||||||
rpmostree_origin_set_live_state (RpmOstreeOrigin *origin,
|
|
||||||
const char *inprogress,
|
|
||||||
const char *live);
|
|
||||||
|
@ -36,4 +36,6 @@ b(GPtrArray)
|
|||||||
b(GChecksum)
|
b(GChecksum)
|
||||||
b(OstreeRepo)
|
b(OstreeRepo)
|
||||||
b(RpmOstreeOrigin)
|
b(RpmOstreeOrigin)
|
||||||
|
b(OstreeDeployment)
|
||||||
|
b(OstreeSysroot)
|
||||||
#undef b
|
#undef b
|
||||||
|
@ -108,7 +108,7 @@ vm_build_rpm scriptpkg2 \
|
|||||||
vm_build_rpm scriptpkg3 \
|
vm_build_rpm scriptpkg3 \
|
||||||
post 'echo %%{_prefix} > /usr/lib/noprefixtest.txt'
|
post 'echo %%{_prefix} > /usr/lib/noprefixtest.txt'
|
||||||
vm_rpmostree pkg-add scriptpkg{2,3}
|
vm_rpmostree pkg-add scriptpkg{2,3}
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_rpmostree ex livefs
|
||||||
vm_cmd cat /usr/lib/noprefixtest.txt > noprefixtest.txt
|
vm_cmd cat /usr/lib/noprefixtest.txt > noprefixtest.txt
|
||||||
assert_file_has_content noprefixtest.txt '%{_prefix}'
|
assert_file_has_content noprefixtest.txt '%{_prefix}'
|
||||||
vm_cmd cat /usr/lib/prefixtest.txt > prefixtest.txt
|
vm_cmd cat /usr/lib/prefixtest.txt > prefixtest.txt
|
||||||
@ -123,7 +123,7 @@ vm_build_rpm rpmostree-lua-override-test-expand \
|
|||||||
post_args "-e -p <lua>" \
|
post_args "-e -p <lua>" \
|
||||||
post 'posix.stat("/")'
|
post 'posix.stat("/")'
|
||||||
vm_rpmostree install rpmostree-lua-override-test{,-expand}
|
vm_rpmostree install rpmostree-lua-override-test{,-expand}
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_rpmostree ex livefs
|
||||||
vm_cmd cat /usr/share/rpmostree-lua-override-test > lua-override.txt
|
vm_cmd cat /usr/share/rpmostree-lua-override-test > lua-override.txt
|
||||||
assert_file_has_content lua-override.txt _install_langs
|
assert_file_has_content lua-override.txt _install_langs
|
||||||
vm_cmd rpm --eval '%{_install_langs}' > install-langs.txt
|
vm_cmd rpm --eval '%{_install_langs}' > install-langs.txt
|
||||||
@ -147,7 +147,7 @@ vm_build_rpm scriptpkg5 \
|
|||||||
transfiletriggerun "/usr/share/licenses/systemd /usr/share/licenses/rpm" 'sort >/usr/share/transfiletriggerun-license-systemd-rpm.txt' \
|
transfiletriggerun "/usr/share/licenses/systemd /usr/share/licenses/rpm" 'sort >/usr/share/transfiletriggerun-license-systemd-rpm.txt' \
|
||||||
transfiletriggerin2 "/usr/share/licenses/sed /usr/share/licenses/tzdata" 'sort >/usr/share/transfiletriggerin-license-sed-tzdata.txt'
|
transfiletriggerin2 "/usr/share/licenses/sed /usr/share/licenses/tzdata" 'sort >/usr/share/transfiletriggerin-license-sed-tzdata.txt'
|
||||||
vm_rpmostree pkg-add scriptpkg{4,5}
|
vm_rpmostree pkg-add scriptpkg{4,5}
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_rpmostree ex livefs
|
||||||
for combo in ${license_combos}; do
|
for combo in ${license_combos}; do
|
||||||
vm_cmd cat /usr/share/transfiletriggerin-license-${combo}.txt > transfiletriggerin-license-${combo}.txt
|
vm_cmd cat /usr/share/transfiletriggerin-license-${combo}.txt > transfiletriggerin-license-${combo}.txt
|
||||||
rm -f transfiletriggerin-fs-${combo}.txt.tmp
|
rm -f transfiletriggerin-fs-${combo}.txt.tmp
|
||||||
@ -229,7 +229,7 @@ echo "ok impervious to rm -rf post"
|
|||||||
# capabilities
|
# capabilities
|
||||||
vm_build_rpm test-cap-drop post "capsh --print > /usr/share/rpmostree-capsh.txt"
|
vm_build_rpm test-cap-drop post "capsh --print > /usr/share/rpmostree-capsh.txt"
|
||||||
vm_rpmostree install test-cap-drop
|
vm_rpmostree install test-cap-drop
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_rpmostree ex livefs
|
||||||
vm_cmd cat /usr/share/rpmostree-capsh.txt > caps.txt
|
vm_cmd cat /usr/share/rpmostree-capsh.txt > caps.txt
|
||||||
assert_not_file_has_content caps.test '^Current: =.*cap_sys_admin'
|
assert_not_file_has_content caps.test '^Current: =.*cap_sys_admin'
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ vm_rpmostree cleanup -pr
|
|||||||
vm_assert_layered_pkg foo absent
|
vm_assert_layered_pkg foo absent
|
||||||
|
|
||||||
vm_build_rpm foo
|
vm_build_rpm foo
|
||||||
|
vm_build_rpm bar
|
||||||
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/foo-1.0-1.x86_64.rpm
|
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/foo-1.0-1.x86_64.rpm
|
||||||
vm_assert_status_jq '.deployments|length == 2'
|
vm_assert_status_jq '.deployments|length == 2'
|
||||||
echo "ok install foo locally"
|
echo "ok install foo locally"
|
||||||
@ -38,22 +39,36 @@ echo "ok install foo locally"
|
|||||||
if vm_cmd rpm -q foo; then
|
if vm_cmd rpm -q foo; then
|
||||||
assert_not_reached "have foo?"
|
assert_not_reached "have foo?"
|
||||||
fi
|
fi
|
||||||
assert_livefs_ok() {
|
|
||||||
vm_rpmostree ex livefs --i-like-danger -n > livefs-analysis.txt
|
|
||||||
assert_file_has_content livefs-analysis.txt 'livefs OK (dry run)'
|
|
||||||
}
|
|
||||||
assert_livefs_ok
|
|
||||||
|
|
||||||
vm_assert_status_jq '.deployments|length == 2' \
|
vm_assert_status_jq '.deployments|length == 2' \
|
||||||
'.deployments[0]["live-replaced"]|not' \
|
'.deployments[0]["live-replaced"]|not' \
|
||||||
'.deployments[1]["live-replaced"]|not'
|
'.deployments[1]["live-replaced"]|not'
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_rpmostree ex livefs
|
||||||
vm_cmd rpm -q foo > rpmq.txt
|
vm_cmd rpm -q foo > rpmq.txt
|
||||||
assert_file_has_content rpmq.txt foo-1.0-1
|
assert_file_has_content rpmq.txt foo-1.0-1
|
||||||
vm_assert_status_jq '.deployments|length == 3' '.deployments[0]["live-replaced"]|not' \
|
vm_cmd ls -al /usr/bin/foo
|
||||||
|
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
||||||
'.deployments[1]["live-replaced"]'
|
'.deployments[1]["live-replaced"]'
|
||||||
|
if vm_cmd test -w /usr; then
|
||||||
|
fatal "Found writable /usr"
|
||||||
|
fi
|
||||||
|
echo "ok livefs basic"
|
||||||
|
|
||||||
echo "ok livefs stage1"
|
vm_rpmostree cleanup -p
|
||||||
|
vm_rpmostree install bar
|
||||||
|
vm_assert_status_jq '.deployments|length == 2' \
|
||||||
|
'.deployments[0]["live-replaced"]|not' \
|
||||||
|
'.deployments[1]["live-replaced"]'
|
||||||
|
vm_rpmostree ex livefs
|
||||||
|
vm_cmd rpm -qa > rpmq.txt
|
||||||
|
assert_file_has_content rpmq.txt bar-1.0-1
|
||||||
|
assert_not_file_has_content rpmq.txt foo-1.0-1
|
||||||
|
vm_cmd ls -al /usr/bin/bar
|
||||||
|
if vm_cmd test -f /usr/bin/foo; then
|
||||||
|
fatal "Still have /usr/bin/foo"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ok livefs again"
|
||||||
|
|
||||||
vm_build_rpm test-livefs-with-etc \
|
vm_build_rpm test-livefs-with-etc \
|
||||||
build 'echo "A config file for %{name}" > %{name}.conf' \
|
build 'echo "A config file for %{name}" > %{name}.conf' \
|
||||||
@ -86,10 +101,9 @@ vm_cmd rm -rf /etc/test-livefs-with-etc \
|
|||||||
/etc/opt/test-livefs-with-etc-opt.conf
|
/etc/opt/test-livefs-with-etc-opt.conf
|
||||||
|
|
||||||
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/test-livefs-{with-etc,service}-1.0-1.x86_64.rpm
|
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/test-livefs-{with-etc,service}-1.0-1.x86_64.rpm
|
||||||
assert_livefs_ok
|
vm_rpmostree ex livefs
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
vm_cmd rpm -q bar test-livefs-{with-etc,service} > rpmq.txt
|
||||||
vm_cmd rpm -q foo test-livefs-{with-etc,service} > rpmq.txt
|
assert_file_has_content rpmq.txt bar-1.0-1 test-livefs-{with-etc,service}-1.0-1
|
||||||
assert_file_has_content rpmq.txt foo-1.0-1 test-livefs-{with-etc,service}-1.0-1
|
|
||||||
vm_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
|
vm_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
|
||||||
assert_file_has_content test-livefs-with-etc.conf "A config file for test-livefs-with-etc"
|
assert_file_has_content test-livefs-with-etc.conf "A config file for test-livefs-with-etc"
|
||||||
for v in subconfig-one subconfig-two subdir/subconfig-three; do
|
for v in subconfig-one subconfig-two subdir/subconfig-three; do
|
||||||
@ -107,91 +121,3 @@ assert_file_has_content test-livefs-group.txt livefs-group
|
|||||||
vm_cmd test -d /var/lib/test-livefs-service
|
vm_cmd test -d /var/lib/test-livefs-service
|
||||||
|
|
||||||
echo "ok livefs stage2"
|
echo "ok livefs stage2"
|
||||||
|
|
||||||
# Now, perform a further change in the pending
|
|
||||||
vm_rpmostree uninstall test-livefs-with-etc-1.0-1.x86_64
|
|
||||||
vm_assert_status_jq '.deployments|length == 3'
|
|
||||||
echo "ok livefs preserved rollback"
|
|
||||||
|
|
||||||
# Reset to rollback, undeploy pending
|
|
||||||
reset() {
|
|
||||||
vm_rpmostree reset
|
|
||||||
vm_reboot
|
|
||||||
vm_rpmostree cleanup -r
|
|
||||||
vm_assert_status_jq '.deployments|length == 1' '.deployments[0]["live-replaced"]|not'
|
|
||||||
}
|
|
||||||
reset
|
|
||||||
|
|
||||||
# If the admin created a config file before, we need to keep it
|
|
||||||
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm
|
|
||||||
vm_cmd cat /etc/test-livefs-with-etc.conf || true
|
|
||||||
vm_cmd echo custom \> /etc/test-livefs-with-etc.conf
|
|
||||||
vm_cmd cat /etc/test-livefs-with-etc.conf
|
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
|
||||||
vm_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
|
|
||||||
assert_file_has_content test-livefs-with-etc.conf "custom"
|
|
||||||
echo "ok livefs preserved modified config"
|
|
||||||
|
|
||||||
vm_rpmostree cleanup -p
|
|
||||||
# make sure there's no layering going on somehow
|
|
||||||
vm_assert_status_jq '.deployments[0]["base-checksum"]|not'
|
|
||||||
vm_rpmostree deploy $(vm_get_booted_deployment_info checksum)
|
|
||||||
echo "ok livefs redeploy booted commit"
|
|
||||||
|
|
||||||
reset
|
|
||||||
vm_rpmostree install /var/tmp/vmcheck/yumrepo/packages/x86_64/foo-1.0-1.x86_64.rpm
|
|
||||||
vm_rpmostree ex livefs --i-like-danger
|
|
||||||
# Picked a file that should be around, but harmless to change for testing. The
|
|
||||||
# first is available on Fedora, the second on CentOS (and newer too).
|
|
||||||
dummy_file_to_modify=usr/share/licenses/ostree/COPYING
|
|
||||||
if ! vm_cmd test -f /${dummy_file_to_modify}; then
|
|
||||||
dummy_file_to_modify=usr/share/ostree/trusted.gpg.d/README-gpg
|
|
||||||
fi
|
|
||||||
vm_cmd test -f /${dummy_file_to_modify}
|
|
||||||
generate_upgrade() {
|
|
||||||
# Create a modified vmcheck commit
|
|
||||||
vm_shell_inline_sysroot_rw <<EOF
|
|
||||||
cd /ostree/repo/tmp
|
|
||||||
rm vmcheck -rf
|
|
||||||
ostree checkout vmcheck vmcheck --fsync=0
|
|
||||||
(date; echo "JUST KIDDING DO WHATEVER") >vmcheck/${dummy_file_to_modify}.new && mv vmcheck/${dummy_file_to_modify}{.new,}
|
|
||||||
$@
|
|
||||||
ostree commit -b vmcheck --tree=dir=vmcheck --link-checkout-speedup
|
|
||||||
rm vmcheck -rf
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
generate_upgrade
|
|
||||||
# And remove the pending deployment so that our origin is now the booted
|
|
||||||
vm_rpmostree cleanup -p
|
|
||||||
vm_rpmostree upgrade
|
|
||||||
vm_assert_status_jq '.deployments|length == 3' '.deployments[0]["live-replaced"]|not' \
|
|
||||||
'.deployments[1]["live-replaced"]'
|
|
||||||
|
|
||||||
echo "ok livefs not carried over across upgrades"
|
|
||||||
|
|
||||||
reset
|
|
||||||
generate_upgrade "mkdir -p vmcheck/usr/newsubdir && date > vmcheck/usr/newsubdir/date.txt"
|
|
||||||
vm_rpmostree upgrade
|
|
||||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
|
||||||
'.deployments[1]["live-replaced"]|not'
|
|
||||||
if vm_rpmostree ex livefs --i-like-danger -n &> livefs-analysis.txt; then
|
|
||||||
assert_not_reached "livefs succeeded?"
|
|
||||||
fi
|
|
||||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
|
||||||
'.deployments[1]["live-replaced"]|not'
|
|
||||||
assert_file_has_content livefs-analysis.txt 'No packages added'
|
|
||||||
echo "ok no modifications"
|
|
||||||
|
|
||||||
# And now replacement
|
|
||||||
vm_rpmostree ex livefs -n --i-like-danger --dangerous-do-not-use-replace &> livefs-analysis.txt
|
|
||||||
assert_file_has_content livefs-analysis.txt 'livefs OK (dry run)'
|
|
||||||
vm_assert_status_jq '.deployments|length == 2' '.deployments[0]["live-replaced"]|not' \
|
|
||||||
'.deployments[1]["live-replaced"]|not'
|
|
||||||
vm_rpmostree ex livefs --i-like-danger --dangerous-do-not-use-replace
|
|
||||||
vm_cmd cat /${dummy_file_to_modify} > dummyfile.txt
|
|
||||||
assert_file_has_content dummyfile.txt "JUST KIDDING DO WHATEVER"
|
|
||||||
vm_cmd test -f /usr/newsubdir/date.txt
|
|
||||||
vm_assert_status_jq '.deployments|length == 3' '.deployments[0]["live-replaced"]|not' \
|
|
||||||
'.deployments[1]["live-replaced"]' '.deployments[1]["booted"]'
|
|
||||||
echo "ok modifications"
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user