Skip to content

Commit ec5edf1

Browse files
authored
Add an append_link() method to handle long link targets (#273)
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
1 parent 5a1c8ea commit ec5edf1

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

src/builder.rs

+48
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,54 @@ impl<W: Write> Builder<W> {
162162
self.append(&header, data)
163163
}
164164

165+
/// Adds a new link (symbolic or hard) entry to this archive with the specified path and target.
166+
///
167+
/// This function is similar to [`Self::append_data`] which supports long filenames,
168+
/// but also supports long link targets using GNU extensions if necessary.
169+
/// You must set the entry type to either [`EntryType::Link`] or [`EntryType::Symlink`].
170+
/// The `set_cksum` method will be invoked after setting the path. No other metadata in the
171+
/// header will be modified.
172+
///
173+
/// If you are intending to use GNU extensions, you must use this method over calling
174+
/// [`Header::set_link_name`] because that function will fail on long links.
175+
///
176+
/// Similar constraints around the position of the archive and completion
177+
/// apply as with [`Self::append_data`].
178+
///
179+
/// # Errors
180+
///
181+
/// This function will return an error for any intermittent I/O error which
182+
/// occurs when either reading or writing.
183+
///
184+
/// # Examples
185+
///
186+
/// ```
187+
/// use tar::{Builder, Header, EntryType};
188+
///
189+
/// let mut ar = Builder::new(Vec::new());
190+
/// let mut header = Header::new_gnu();
191+
/// header.set_username("foo");
192+
/// header.set_entry_type(EntryType::Symlink);
193+
/// header.set_size(0);
194+
/// ar.append_link(&mut header, "really/long/path/to/foo", "other/really/long/target").unwrap();
195+
/// let data = ar.into_inner().unwrap();
196+
/// ```
197+
pub fn append_link<P: AsRef<Path>, T: AsRef<Path>>(
198+
&mut self,
199+
header: &mut Header,
200+
path: P,
201+
target: T,
202+
) -> io::Result<()> {
203+
self._append_link(header, path.as_ref(), target.as_ref())
204+
}
205+
206+
fn _append_link(&mut self, header: &mut Header, path: &Path, target: &Path) -> io::Result<()> {
207+
prepare_header_path(self.get_mut(), header, path)?;
208+
prepare_header_link(self.get_mut(), header, target)?;
209+
header.set_cksum();
210+
self.append(&header, std::io::empty())
211+
}
212+
165213
/// Adds a file on the local filesystem to this archive.
166214
///
167215
/// This function will open the file specified by `path` and insert the file

src/header.rs

+2
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ impl Header {
420420
/// in the appropriate format. May fail if the link name is too long or if
421421
/// the path specified is not Unicode and this is a Windows platform. Will
422422
/// strip out any "." path component, which signifies the current directory.
423+
///
424+
/// To use GNU long link names, prefer instead [`crate::Builder::append_link`].
423425
pub fn set_link_name<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
424426
self._set_link_name(p.as_ref())
425427
}

tests/all.rs

+21
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,27 @@ fn long_linkname_trailing_nul() {
914914
assert_eq!(&*e.link_name_bytes().unwrap(), b"foo");
915915
}
916916

917+
#[test]
918+
fn long_linkname_gnu() {
919+
for t in [tar::EntryType::Symlink, tar::EntryType::Link] {
920+
let mut b = Builder::new(Vec::<u8>::new());
921+
let mut h = Header::new_gnu();
922+
h.set_entry_type(t);
923+
h.set_size(0);
924+
let path = "usr/lib/.build-id/05/159ed904e45ff5100f7acd3d3b99fa7e27e34f";
925+
let target = "../../../../usr/lib64/qt5/plugins/wayland-graphics-integration-server/libqt-wayland-compositor-xcomposite-egl.so";
926+
t!(b.append_link(&mut h, path, target));
927+
928+
let contents = t!(b.into_inner());
929+
let mut a = Archive::new(&contents[..]);
930+
931+
let e = &t!(t!(a.entries()).next().unwrap());
932+
assert_eq!(e.header().entry_type(), t);
933+
assert_eq!(e.path().unwrap().to_str().unwrap(), path);
934+
assert_eq!(e.link_name().unwrap().unwrap().to_str().unwrap(), target);
935+
}
936+
}
937+
917938
#[test]
918939
fn encoded_long_name_has_trailing_nul() {
919940
let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());

0 commit comments

Comments
 (0)