From 70619bab29109647275e540565be6c4f7bb39f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jelmer=20Vernoo=C4=B3?= Date: Fri, 18 Oct 2024 17:35:11 +0100 Subject: [PATCH] Overwrite existing symlinks and hardlinks, just like is done for files --- src/entry.rs | 36 ++++++++++++++++++++++++------- tests/entry.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/entry.rs b/src/entry.rs index 537e5b6..601c15a 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -549,8 +549,18 @@ impl EntryFields { } None => src.into_owned(), }; - fs::hard_link(&link_src, dst).await.map_err(|err| { - Error::new( + match fs::hard_link(&link_src, dst).await { + Ok(()) => Ok(()), + Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => { + // If the destination already exists, we want to remove it + // and try again. This is because the destination is + // probably a symlink, and we want to overwrite the + // symlink. + fs::remove_file(dst).await?; + fs::hard_link(&link_src, dst).await?; + Ok(()) + } + Err(err) => Err(Error::new( err.kind(), format!( "{} when hard linking {} to {}", @@ -558,11 +568,21 @@ impl EntryFields { link_src.display(), dst.display() ), - ) - })?; + )), + }?; } else { - symlink(&src, dst).await.map_err(|err| { - Error::new( + match symlink(&src, dst).await { + Ok(()) => Ok(()), + Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => { + // If the destination already exists, we want to remove it + // and try again. This is because the destination is + // probably a symlink, and we want to overwrite the + // symlink. + fs::remove_file(dst).await?; + symlink(&src, dst).await?; + Ok(()) + } + Err(err) => Err(Error::new( err.kind(), format!( "{} when symlinking {} to {}", @@ -570,8 +590,8 @@ impl EntryFields { src.display(), dst.display() ), - ) - })?; + )), + }?; }; return Ok(Unpacked::Other); diff --git a/tests/entry.rs b/tests/entry.rs index f93df8a..1d181b4 100644 --- a/tests/entry.rs +++ b/tests/entry.rs @@ -348,3 +348,61 @@ async fn modify_symlink_just_created() { t!(t!(File::open(&test).await).read_to_end(&mut contents).await); assert_eq!(contents.len(), 0); } + +#[tokio::test] +async fn overwrite_existing_symlink() { + let mut ar = async_tar::Builder::new(Vec::new()); + + let mut header = async_tar::Header::new_gnu(); + header.set_size(0); + header.set_entry_type(async_tar::EntryType::Symlink); + t!(header.set_path("foo")); + t!(header.set_link_name("test")); + header.set_cksum(); + t!(ar.append(&header, &[][..]).await); + + let bytes = t!(ar.into_inner().await); + let mut ar = async_tar::Archive::new(&bytes[..]); + + let td = t!(Builder::new().prefix("tar").tempdir()); + + t!(tokio::fs::symlink("bar", td.path().join("foo")).await); + + t!(ar.unpack(&td.path()).await); + + assert_eq!( + t!(std::fs::read_link(td.path().join("foo"))), + std::path::PathBuf::from("test") + ); +} + +#[tokio::test] +async fn overwrite_existing_hardlink() { + let mut ar = async_tar::Builder::new(Vec::new()); + + let mut header = async_tar::Header::new_gnu(); + header.set_size(1); + header.set_entry_type(async_tar::EntryType::Regular); + t!(header.set_path("foo")); + header.set_cksum(); + t!(ar.append(&header, &b"x"[..]).await); + + let mut header = async_tar::Header::new_gnu(); + header.set_size(0); + header.set_entry_type(async_tar::EntryType::Link); + t!(header.set_path("test")); + t!(header.set_link_name("foo")); + header.set_cksum(); + t!(ar.append(&header, &[][..]).await); + + let bytes = t!(ar.into_inner().await); + let mut ar = async_tar::Archive::new(&bytes[..]); + + let td = t!(Builder::new().prefix("tar").tempdir()); + + t!(tokio::fs::write(td.path().join("test"), b"y").await); + + t!(ar.unpack(&td.path()).await); + + assert_eq!(t!(std::fs::read_to_string(td.path().join("foo"))), "x"); +}