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;
|
use std::pin::Pin;
|
||||||
|
|
||||||
/* See rpmostree-core.h */
|
/* 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_RPMDB_LOCATION: &str = "usr/share/rpm";
|
||||||
|
const RPMOSTREE_SYSIMAGE_RPMDB: &str = "usr/lib/sysimage/rpm";
|
||||||
const TRADITIONAL_RPMDB_LOCATION: &str = "var/lib/rpm";
|
const TRADITIONAL_RPMDB_LOCATION: &str = "var/lib/rpm";
|
||||||
|
|
||||||
#[context("Moving {}", name)]
|
#[context("Moving {}", name)]
|
||||||
@ -853,6 +855,119 @@ fn workaround_selinux_cross_labeling_recurse(
|
|||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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,
|
rootfs_dfd: i32,
|
||||||
cancellable: Pin<&mut GCancellable>,
|
cancellable: Pin<&mut GCancellable>,
|
||||||
) -> Result<()>;
|
) -> 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
|
// A grab-bag of metadata from the deployment's ostree commit
|
||||||
|
@ -83,64 +83,6 @@ rename_if_exists (int src_dfd,
|
|||||||
return TRUE;
|
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:
|
/* Handle the kernel/initramfs, which can be in at least 2 different places:
|
||||||
* - /boot (CentOS, Fedora treecompose before we suppressed kernel.spec's %posttrans)
|
* - /boot (CentOS, Fedora treecompose before we suppressed kernel.spec's %posttrans)
|
||||||
* - /usr/lib/modules (Fedora treecompose without 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 */
|
/* we're composing a new tree; copy the rpmdb to the base location */
|
||||||
if (!glnx_fstatat_allow_noent (rootfs_dfd, RPMOSTREE_RPMDB_LOCATION, NULL,
|
rpmostreecxx::prepare_rpmdb_base_location(rootfs_dfd, *cancellable);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user