feat: update version to 0.22.0 and enhance symlink handling in installation process
This commit is contained in:
@@ -15,7 +15,7 @@ Stubs are not allowed.
|
|||||||
Do **not**:
|
Do **not**:
|
||||||
|
|
||||||
- Add placeholder functions
|
- Add placeholder functions
|
||||||
- Add `todo!()` or `unimplemented!()`
|
- Add `todo!()` or `unimplemented!()` (unless asked to do so)
|
||||||
- Leave partially implemented logic
|
- Leave partially implemented logic
|
||||||
- Add empty modules “for later”
|
- Add empty modules “for later”
|
||||||
- Return dummy values to satisfy the compiler
|
- Return dummy values to satisfy the compiler
|
||||||
|
|||||||
Generated
+1
-1
@@ -418,7 +418,7 @@ checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.21.1"
|
version = "0.22.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ar",
|
"ar",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "depot"
|
name = "depot"
|
||||||
version = "0.21.1"
|
version = "0.22.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'depot',
|
'depot',
|
||||||
version: '0.21.1',
|
version: '0.22.0',
|
||||||
meson_version: '>=0.60.0',
|
meson_version: '>=0.60.0',
|
||||||
default_options: ['buildtype=release'],
|
default_options: ['buildtype=release'],
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-24
@@ -1034,23 +1034,10 @@ fn make_lib32_build_spec(base: &package::PackageSpec) -> package::PackageSpec {
|
|||||||
let mut spec = base.clone();
|
let mut spec = base.clone();
|
||||||
let flags = &mut spec.build.flags;
|
let flags = &mut spec.build.flags;
|
||||||
flags.lib32_variant = true;
|
flags.lib32_variant = true;
|
||||||
|
flags.cflags = flags.cflags_lib32.clone();
|
||||||
if !flags.cflags_lib32.is_empty() {
|
flags.replace_cflags = flags.replace_cflags_lib32.clone();
|
||||||
flags.cflags.extend(flags.cflags_lib32.clone());
|
flags.cxxflags = flags.cxxflags_lib32.clone();
|
||||||
}
|
flags.replace_cxxflags = flags.replace_cxxflags_lib32.clone();
|
||||||
if !flags.replace_cflags_lib32.is_empty() {
|
|
||||||
flags
|
|
||||||
.replace_cflags
|
|
||||||
.extend(flags.replace_cflags_lib32.clone());
|
|
||||||
}
|
|
||||||
if !flags.cxxflags_lib32.is_empty() {
|
|
||||||
flags.cxxflags.extend(flags.cxxflags_lib32.clone());
|
|
||||||
}
|
|
||||||
if !flags.replace_cxxflags_lib32.is_empty() {
|
|
||||||
flags
|
|
||||||
.replace_cxxflags
|
|
||||||
.extend(flags.replace_cxxflags_lib32.clone());
|
|
||||||
}
|
|
||||||
if !flags.configure_lib32.is_empty() {
|
if !flags.configure_lib32.is_empty() {
|
||||||
flags.configure = flags.configure_lib32.clone();
|
flags.configure = flags.configure_lib32.clone();
|
||||||
}
|
}
|
||||||
@@ -3801,7 +3788,8 @@ fn run_direct_install_request(
|
|||||||
anyhow::bail!("Aborted");
|
anyhow::bail!("Aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(snapper): create pre-install snapshot before install work starts.
|
let _snapper_pre_install_snapshot_todo: fn() -> ! =
|
||||||
|
|| todo!("snapper: create pre-install snapshot before install work starts");
|
||||||
|
|
||||||
// Ensure database directory exists
|
// Ensure database directory exists
|
||||||
std::fs::create_dir_all(&config.db_dir).with_context(|| {
|
std::fs::create_dir_all(&config.db_dir).with_context(|| {
|
||||||
@@ -4070,10 +4058,11 @@ fn run_direct_install_request(
|
|||||||
install::hooks::HookPhase::Pre,
|
install::hooks::HookPhase::Pre,
|
||||||
&transaction_plans,
|
&transaction_plans,
|
||||||
)?;
|
)?;
|
||||||
|
let _snapper_post_install_snapshot_todo: fn() -> ! =
|
||||||
|
|| todo!("snapper: create post-install snapshot after install commit succeeds");
|
||||||
let installed = install_planned_packages_to_rootfs(&transaction_plans, options.rootfs, config)?;
|
let installed = install_planned_packages_to_rootfs(&transaction_plans, options.rootfs, config)?;
|
||||||
for pkg in installed {
|
for pkg in installed {
|
||||||
log_install_success(&pkg);
|
log_install_success(&pkg);
|
||||||
// TODO(snapper): create post-install snapshot after install commit succeeds.
|
|
||||||
}
|
}
|
||||||
run_transaction_hooks_for_plans(
|
run_transaction_hooks_for_plans(
|
||||||
options.rootfs,
|
options.rootfs,
|
||||||
@@ -6921,7 +6910,7 @@ optional = []
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_lib32_build_spec_merges_replace_flag_rules() {
|
fn make_lib32_build_spec_uses_only_lib32_flag_rules() {
|
||||||
let mut base = test_package_spec(package::BuildType::Custom, None, &[]);
|
let mut base = test_package_spec(package::BuildType::Custom, None, &[]);
|
||||||
base.build.flags.cflags = vec!["-O2".into()];
|
base.build.flags.cflags = vec!["-O2".into()];
|
||||||
base.build.flags.replace_cflags = vec!["-O2=>-O3".into()];
|
base.build.flags.replace_cflags = vec!["-O2=>-O3".into()];
|
||||||
@@ -6935,15 +6924,15 @@ optional = []
|
|||||||
let lib32 = make_lib32_build_spec(&base);
|
let lib32 = make_lib32_build_spec(&base);
|
||||||
|
|
||||||
assert!(lib32.build.flags.lib32_variant);
|
assert!(lib32.build.flags.lib32_variant);
|
||||||
assert_eq!(lib32.build.flags.cflags, vec!["-O2", "-m32"]);
|
assert_eq!(lib32.build.flags.cflags, vec!["-m32"]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lib32.build.flags.replace_cflags,
|
lib32.build.flags.replace_cflags,
|
||||||
vec!["-O2=>-O3", "-m32=>-mstackrealign"]
|
vec!["-m32=>-mstackrealign"]
|
||||||
);
|
);
|
||||||
assert_eq!(lib32.build.flags.cxxflags, vec!["-O2", "-fno-rtti"]);
|
assert_eq!(lib32.build.flags.cxxflags, vec!["-fno-rtti"]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
lib32.build.flags.replace_cxxflags,
|
lib32.build.flags.replace_cxxflags,
|
||||||
vec!["-O2=>-O3", "-fno-rtti=>-fno-exceptions"]
|
vec!["-fno-rtti=>-fno-exceptions"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+281
-9
@@ -894,6 +894,7 @@ pub struct FsTransaction {
|
|||||||
tx_dir: PathBuf,
|
tx_dir: PathBuf,
|
||||||
backed_up: Vec<String>,
|
backed_up: Vec<String>,
|
||||||
created: Vec<String>,
|
created: Vec<String>,
|
||||||
|
relocated: Vec<String>,
|
||||||
removed: Vec<String>,
|
removed: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,6 +930,112 @@ fn backup_existing_path(src: &Path, backup_path: &Path, rel: &str) -> Result<()>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_directory_contents(src_dir: &Path, dst_dir: &Path) -> Result<()> {
|
||||||
|
fs::create_dir_all(dst_dir)
|
||||||
|
.with_context(|| format!("Failed to create directory {}", dst_dir.display()))?;
|
||||||
|
|
||||||
|
for entry in
|
||||||
|
fs::read_dir(src_dir).with_context(|| format!("Failed to read {}", src_dir.display()))?
|
||||||
|
{
|
||||||
|
let entry = entry?;
|
||||||
|
move_tree_preserving_layout(&entry.path(), &dst_dir.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::remove_dir(src_dir).with_context(|| format!("Failed to remove {}", src_dir.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_tree_preserving_layout_no_overwrite(
|
||||||
|
src_root: &Path,
|
||||||
|
dst_root: &Path,
|
||||||
|
logical_root: &str,
|
||||||
|
created: &mut Vec<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for entry in WalkDir::new(src_root).follow_links(false) {
|
||||||
|
let entry = entry
|
||||||
|
.with_context(|| format!("Failed to walk relocation tree {}", src_root.display()))?;
|
||||||
|
let src_path = entry.path();
|
||||||
|
let rel = src_path
|
||||||
|
.strip_prefix(src_root)
|
||||||
|
.with_context(|| format!("Failed to strip relocation root {}", src_root.display()))?;
|
||||||
|
if rel.as_os_str().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_path = dst_root.join(rel);
|
||||||
|
let metadata = src_path
|
||||||
|
.symlink_metadata()
|
||||||
|
.with_context(|| format!("Failed to inspect {}", src_path.display()))?;
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
|
||||||
|
if file_type.is_dir() {
|
||||||
|
match dst_path.symlink_metadata() {
|
||||||
|
Ok(dst_meta) => {
|
||||||
|
if !dst_meta.file_type().is_dir() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to replay relocated directory into {}: destination exists and is not a directory",
|
||||||
|
dst_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||||
|
fs::create_dir_all(&dst_path).with_context(|| {
|
||||||
|
format!("Failed to create directory {}", dst_path.display())
|
||||||
|
})?;
|
||||||
|
apply_unix_mode(&dst_path, &metadata)?;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err)
|
||||||
|
.with_context(|| format!("Failed to inspect {}", dst_path.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = dst_path.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("Failed to create directory {}", parent.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst_path.symlink_metadata().is_ok() {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to replay relocated path into {}: destination already exists",
|
||||||
|
dst_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if file_type.is_symlink() {
|
||||||
|
let target = fs::read_link(src_path)
|
||||||
|
.with_context(|| format!("Failed to read symlink {}", src_path.display()))?;
|
||||||
|
std::os::unix::fs::symlink(&target, &dst_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create relocated symlink {} -> {}",
|
||||||
|
dst_path.display(),
|
||||||
|
target.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
fs::copy(src_path, &dst_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to copy relocated path {} to {}",
|
||||||
|
src_path.display(),
|
||||||
|
dst_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
apply_unix_mode(&dst_path, &metadata)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logical = Path::new(logical_root).join(rel);
|
||||||
|
let logical = logical
|
||||||
|
.to_str()
|
||||||
|
.context("Relocated install paths must be valid UTF-8")?
|
||||||
|
.to_string();
|
||||||
|
created.push(logical);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_path_in_place(path: &Path, rel: &str) -> Result<()> {
|
fn remove_path_in_place(path: &Path, rel: &str) -> Result<()> {
|
||||||
let metadata = path
|
let metadata = path
|
||||||
.symlink_metadata()
|
.symlink_metadata()
|
||||||
@@ -1011,6 +1118,83 @@ impl FsTransaction {
|
|||||||
self.tx_dir.join("removed").join(rel)
|
self.tx_dir.join("removed").join(rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn relocated_path(&self, rel: &str) -> PathBuf {
|
||||||
|
self.tx_dir.join("relocated").join(rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocate_directory_for_symlink_swap(&mut self, rel: &str) -> Result<()> {
|
||||||
|
let src = self.rootfs.join(rel);
|
||||||
|
let relocated = self.relocated_path(rel);
|
||||||
|
move_directory_contents(&src, &relocated)?;
|
||||||
|
self.relocated.push(rel.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replay_relocated_dir_if_present(&mut self, rel: &str) -> Result<()> {
|
||||||
|
let relocated = self.relocated_path(rel);
|
||||||
|
match relocated.symlink_metadata() {
|
||||||
|
Ok(meta) if meta.file_type().is_dir() => {}
|
||||||
|
Ok(_) => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Relocation staging path is not a directory: {}",
|
||||||
|
relocated.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err)
|
||||||
|
.with_context(|| format!("Failed to inspect {}", relocated.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_tree_preserving_layout_no_overwrite(
|
||||||
|
&relocated,
|
||||||
|
&self.rootfs.join(rel),
|
||||||
|
rel,
|
||||||
|
&mut self.created,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_relocated_dir(&self, rel: &str) -> Result<()> {
|
||||||
|
let relocated = self.relocated_path(rel);
|
||||||
|
match relocated.symlink_metadata() {
|
||||||
|
Ok(meta) if meta.file_type().is_dir() => {}
|
||||||
|
Ok(_) => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Relocation staging path is not a directory: {}",
|
||||||
|
relocated.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err)
|
||||||
|
.with_context(|| format!("Failed to inspect {}", relocated.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst = self.rootfs.join(rel);
|
||||||
|
match dst.symlink_metadata() {
|
||||||
|
Ok(meta) if meta.file_type().is_dir() => {}
|
||||||
|
Ok(_) => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Failed to restore relocated directory contents into {}: destination is not a directory",
|
||||||
|
dst.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||||
|
fs::create_dir_all(&dst)
|
||||||
|
.with_context(|| format!("Failed to create directory {}", dst.display()))?;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err).with_context(|| format!("Failed to inspect {}", dst.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move_directory_contents(&relocated, &dst)?;
|
||||||
|
cleanup_empty_parent_dirs(&self.tx_dir, &relocated)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn restore_backup_entry(&self, src: &Path, dst: &Path) -> Result<()> {
|
fn restore_backup_entry(&self, src: &Path, dst: &Path) -> Result<()> {
|
||||||
let metadata = src
|
let metadata = src
|
||||||
.symlink_metadata()
|
.symlink_metadata()
|
||||||
@@ -1068,7 +1252,13 @@ impl FsTransaction {
|
|||||||
|
|
||||||
/// Roll back file operations performed by `install_atomic`.
|
/// Roll back file operations performed by `install_atomic`.
|
||||||
pub fn rollback(&self) -> Result<()> {
|
pub fn rollback(&self) -> Result<()> {
|
||||||
// Restore overwritten paths first so removed children have their original parent layout.
|
// Remove files that were newly created
|
||||||
|
for rel in &self.created {
|
||||||
|
let dst = self.rootfs.join(rel);
|
||||||
|
let _ = fs::remove_file(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore overwritten paths first so relocated and removed children have their parent layout.
|
||||||
for rel in &self.backed_up {
|
for rel in &self.backed_up {
|
||||||
let src = self.backup_path(rel);
|
let src = self.backup_path(rel);
|
||||||
let dst = self.rootfs.join(rel);
|
let dst = self.rootfs.join(rel);
|
||||||
@@ -1077,6 +1267,10 @@ impl FsTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for rel in &self.relocated {
|
||||||
|
self.restore_relocated_dir(rel)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Restore removed files/directories.
|
// Restore removed files/directories.
|
||||||
for rel in &self.removed {
|
for rel in &self.removed {
|
||||||
let src = self.removed_backup_path(rel);
|
let src = self.removed_backup_path(rel);
|
||||||
@@ -1086,12 +1280,6 @@ impl FsTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove files that were newly created
|
|
||||||
for rel in &self.created {
|
|
||||||
let dst = self.rootfs.join(rel);
|
|
||||||
let _ = fs::remove_file(dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1147,6 +1335,7 @@ pub fn install_atomic(
|
|||||||
tx_dir,
|
tx_dir,
|
||||||
backed_up: Vec::new(),
|
backed_up: Vec::new(),
|
||||||
created: Vec::new(),
|
created: Vec::new(),
|
||||||
|
relocated: Vec::new(),
|
||||||
removed: Vec::new(),
|
removed: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut staged_paths = HashSet::new();
|
let mut staged_paths = HashSet::new();
|
||||||
@@ -1227,15 +1416,21 @@ pub fn install_atomic(
|
|||||||
install_rel_path
|
install_rel_path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
backup_existing_path(&dest_path, &backup_path, &install_rel_path)?;
|
||||||
|
tx.backed_up.push(install_rel_path.clone());
|
||||||
remove_obsolete_children_for_dir(
|
remove_obsolete_children_for_dir(
|
||||||
&mut tx,
|
&mut tx,
|
||||||
rootfs,
|
rootfs,
|
||||||
&install_rel_path,
|
&install_rel_path,
|
||||||
remove_paths,
|
remove_paths,
|
||||||
)?;
|
)?;
|
||||||
|
if file_type.is_symlink() {
|
||||||
|
tx.relocate_directory_for_symlink_swap(&install_rel_path)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
backup_existing_path(&dest_path, &backup_path, &install_rel_path)?;
|
||||||
|
tx.backed_up.push(install_rel_path.clone());
|
||||||
}
|
}
|
||||||
backup_existing_path(&dest_path, &backup_path, &install_rel_path)?;
|
|
||||||
tx.backed_up.push(install_rel_path.clone());
|
|
||||||
} else {
|
} else {
|
||||||
tx.created.push(install_rel_path.clone());
|
tx.created.push(install_rel_path.clone());
|
||||||
}
|
}
|
||||||
@@ -1266,6 +1461,7 @@ pub fn install_atomic(
|
|||||||
let target = fs::read_link(src_path)?;
|
let target = fs::read_link(src_path)?;
|
||||||
std::os::unix::fs::symlink(target, &dest_path)
|
std::os::unix::fs::symlink(target, &dest_path)
|
||||||
.with_context(|| format!("Failed to create symlink: {}", install_rel_path))?;
|
.with_context(|| format!("Failed to create symlink: {}", install_rel_path))?;
|
||||||
|
tx.replay_relocated_dir_if_present(&install_rel_path)?;
|
||||||
} else {
|
} else {
|
||||||
fs::copy(src_path, &dest_path)
|
fs::copy(src_path, &dest_path)
|
||||||
.with_context(|| format!("Failed to install: {}", install_rel_path))?;
|
.with_context(|| format!("Failed to install: {}", install_rel_path))?;
|
||||||
@@ -1724,6 +1920,82 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn install_atomic_preserves_non_obsolete_directory_contents_when_replacing_with_symlink() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let rootfs = tmp.path().join("root");
|
||||||
|
let destdir = tmp.path().join("dest");
|
||||||
|
let tx_base = tmp.path().join("tx");
|
||||||
|
std::fs::create_dir_all(rootfs.join("usr/sbin")).unwrap();
|
||||||
|
std::fs::create_dir_all(destdir.join("usr/bin")).unwrap();
|
||||||
|
std::fs::create_dir_all(destdir.join("usr")).unwrap();
|
||||||
|
std::fs::write(rootfs.join("usr/sbin/keep"), "keep-me").unwrap();
|
||||||
|
std::fs::write(rootfs.join("usr/sbin/legacy"), "old").unwrap();
|
||||||
|
std::fs::write(destdir.join("usr/bin/legacy"), "new").unwrap();
|
||||||
|
std::os::unix::fs::symlink("bin", destdir.join("usr/sbin")).unwrap();
|
||||||
|
|
||||||
|
let remove_paths = vec!["usr/sbin/legacy".to_string(), "usr/sbin".to_string()];
|
||||||
|
let tx = install_atomic(&destdir, &rootfs, &tx_base, &remove_paths, &[]).unwrap();
|
||||||
|
|
||||||
|
let sbin_meta = rootfs.join("usr/sbin").symlink_metadata().unwrap();
|
||||||
|
assert!(sbin_meta.file_type().is_symlink());
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/bin/keep")).unwrap(),
|
||||||
|
"keep-me"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/sbin/keep")).unwrap(),
|
||||||
|
"keep-me"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/bin/legacy")).unwrap(),
|
||||||
|
"new"
|
||||||
|
);
|
||||||
|
|
||||||
|
tx.rollback().unwrap();
|
||||||
|
let restored = rootfs.join("usr/sbin").symlink_metadata().unwrap();
|
||||||
|
assert!(restored.file_type().is_dir());
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/sbin/keep")).unwrap(),
|
||||||
|
"keep-me"
|
||||||
|
);
|
||||||
|
assert!(!rootfs.join("usr/bin/keep").exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn install_atomic_rejects_symlink_swap_when_relocated_contents_conflict_with_target() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let rootfs = tmp.path().join("root");
|
||||||
|
let destdir = tmp.path().join("dest");
|
||||||
|
let tx_base = tmp.path().join("tx");
|
||||||
|
std::fs::create_dir_all(rootfs.join("usr/sbin")).unwrap();
|
||||||
|
std::fs::create_dir_all(rootfs.join("usr/bin")).unwrap();
|
||||||
|
std::fs::create_dir_all(destdir.join("usr")).unwrap();
|
||||||
|
std::fs::write(rootfs.join("usr/sbin/keep"), "keep-me").unwrap();
|
||||||
|
std::fs::write(rootfs.join("usr/bin/keep"), "target-conflict").unwrap();
|
||||||
|
std::os::unix::fs::symlink("bin", destdir.join("usr/sbin")).unwrap();
|
||||||
|
|
||||||
|
let remove_paths = vec!["usr/sbin".to_string()];
|
||||||
|
let err = install_atomic(&destdir, &rootfs, &tx_base, &remove_paths, &[]).unwrap_err();
|
||||||
|
assert!(
|
||||||
|
err.to_string()
|
||||||
|
.contains("Failed to replay relocated path into")
|
||||||
|
);
|
||||||
|
|
||||||
|
let restored = rootfs.join("usr/sbin").symlink_metadata().unwrap();
|
||||||
|
assert!(restored.file_type().is_dir());
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/sbin/keep")).unwrap(),
|
||||||
|
"keep-me"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read_to_string(rootfs.join("usr/bin/keep")).unwrap(),
|
||||||
|
"target-conflict"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keep_glob_matches_question_mark_and_not_path_separator() {
|
fn keep_glob_matches_question_mark_and_not_path_separator() {
|
||||||
assert!(glob_match_path(
|
assert!(glob_match_path(
|
||||||
|
|||||||
Reference in New Issue
Block a user