diff --git a/src/builder.rs b/src/builder.rs index 0c904474..0a5bf9f8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -162,6 +162,54 @@ impl Builder { 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}; + /// + /// let mut header = Header::new_gnu(); + /// header.set_username("foo"); + /// header.set_entry_type(EntryType::symlink()); + /// header.set_size(0); + /// let mut ar = Builder::new(Vec::new()); + /// 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, T: AsRef>( + &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 diff --git a/src/header.rs b/src/header.rs index a76f78a6..b8b590be 100644 --- a/src/header.rs +++ b/src/header.rs @@ -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>(&mut self, p: P) -> io::Result<()> { self._set_link_name(p.as_ref()) } diff --git a/tests/all.rs b/tests/all.rs index 09d82ea9..c5dbb3c3 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -914,6 +914,26 @@ 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::::new()); + let mut h = Header::new_gnu(); + h.set_entry_type(t); + 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());