diff --git a/src/builder.rs b/src/builder.rs index a38fe81e..0c904474 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -410,10 +410,61 @@ fn append_path_with_name( Some(&link_name), ) } else { - Err(other(&format!("{} has unknown file type", path.display()))) + #[cfg(unix)] + { + append_special(dst, path, &stat, mode) + } + #[cfg(not(unix))] + { + Err(other(&format!("{} has unknown file type", path.display()))) + } } } +#[cfg(unix)] +fn append_special( + dst: &mut dyn Write, + path: &Path, + stat: &fs::Metadata, + mode: HeaderMode, +) -> io::Result<()> { + use ::std::os::unix::fs::{FileTypeExt, MetadataExt}; + + let file_type = stat.file_type(); + let entry_type; + if file_type.is_socket() { + // sockets can't be archived + return 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() { + entry_type = EntryType::Char; + } else if file_type.is_block_device() { + entry_type = EntryType::Block; + } else { + return Err(other(&format!("{} has unknown file type", path.display()))); + } + + let mut header = Header::new_gnu(); + header.set_metadata_in_mode(stat, mode); + prepare_header_path(dst, &mut header, path)?; + + header.set_entry_type(entry_type); + let dev_id = stat.rdev(); + let dev_major = ((dev_id >> 32) & 0xffff_f000) | ((dev_id >> 8) & 0x0000_0fff); + let dev_minor = ((dev_id >> 12) & 0xffff_ff00) | ((dev_id) & 0x0000_00ff); + header.set_device_major(dev_major as u32)?; + header.set_device_minor(dev_minor as u32)?; + + header.set_cksum(); + dst.write_all(header.as_bytes())?; + + Ok(()) +} + fn append_file( dst: &mut dyn Write, path: &Path, @@ -543,6 +594,14 @@ fn append_dir_all( let link_name = fs::read_link(&src)?; append_fs(dst, &dest, &stat, &mut io::empty(), mode, Some(&link_name))?; } else { + #[cfg(unix)] + { + let stat = fs::metadata(&src)?; + if !stat.is_file() { + append_special(dst, &dest, &stat, mode)?; + continue; + } + } append_file(dst, &dest, &mut fs::File::open(src)?, mode)?; } } diff --git a/tests/all.rs b/tests/all.rs index 1abb9161..d29a5190 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1212,3 +1212,36 @@ fn read_only_directory_containing_files() { let mut ar = Archive::new(&contents[..]); assert!(ar.unpack(td.path()).is_ok()); } + +// This test was marked linux only due to macOS CI can't handle `set_current_dir` correctly +#[test] +#[cfg(target_os = "linux")] +fn tar_directory_containing_special_files() { + use std::env; + use std::ffi::CString; + + let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); + let fifo = td.path().join("fifo"); + + unsafe { + let fifo_path = t!(CString::new(fifo.to_str().unwrap())); + let ret = libc::mknod(fifo_path.as_ptr(), libc::S_IFIFO | 0o644, 0); + if ret != 0 { + libc::perror(fifo_path.as_ptr()); + panic!("Failed to create a FIFO file"); + } + } + + t!(env::set_current_dir(td.path())); + let mut ar = Builder::new(Vec::new()); + // append_path has a different logic for processing files, so we need to test it as well + t!(ar.append_path("fifo")); + 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 + t!(env::set_current_dir("/dev/")); + t!(ar.append_path("loop0")); + // CI systems seem to have issues with creating a chr device + t!(ar.append_path("null")); + t!(ar.finish()); +}