Skip to content

Commit

Permalink
builder: add support for archiving special files on Unix (#246)
Browse files Browse the repository at this point in the history
* builder: add support for archiving special files on Unix

Added support for archiving character, block and fifo files on Unix,
this is useful when you want to take a whole system backup or
re-archiving an extracted system image

* tests: display system error for the special files test

* builder: address comments ...

* attempt to reduce `stat` calls

* builder: merge `append_fs_special` into `append_special`
  • Loading branch information
liushuyu committed Jul 22, 2021
1 parent 6fa206d commit b9cffc8
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
61 changes: 60 additions & 1 deletion src/builder.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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)?;
}
}
Expand Down
33 changes: 33 additions & 0 deletions tests/all.rs
Expand Up @@ -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());
}

0 comments on commit b9cffc8

Please sign in to comment.