Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions man/3cpio.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Benjamin Drung
*3cpio* {*-t*|*--list*} [*-v*|*--debug*] [*-P* _LIST_] _ARCHIVE_ [_pattern_...]

*3cpio* {*-x*|*--extract*} [*-v*|*--debug*] [*-C* _DIR_] [*-P* _LIST_] [*-p*] [*-s* _NAME_]
[*--to-stdout*] [*--force*] _ARCHIVE_ [_pattern_...]
[*--to-stdout*] [*--force*] [*-F*] _ARCHIVE_ [*-F*|*--file* _ARCHIVE_...] [_pattern_...]

*3cpio* {*-V*|*--version*}

Expand Down Expand Up @@ -74,8 +74,10 @@ Following compression formats are supported: bzip2, gzip, lz4, lzma, lzop, xz, z
matching at least one of those __pattern__s.
These __pattern__s are shell wildcard patterns (see *glob*(7)).

*-x*, *--extract* _ARCHIVE_ [_pattern_...]::
*-x*, *--extract* [*-F*|*--file*] _ARCHIVE_ [*-F*|*--file* _ARCHIVE_] [_pattern_...]::
Extract cpio archives.
Multiple archives can be specified with *-F*.
The option *-F* can be omitted for the first archive.
If one or more __pattern__s are supplied, extract only files
matching at least one of those __pattern__s.
These __pattern__s are shell wildcard patterns (see *glob*(7)).
Expand Down
66 changes: 37 additions & 29 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct Args {
force: bool,
list: bool,
log_level: Level,
archive: Option<String>,
archives: Vec<String>,
make_directories: bool,
parts: Option<Ranges>,
patterns: Vec<Pattern>,
Expand Down Expand Up @@ -58,7 +58,9 @@ fn print_help() {
{executable} {{-c|--create}} [-v|--debug] [-C DIR] [--data-align BYTES] [ARCHIVE] < manifest
{executable} {{-e|--examine}} [--raw] ARCHIVE
{executable} {{-t|--list}} [-v|--debug] [-P LIST] ARCHIVE [pattern...]
{executable} {{-x|--extract}} [-v|--debug] [-C DIR] [--make-directories] [-P LIST] [-p] [-s NAME] [--to-stdout] [--force] ARCHIVE [pattern...]
{executable} {{-x|--extract}} [-v|--debug] [-C DIR] [--make-directories] [-P LIST] \
[-p] [-s NAME] [--to-stdout] [--force] [-F|--file] \
ARCHIVE [-F|--file ARCHIVE...] [pattern...]

Optional arguments:
--count Print the number of concatenated cpio archives.
Expand Down Expand Up @@ -103,7 +105,7 @@ fn parse_args() -> Result<Args, lexopt::Error> {
let mut list = 0;
let mut log_level = Level::Warning;
let mut directory = ".".into();
let mut archive = None;
let mut archives = Vec::new();
let mut make_directories = false;
let mut patterns = Vec::new();
let mut raw = false;
Expand Down Expand Up @@ -179,8 +181,11 @@ fn parse_args() -> Result<Args, lexopt::Error> {
Short('x') | Long("extract") => {
extract = 1;
}
Value(val) if archive.is_none() => {
archive = Some(val.string()?);
Value(val) if archives.is_empty() => {
archives.push(val.string()?);
}
Short('F') | Long("file") => {
archives.push(parser.value()?.string()?);
}
Value(val) => arguments.push(val.string()?),
_ => return Err(arg.unexpected()),
Expand Down Expand Up @@ -210,9 +215,12 @@ fn parse_args() -> Result<Args, lexopt::Error> {
}
}

if create != 1 && archive.is_none() {
if create != 1 && archives.is_empty() {
return Err("missing argument ARCHIVE".into());
}
if extract != 1 && archives.len() > 1 {
return Err("specifying multiple --file=ARCHIVE is only supported for --extract".into());
}

Ok(Args {
count: count == 1,
Expand All @@ -224,7 +232,7 @@ fn parse_args() -> Result<Args, lexopt::Error> {
force,
list: list == 1,
log_level,
archive,
archives,
make_directories,
parts,
patterns,
Expand Down Expand Up @@ -342,7 +350,7 @@ fn main() -> ExitCode {

if args.create {
let mut archive = None;
if let Some(path) = args.archive.as_ref() {
if let Some(path) = args.archives.first() {
archive = match File::create(path) {
Ok(f) => Some(f),
Err(e) => {
Expand All @@ -368,7 +376,7 @@ fn main() -> ExitCode {
_ => {
eprintln!(
"{executable}: Error: Failed to create '{}': {error}",
args.archive.unwrap_or("cpio on stdout".into()),
args.archives.first().unwrap_or(&"cpio on stdout".into()),
);
return ExitCode::FAILURE;
}
Expand All @@ -377,16 +385,17 @@ fn main() -> ExitCode {
return ExitCode::SUCCESS;
};

let archive = match File::open(args.archive.as_ref().unwrap()) {
Ok(f) => f,
Err(e) => {
eprintln!(
"{executable}: Error: Failed to open '{}': {e}",
args.archive.unwrap(),
);
return ExitCode::FAILURE;
}
};
let mut opened_archives = Vec::new();
for path in &args.archives {
let archive = match File::open(path) {
Ok(f) => f,
Err(e) => {
eprintln!("{executable}: Error: Failed to open '{path}': {e}");
return ExitCode::FAILURE;
}
};
opened_archives.push((path.clone(), archive));
}

if args.extract && !args.to_stdout {
if let Err(e) = create_and_set_current_dir(&args.directory, args.force) {
Expand All @@ -402,16 +411,15 @@ fn main() -> ExitCode {
return ExitCode::FAILURE;
}
};
let (operation, result) = operate_on_archive(archive, &args, &cwd, &mut logger);
if let Err(e) = result {
match e.kind() {
ErrorKind::BrokenPipe => {}
_ => {
eprintln!(
"{executable}: Error: Failed to {operation} of '{}': {e}",
args.archive.as_deref().unwrap(),
);
return ExitCode::FAILURE;
for (path, archive) in opened_archives {
let (operation, result) = operate_on_archive(archive, &args, &cwd, &mut logger);
if let Err(e) = result {
match e.kind() {
ErrorKind::BrokenPipe => break,
_ => {
eprintln!("{executable}: Error: Failed to {operation} of '{path}': {e}");
return ExitCode::FAILURE;
}
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,19 @@ fn test_create_missing_path() -> Result<(), Box<dyn Error>> {
Ok(())
}

#[test]
fn test_create_multiple() -> Result<(), Box<dyn Error>> {
let mut cmd = get_command();
cmd.args(["--create", "--file", "/tmp/first", "-F", "/tmp/second"]);
cmd.output()?
.assert_failure(2)
.assert_stderr_contains(
"Error: specifying multiple --file=ARCHIVE is only supported for --extract",
)
.assert_stdout("");
Ok(())
}

#[test]
fn test_create_uncompressed_plus_zstd_on_stdout() -> Result<(), Box<dyn Error>> {
let mut cmd = get_command();
Expand Down Expand Up @@ -462,6 +475,30 @@ fn test_examine_single_cpio_raw() -> Result<(), Box<dyn Error>> {
Ok(())
}

#[test]
fn test_extract_multiple() -> Result<(), Box<dyn Error>> {
let tempdir = TempDir::new()?;
let mut cmd = get_command();
cmd.arg("-x")
.arg("-C")
.arg(&tempdir.path)
.arg("-v")
.arg("-F")
.arg("tests/single.cpio")
.arg("--file")
.arg("tests/shell.cpio");

println!("tempdir = {:?}", tempdir.path);
cmd.output()?
.assert_stderr(".\npath\npath/file\n.\nusr\nusr/bin\nusr/bin/ash\nusr/bin/sh\n")
.assert_success()
.assert_stdout("");
assert!(tempdir.path.join("path/file").exists());
assert!(tempdir.path.join("usr/bin/ash").exists());
assert!(tempdir.path.join("usr/bin/sh").exists());
Ok(())
}

#[test]
fn test_extract_to_stdout() -> Result<(), Box<dyn Error>> {
let mut cmd = get_command();
Expand Down
20 changes: 10 additions & 10 deletions tests/generate
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,36 @@ generate_cpio "${input}/single" > single.cpio
mkdir -p "$input/shell/usr/bin/"
echo "This is a fake busybox binary to simulate a POSIX shell" > "$input/shell/usr/bin/sh"
ln -s sh "$input/shell/usr/bin/ash"
generate_cpio "${input}/shell" > "$input/shell.cpio"
touch --date="@${SOURCE_DATE_EPOCH}" "$input/shell.cpio"
generate_cpio "${input}/shell" > shell.cpio
touch --date="@${SOURCE_DATE_EPOCH}" shell.cpio

mkdir -p "$input/bigdata"
dd bs=1000 count=102500 if=/dev/zero of="$input/bigdata/zeros"
generate_cpio "${input}/bigdata" > "$input/bigdata.cpio"
touch --date="@${SOURCE_DATE_EPOCH}" "$input/bigdata.cpio"

cp single.cpio bzip2.cpio
bzip2 -9 < "$input/shell.cpio" >> bzip2.cpio
bzip2 -9 < shell.cpio >> bzip2.cpio

cp single.cpio gzip.cpio
gzip -n -9 < "$input/shell.cpio" >> gzip.cpio
gzip -n -9 < shell.cpio >> gzip.cpio

cp single.cpio lz4.cpio
lz4 -l -9 < "$input/shell.cpio" >> lz4.cpio
lz4 -l -9 < shell.cpio >> lz4.cpio

cp single.cpio lzma.cpio
lzma -9 < "$input/shell.cpio" >> lzma.cpio
lzma -9 < shell.cpio >> lzma.cpio

cp single.cpio lzop.cpio
lzop -9 -c "$input/shell.cpio" >> lzop.cpio
lzop -9 -c shell.cpio >> lzop.cpio

cp single.cpio xz.cpio
xz --check=crc32 --threads=1 -9 < "$input/shell.cpio" >> xz.cpio
xz --check=crc32 --threads=1 -9 < shell.cpio >> xz.cpio

cp single.cpio zstd.cpio
zstd -q -9 < "$input/shell.cpio" >> zstd.cpio
zstd -q -9 < shell.cpio >> zstd.cpio

cat single.cpio "$input/shell.cpio" > bigdata.cpio
cat single.cpio shell.cpio > bigdata.cpio
zstd -q -9 < "$input/bigdata.cpio" >> bigdata.cpio

mkdir "$input/path-traversal"
Expand Down
Binary file added tests/shell.cpio
Binary file not shown.