Skip to content

Commit

Permalink
Add an append_link() method to handle long link targets
Browse files Browse the repository at this point in the history
We should support appending long symlink targets, because this
occurs in real world filesystems.  In my case, RPM set up
`.build-id` symlinks which can get long.

Add an `append_link()` method following the precendent of
`append_path()` - we're just supporting *two* potentially
long filenames.

As a side benefit, we can just do the `std::io::empty()` dance
internally and not require the caller to specify it.

The addition of special case `append()` methods is unfortunate,
because the header API methods are then really an attractive nuisance.
We should potentially consider deprecating them.

Closes: #192
  • Loading branch information
cgwalters committed Nov 18, 2021
1 parent 5a1c8ea commit 02a9426
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/builder.rs
Expand Up @@ -162,6 +162,54 @@ impl<W: Write> Builder<W> {
self.append(&header, data)
}

/// Adds a new link (symbolic or hard) entry to this archive with the specified path and target.
///
/// This function is similar to [`Self::append_data`] which supports long filenames,
/// but also supports long link targets using GNU extensions if necessary.
/// You must set the entry type to either [`EntryType::Link`] or [`EntryType::Symlink`].
/// The `set_cksum` method will be invoked after setting the path. No other metadata in the
/// header will be modified.
///
/// If you are intending to use GNU extensions, you must use this method over calling
/// [`Header::set_link_name`] because that function will fail on long links.
///
/// Similar constraints around the position of the archive and completion
/// apply as with [`Self::append_data`].
///
/// # Errors
///
/// This function will return an error for any intermittent I/O error which
/// occurs when either reading or writing.
///
/// # Examples
///
/// ```
/// use tar::{Builder, Header, EntryType};
///
/// let mut ar = Builder::new(Vec::new());
/// let mut header = Header::new_gnu();
/// header.set_username("foo");
/// header.set_entry_type(EntryType::Symlink);
/// header.set_size(0);
/// ar.append_link(&mut header, "really/long/path/to/foo", "other/really/long/target").unwrap();
/// let data = ar.into_inner().unwrap();
/// ```
pub fn append_link<P: AsRef<Path>, T: AsRef<Path>>(
&mut self,
header: &mut Header,
path: P,
target: T,
) -> io::Result<()> {
self._append_link(header, path.as_ref(), target.as_ref())
}

fn _append_link(&mut self, header: &mut Header, path: &Path, target: &Path) -> io::Result<()> {
prepare_header_path(self.get_mut(), header, path)?;
prepare_header_link(self.get_mut(), header, target)?;
header.set_cksum();
self.append(&header, std::io::empty())
}

/// Adds a file on the local filesystem to this archive.
///
/// This function will open the file specified by `path` and insert the file
Expand Down
2 changes: 2 additions & 0 deletions src/header.rs
Expand Up @@ -420,6 +420,8 @@ impl Header {
/// in the appropriate format. May fail if the link name is too long or if
/// the path specified is not Unicode and this is a Windows platform. Will
/// strip out any "." path component, which signifies the current directory.
///
/// To use GNU long link names, prefer instead [`crate::Builder::append_link`].
pub fn set_link_name<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
self._set_link_name(p.as_ref())
}
Expand Down
21 changes: 21 additions & 0 deletions tests/all.rs
Expand Up @@ -914,6 +914,27 @@ fn long_linkname_trailing_nul() {
assert_eq!(&*e.link_name_bytes().unwrap(), b"foo");
}

#[test]
fn long_linkname_gnu() {
for t in [tar::EntryType::Symlink, tar::EntryType::Link] {
let mut b = Builder::new(Vec::<u8>::new());
let mut h = Header::new_gnu();
h.set_entry_type(t);
h.set_size(0);
let path = "usr/lib/.build-id/05/159ed904e45ff5100f7acd3d3b99fa7e27e34f";
let target = "../../../../usr/lib64/qt5/plugins/wayland-graphics-integration-server/libqt-wayland-compositor-xcomposite-egl.so";
t!(b.append_link(&mut h, path, target));

let contents = t!(b.into_inner());
let mut a = Archive::new(&contents[..]);

let e = &t!(t!(a.entries()).next().unwrap());
assert_eq!(e.header().entry_type(), t);
assert_eq!(e.path().unwrap().to_str().unwrap(), path);
assert_eq!(e.link_name().unwrap().unwrap().to_str().unwrap(), target);
}
}

#[test]
fn encoded_long_name_has_trailing_nul() {
let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());
Expand Down

0 comments on commit 02a9426

Please sign in to comment.