composepost: move rootfs symlinks creation to Rust

This ports the post-processing logic which creates symlinks for
several known state directories under /usr.
This commit is contained in:
Luca BRUNO 2021-04-20 14:40:51 +00:00 committed by Colin Walters
parent 02b81d7845
commit ad365df4b0
5 changed files with 94 additions and 89 deletions

View File

@ -697,6 +697,70 @@ fn convert_path_to_tmpfiles_d_recurse(
Ok(())
}
/// Walk over the root filesystem and perform some core conversions
/// from RPM conventions to OSTree conventions.
///
/// For example:
/// - Symlink /usr/local -> /var/usrlocal
/// - Symlink /var/lib/alternatives -> /usr/lib/alternatives
/// - Symlink /var/lib/vagrant -> /usr/lib/vagrant
#[context("Preparing symlinks in rootfs")]
pub fn rootfs_prepare_links(rootfs_dfd: i32) -> CxxResult<()> {
let rootfs = crate::ffiutil::ffi_view_openat_dir(rootfs_dfd);
rootfs
.remove_all("usr/local")
.context("Removing /usr/local")?;
let state_paths = &["usr/lib/alternatives", "usr/lib/vagrant"];
for entry in state_paths {
rootfs
.ensure_dir_all(*entry, 0o0755)
.with_context(|| format!("Creating '/{}'", entry))?;
}
let symlinks = &[
("../var/usrlocal", "usr/local"),
("../../usr/lib/alternatives", "var/lib/alternatives"),
("../../usr/lib/vagrant", "var/lib/vagrant"),
];
for (target, linkpath) in symlinks {
ensure_symlink(&rootfs, target, linkpath)?;
}
Ok(())
}
/// Create a symlink at `linkpath` if it does not exist, pointing to `target`.
///
/// This is idempotent and does not alter any content already existing at `linkpath`.
/// It returns `true` if the symlink has been created, `false` otherwise.
#[context("Symlinking '/{}' to empty directory '/{}'", linkpath, target)]
fn ensure_symlink(rootfs: &openat::Dir, target: &str, linkpath: &str) -> Result<bool> {
use openat::SimpleType;
if let Some(meta) = rootfs.metadata_optional(linkpath)? {
match meta.simple_type() {
SimpleType::Symlink => {
// We assume linkpath already points to the correct target,
// thus this short-circuits in an idempotent way.
return Ok(false);
}
SimpleType::Dir => rootfs.remove_dir(linkpath)?,
_ => bail!("Content already exists at link path"),
};
} else {
// For maximum compatibility, create parent directories too. This
// is necessary when we're doing layering on top of a base commit,
// and the /var will be empty. We should probably consider running
// systemd-tmpfiles to setup the temporary /var.
rootfs.ensure_dir_all(linkpath, 0o755)?;
rootfs.remove_dir(linkpath)?;
}
rootfs.symlink(linkpath, target)?;
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
@ -854,4 +918,30 @@ OSTREE_VERSION='33.4'
}
assert_eq!(entries.len(), expected.len(), "{:#?}", entries);
}
#[test]
fn test_prepare_symlinks() {
let temp_rootfs = tempfile::tempdir().unwrap();
let rootfs = openat::Dir::open(temp_rootfs.path()).unwrap();
rootfs.ensure_dir_all("usr/local", 0o755).unwrap();
rootfs_prepare_links(rootfs.as_raw_fd()).unwrap();
{
let usr_dir = rootfs.sub_dir("usr").unwrap();
let local_target = usr_dir.read_link("local").unwrap();
assert_eq!(local_target.to_str(), Some("../var/usrlocal"));
}
{
let varlib_dir = rootfs.sub_dir("var/lib").unwrap();
let varcases = &[
("alternatives", "../../usr/lib/alternatives"),
("vagrant", "../../usr/lib/vagrant"),
];
for (linkpath, content) in varcases {
let target = varlib_dir.read_link(*linkpath);
assert!(target.is_ok(), "/var/lib/{}", linkpath);
assert_eq!(target.unwrap().to_str(), Some(*content));
}
}
}
}

View File

@ -149,6 +149,7 @@ pub mod ffi {
rootfs_dfd: i32,
cancellable: Pin<&mut GCancellable>,
) -> Result<()>;
fn rootfs_prepare_links(rootfs_dfd: i32) -> Result<()>;
}
// A grab-bag of metadata from the deployment's ostree commit

View File

@ -4321,8 +4321,7 @@ rpmostree_context_assemble (RpmOstreeContext *self,
*/
if (!glnx_shutil_mkdir_p_at (tmprootfs_dfd, "var/tmp", 0755, cancellable, error))
return FALSE;
if (!rpmostree_rootfs_prepare_links (tmprootfs_dfd, cancellable, error))
return FALSE;
rpmostreecxx::rootfs_prepare_links(tmprootfs_dfd);
gboolean skip_sanity_check = FALSE;
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);

View File

@ -611,8 +611,8 @@ rpmostree_postprocess_final (int rootfs_dfd,
rpmostreecxx::convert_var_to_tmpfiles_d (rootfs_dfd, *cancellable);
if (!rpmostree_rootfs_prepare_links (rootfs_dfd, cancellable, error))
return FALSE;
rpmostreecxx::rootfs_prepare_links(rootfs_dfd);
if (!rpmostree_rootfs_postprocess_common (rootfs_dfd, cancellable, error))
return FALSE;
@ -672,80 +672,6 @@ rpmostree_postprocess_final (int rootfs_dfd,
return TRUE;
}
gboolean
rpmostree_rootfs_symlink_emptydir_at (int rootfs_fd,
const char *dest,
const char *src,
GError **error)
{
const char *parent = dirname (strdupa (src));
struct stat stbuf;
gboolean make_symlink = TRUE;
/* For maximum compatibility, create parent directories too. This
* is necessary when we're doing layering on top of a base commit,
* and the /var will be empty. We should probably consider running
* systemd-tmpfiles to setup the temporary /var.
*/
if (parent && strcmp (parent, ".") != 0)
{
if (!glnx_shutil_mkdir_p_at (rootfs_fd, parent, 0755, NULL, error))
return FALSE;
}
if (!glnx_fstatat_allow_noent (rootfs_fd, src, &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
if (errno == 0)
{
if (S_ISLNK (stbuf.st_mode))
make_symlink = FALSE;
else if (S_ISDIR (stbuf.st_mode))
{
if (!glnx_unlinkat (rootfs_fd, src, AT_REMOVEDIR, error))
return FALSE;
}
}
if (make_symlink)
{
if (symlinkat (dest, rootfs_fd, src) < 0)
return glnx_throw_errno_prefix (error, "Symlinking %s", src);
}
return TRUE;
}
/**
* rpmostree_rootfs_prepare_links:
*
* Walk over the root filesystem and perform some core conversions
* from RPM conventions to OSTree conventions. For example:
*
* - Symlink /usr/local -> /var/usrlocal
* - Symlink /var/lib/alternatives -> /usr/lib/alternatives
* - Symlink /var/lib/vagrant -> /usr/lib/vagrant
*/
gboolean
rpmostree_rootfs_prepare_links (int rootfs_fd,
GCancellable *cancellable,
GError **error)
{
if (!glnx_shutil_rm_rf_at (rootfs_fd, "usr/local", cancellable, error))
return FALSE;
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../var/usrlocal", "usr/local", error))
return FALSE;
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/lib/alternatives", 0755, cancellable, error))
return FALSE;
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../../usr/lib/alternatives", "var/lib/alternatives", error))
return FALSE;
if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/lib/vagrant", 0755, cancellable, error))
return FALSE;
if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../../usr/lib/vagrant", "var/lib/vagrant", error))
return FALSE;
return TRUE;
}
static gboolean
cleanup_leftover_files (int rootfs_fd,
const char *subpath,

View File

@ -26,17 +26,6 @@
G_BEGIN_DECLS
gboolean
rpmostree_rootfs_symlink_emptydir_at (int rootfs_fd,
const char *dest,
const char *src,
GError **error);
gboolean
rpmostree_rootfs_prepare_links (int rootfs_fd,
GCancellable *cancellable,
GError **error);
gboolean
rpmostree_cleanup_leftover_rpmdb_files (int rootfs_fd,
GCancellable *cancellable,