From fa458aa80994dca88f2e97cb12a575ea73644355 Mon Sep 17 00:00:00 2001 From: Jean Belicot Date: Thu, 2 Nov 2023 18:20:14 +0100 Subject: [PATCH] feat: allow to skip unsupported files (e.g. UNIX named socket) --- src/builder.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------- tests/all.rs | 21 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index a26eb31d..52a6caf6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -15,6 +15,8 @@ pub struct Builder { mode: HeaderMode, follow: bool, finished: bool, + #[cfg(unix)] + skip_unsupported: bool, obj: Option, } @@ -27,6 +29,8 @@ impl Builder { mode: HeaderMode::Complete, follow: true, finished: false, + #[cfg(unix)] + skip_unsupported: false, obj: Some(obj), } } @@ -44,6 +48,13 @@ impl Builder { self.follow = follow; } + /// Skip unsupported file types (e.g. UNIX sockets) rather than returning + /// with an error. + #[cfg(unix)] + pub fn skip_unsupported_file_types(&mut self, skip: bool) { + self.skip_unsupported = skip + } + /// Gets shared reference to the underlying object. pub fn get_ref(&self) -> &W { self.obj.as_ref().unwrap() @@ -237,7 +248,17 @@ impl Builder { pub fn append_path>(&mut self, path: P) -> io::Result<()> { let mode = self.mode.clone(); let follow = self.follow; - append_path_with_name(self.get_mut(), path.as_ref(), None, mode, follow) + #[cfg(unix)] + let skip_unsupported = self.skip_unsupported; + append_path_with_name( + self.get_mut(), + path.as_ref(), + None, + mode, + follow, + #[cfg(unix)] + skip_unsupported, + ) } /// Adds a file on the local filesystem to this archive under another name. @@ -275,12 +296,16 @@ impl Builder { ) -> io::Result<()> { let mode = self.mode.clone(); let follow = self.follow; + #[cfg(unix)] + let skip_unsupported = self.skip_unsupported; append_path_with_name( self.get_mut(), path.as_ref(), Some(name.as_ref()), mode, follow, + #[cfg(unix)] + skip_unsupported, ) } @@ -381,12 +406,16 @@ impl Builder { { let mode = self.mode.clone(); let follow = self.follow; + #[cfg(unix)] + let skip_unsupported = self.skip_unsupported; append_dir_all( self.get_mut(), path.as_ref(), src_path.as_ref(), mode, follow, + #[cfg(unix)] + skip_unsupported, ) } @@ -426,6 +455,7 @@ fn append_path_with_name( name: Option<&Path>, mode: HeaderMode, follow: bool, + #[cfg(unix)] skip_unsupported: bool, ) -> io::Result<()> { let stat = if follow { fs::metadata(path).map_err(|err| { @@ -460,7 +490,7 @@ fn append_path_with_name( } else { #[cfg(unix)] { - append_special(dst, path, &stat, mode) + append_special(dst, path, &stat, mode, skip_unsupported) } #[cfg(not(unix))] { @@ -475,6 +505,7 @@ fn append_special( path: &Path, stat: &fs::Metadata, mode: HeaderMode, + skip_unsupported: bool, ) -> io::Result<()> { use ::std::os::unix::fs::{FileTypeExt, MetadataExt}; @@ -482,10 +513,13 @@ fn append_special( let entry_type; if file_type.is_socket() { // sockets can't be archived - return Err(other(&format!( - "{}: socket can not be archived", - path.display() - ))); + return match skip_unsupported { + true => Ok(()), + false => Err(other(&format!( + "{}: socket can not be archived", + path.display() + ))), + }; } else if file_type.is_fifo() { entry_type = EntryType::Fifo; } else if file_type.is_char_device() { @@ -623,6 +657,7 @@ fn append_dir_all( src_path: &Path, mode: HeaderMode, follow: bool, + #[cfg(unix)] skip_unsupported: bool, ) -> io::Result<()> { let mut stack = vec![(src_path.to_path_buf(), true, false)]; while let Some((src, is_dir, is_symlink)) = stack.pop() { @@ -646,7 +681,7 @@ fn append_dir_all( { let stat = fs::metadata(&src)?; if !stat.is_file() { - append_special(dst, &dest, &stat, mode)?; + append_special(dst, &dest, &stat, mode, skip_unsupported)?; continue; } } diff --git a/tests/all.rs b/tests/all.rs index a7954e1f..87001d5c 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -23,6 +23,15 @@ macro_rules! t { }; } +macro_rules! te { + ($e:expr) => { + match $e { + Err(e) => e, + Ok(_) => panic!("{} was expected to return with error", stringify!($e)), + } + }; +} + macro_rules! tar { ($e:expr) => { &include_bytes!(concat!("archives/", $e))[..] @@ -1368,6 +1377,7 @@ fn read_only_directory_containing_files() { fn tar_directory_containing_special_files() { use std::env; use std::ffi::CString; + use std::os::unix::net::UnixListener; let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); let fifo = td.path().join("fifo"); @@ -1381,10 +1391,21 @@ fn tar_directory_containing_special_files() { } } + let socket = td.path().join("socket"); + match UnixListener::bind(socket) { + Ok(_) => {} + Err(e) => panic!("Failed to create and bind to a UNIX named socket: {}", e), + } + t!(env::set_current_dir(td.path())); let mut ar = Builder::new(Vec::new()); + // Default behavior is expected to reject UNIX named sockets, so we need to test it first. // append_path has a different logic for processing files, so we need to test it as well + te!(ar.append_path("socket")); + te!(ar.append_dir_all("special", td.path())); + ar.skip_unsupported_file_types(true); t!(ar.append_path("fifo")); + t!(ar.append_path("socket")); t!(ar.append_dir_all("special", td.path())); // unfortunately, block device file cannot be created by non-root users // as a substitute, just test the file that exists on most Unix systems