composepost: move rpmdb linking logic to Rust
This ports the rpmdb hardlinking logic which serves to maintain a coherent single source of truth across tools.
This commit is contained in:
parent
30d5c79272
commit
3b76a7eeef
@ -25,7 +25,9 @@ use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
|
||||
/* See rpmostree-core.h */
|
||||
const RPMOSTREE_BASE_RPMDB: &str = "usr/lib/sysimage/rpm-ostree-base-db";
|
||||
const RPMOSTREE_RPMDB_LOCATION: &str = "usr/share/rpm";
|
||||
const RPMOSTREE_SYSIMAGE_RPMDB: &str = "usr/lib/sysimage/rpm";
|
||||
const TRADITIONAL_RPMDB_LOCATION: &str = "var/lib/rpm";
|
||||
|
||||
#[context("Moving {}", name)]
|
||||
@ -853,6 +855,119 @@ fn workaround_selinux_cross_labeling_recurse(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prepare_rpmdb_base_location(
|
||||
rootfs_dfd: i32,
|
||||
mut cancellable: Pin<&mut crate::FFIGCancellable>,
|
||||
) -> CxxResult<()> {
|
||||
let rootfs = crate::ffiutil::ffi_view_openat_dir(rootfs_dfd);
|
||||
let cancellable = &cancellable.gobj_wrap();
|
||||
|
||||
hardlink_rpmdb_base_location(&rootfs, Some(cancellable))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[context("Hardlinking rpmdb to base location")]
|
||||
fn hardlink_rpmdb_base_location(
|
||||
rootfs: &openat::Dir,
|
||||
cancellable: Option<&gio::Cancellable>,
|
||||
) -> Result<bool> {
|
||||
if !rootfs.exists(RPMOSTREE_RPMDB_LOCATION)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Hardlink our own `/usr/lib/sysimage/rpm-ostree-base-db/` hierarchy
|
||||
// to the well-known `/usr/share/rpm/`.
|
||||
rootfs.ensure_dir_all(RPMOSTREE_BASE_RPMDB, 0o755)?;
|
||||
rootfs.set_mode(RPMOSTREE_BASE_RPMDB, 0o755)?;
|
||||
hardlink_hierarchy(
|
||||
rootfs,
|
||||
RPMOSTREE_RPMDB_LOCATION,
|
||||
RPMOSTREE_BASE_RPMDB,
|
||||
cancellable,
|
||||
)?;
|
||||
|
||||
// And write a symlink from the proposed standard /usr/lib/sysimage/rpm
|
||||
// to our /usr/share/rpm - eventually we will invert this.
|
||||
rootfs.symlink(RPMOSTREE_SYSIMAGE_RPMDB, "../../share/rpm")?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Recursively hard-link `source` hierarchy to `target` directory.
|
||||
///
|
||||
/// Both directories must exist beforehand.
|
||||
#[context("Hardlinking /{} to /{}", source, target)]
|
||||
fn hardlink_hierarchy(
|
||||
rootfs: &openat::Dir,
|
||||
source: &str,
|
||||
target: &str,
|
||||
cancellable: Option<&gio::Cancellable>,
|
||||
) -> Result<()> {
|
||||
let mut prefix = "".to_string();
|
||||
hardlink_recurse(rootfs, source, target, &mut prefix, &cancellable)
|
||||
.with_context(|| format!("Analyzing /{}/{} content", source, prefix))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively hard-link `source_prefix` to `dest_prefix.`
|
||||
///
|
||||
/// `relative_path` is updated at each recursive step, so that in case of errors
|
||||
/// it can be used to pinpoint the faulty path.
|
||||
fn hardlink_recurse(
|
||||
rootfs: &openat::Dir,
|
||||
source_prefix: &str,
|
||||
dest_prefix: &str,
|
||||
relative_path: &mut String,
|
||||
cancellable: &Option<&gio::Cancellable>,
|
||||
) -> Result<()> {
|
||||
use openat::SimpleType;
|
||||
|
||||
let current_dir = relative_path.clone();
|
||||
let current_source_dir = format!("{}/{}", source_prefix, relative_path);
|
||||
for subpath in rootfs.list_dir(¤t_source_dir)? {
|
||||
if cancellable.map(|c| c.is_cancelled()).unwrap_or_default() {
|
||||
bail!("Cancelled");
|
||||
};
|
||||
|
||||
let subpath = subpath?;
|
||||
let full_path = {
|
||||
let fname = subpath.file_name();
|
||||
let path_name = fname
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("invalid non-UTF-8 path: {:?}", fname))?;
|
||||
if !current_dir.is_empty() {
|
||||
format!("{}/{}", current_dir, path_name)
|
||||
} else {
|
||||
path_name.to_string()
|
||||
}
|
||||
};
|
||||
let source_path = format!("{}/{}", source_prefix, full_path);
|
||||
let dest_path = format!("{}/{}", dest_prefix, full_path);
|
||||
let path_type = subpath.simple_type().unwrap_or(SimpleType::Other);
|
||||
|
||||
if path_type == SimpleType::Dir {
|
||||
// New subdirectory discovered, create it at the target.
|
||||
let perms = rootfs.metadata(&source_path)?.stat().st_mode & !libc::S_IFMT;
|
||||
rootfs.ensure_dir(&dest_path, perms)?;
|
||||
rootfs.set_mode(&dest_path, perms)?;
|
||||
|
||||
// Recurse into the subdirectory.
|
||||
*relative_path = full_path.clone();
|
||||
hardlink_recurse(
|
||||
rootfs,
|
||||
source_prefix,
|
||||
dest_prefix,
|
||||
relative_path,
|
||||
cancellable,
|
||||
)?;
|
||||
} else {
|
||||
openat::hardlink(rootfs, source_path, rootfs, dest_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -1080,4 +1195,42 @@ OSTREE_VERSION='33.4'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hardlink_rpmdb_base_location() {
|
||||
let temp_rootfs = tempfile::tempdir().unwrap();
|
||||
let rootfs = openat::Dir::open(temp_rootfs.path()).unwrap();
|
||||
|
||||
{
|
||||
let done = hardlink_rpmdb_base_location(&rootfs, gio::NONE_CANCELLABLE).unwrap();
|
||||
assert_eq!(done, false);
|
||||
}
|
||||
|
||||
let dirs = &[RPMOSTREE_RPMDB_LOCATION, "usr/share/rpm/foo/bar"];
|
||||
for entry in dirs {
|
||||
rootfs.ensure_dir_all(*entry, 0o755).unwrap();
|
||||
}
|
||||
let files = &[
|
||||
"usr/share/rpm/rpmdb.sqlite",
|
||||
"usr/share/rpm/foo/bar/placeholder",
|
||||
];
|
||||
for entry in files {
|
||||
rootfs.write_file(*entry, 0o755).unwrap();
|
||||
}
|
||||
|
||||
let done = hardlink_rpmdb_base_location(&rootfs, gio::NONE_CANCELLABLE).unwrap();
|
||||
assert_eq!(done, true);
|
||||
|
||||
assert_eq!(rootfs.exists(RPMOSTREE_BASE_RPMDB).unwrap(), true);
|
||||
let placeholder = rootfs
|
||||
.metadata(format!("{}/foo/bar/placeholder", RPMOSTREE_BASE_RPMDB))
|
||||
.unwrap();
|
||||
assert_eq!(placeholder.is_file(), true);
|
||||
let rpmdb = rootfs
|
||||
.metadata(format!("{}/rpmdb.sqlite", RPMOSTREE_BASE_RPMDB))
|
||||
.unwrap();
|
||||
assert_eq!(rpmdb.is_file(), true);
|
||||
let sysimage_link = rootfs.read_link(RPMOSTREE_SYSIMAGE_RPMDB).unwrap();
|
||||
assert_eq!(&sysimage_link, Path::new("../../share/rpm"));
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,10 @@ pub mod ffi {
|
||||
rootfs_dfd: i32,
|
||||
cancellable: Pin<&mut GCancellable>,
|
||||
) -> Result<()>;
|
||||
fn prepare_rpmdb_base_location(
|
||||
rootfs_dfd: i32,
|
||||
cancellable: Pin<&mut GCancellable>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
// A grab-bag of metadata from the deployment's ostree commit
|
||||
|
@ -83,64 +83,6 @@ rename_if_exists (int src_dfd,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Given a directory referenced by @src_dfd+@src_path, as well as a target
|
||||
* directory @dest_dfd+@dest_path (which must already exist), hardlink all
|
||||
* content recursively.
|
||||
*/
|
||||
static gboolean
|
||||
hardlink_recurse (int src_dfd,
|
||||
const char *src_path,
|
||||
int dest_dfd,
|
||||
const char *dest_path,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||
glnx_autofd int dest_target_dfd = -1;
|
||||
|
||||
if (!glnx_dirfd_iterator_init_at (src_dfd, src_path, TRUE, &dfd_iter, error))
|
||||
return FALSE;
|
||||
|
||||
if (!glnx_opendirat (dest_dfd, dest_path, TRUE, &dest_target_dfd, error))
|
||||
return FALSE;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
struct dirent *dent = NULL;
|
||||
struct stat stbuf;
|
||||
|
||||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
||||
return FALSE;
|
||||
if (!dent)
|
||||
break;
|
||||
|
||||
if (!glnx_fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
|
||||
if (dent->d_type == DT_DIR)
|
||||
{
|
||||
mode_t perms = stbuf.st_mode & ~S_IFMT;
|
||||
|
||||
if (!glnx_ensure_dir (dest_target_dfd, dent->d_name, perms, error))
|
||||
return FALSE;
|
||||
if (fchmodat (dest_target_dfd, dent->d_name, perms, 0) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fchmodat");
|
||||
if (!hardlink_recurse (dfd_iter.fd, dent->d_name,
|
||||
dest_target_dfd, dent->d_name,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (linkat (dfd_iter.fd, dent->d_name,
|
||||
dest_target_dfd, dent->d_name, 0) < 0)
|
||||
return glnx_throw_errno_prefix (error, "linkat");
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Handle the kernel/initramfs, which can be in at least 2 different places:
|
||||
* - /boot (CentOS, Fedora treecompose before we suppressed kernel.spec's %posttrans)
|
||||
* - /usr/lib/modules (Fedora treecompose without kernel.spec's %posttrans)
|
||||
@ -574,24 +516,7 @@ rpmostree_postprocess_final (int rootfs_dfd,
|
||||
}
|
||||
|
||||
/* we're composing a new tree; copy the rpmdb to the base location */
|
||||
if (!glnx_fstatat_allow_noent (rootfs_dfd, RPMOSTREE_RPMDB_LOCATION, NULL,
|
||||
AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
if (errno == 0)
|
||||
{
|
||||
if (!glnx_shutil_mkdir_p_at (rootfs_dfd, RPMOSTREE_BASE_RPMDB, 0755,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
if (!hardlink_recurse (rootfs_dfd, RPMOSTREE_RPMDB_LOCATION,
|
||||
rootfs_dfd, RPMOSTREE_BASE_RPMDB,
|
||||
cancellable, error))
|
||||
return glnx_prefix_error (error, "Hardlinking %s", RPMOSTREE_BASE_RPMDB);
|
||||
/* And write a symlink from the proposed standard /usr/lib/sysimage/rpm
|
||||
* to our /usr/share/rpm - eventually we will invert this.
|
||||
*/
|
||||
if (symlinkat ("../../share/rpm", rootfs_dfd, RPMOSTREE_SYSIMAGE_RPMDB) < 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinking %s", RPMOSTREE_SYSIMAGE_RPMDB);
|
||||
}
|
||||
rpmostreecxx::prepare_rpmdb_base_location(rootfs_dfd, *cancellable);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user