hexdump: Add tool
This supports the same functionality as util-linux hexdump except for --format and --color options. Output should be bit-exact, except as added bonus this supports the `-s` option on pipes and other non-seekable files.
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1359,6 +1359,7 @@ dependencies = [
|
||||
"uu_ctrlaltdel",
|
||||
"uu_dmesg",
|
||||
"uu_fsfreeze",
|
||||
"uu_hexdump",
|
||||
"uu_last",
|
||||
"uu_lscpu",
|
||||
"uu_lsipc",
|
||||
@@ -1443,6 +1444,14 @@ dependencies = [
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_hexdump"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_last"
|
||||
version = "0.0.1"
|
||||
|
||||
@@ -32,6 +32,7 @@ feat_common_core = [
|
||||
"ctrlaltdel",
|
||||
"dmesg",
|
||||
"fsfreeze",
|
||||
"hexdump",
|
||||
"last",
|
||||
"lscpu",
|
||||
"lsipc",
|
||||
@@ -99,6 +100,7 @@ chcpu = { optional = true, version = "0.0.1", package = "uu_chcpu", path = "src/
|
||||
ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" }
|
||||
dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" }
|
||||
fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" }
|
||||
hexdump = { optional = true, version = "0.0.1", package = "uu_hexdump", path = "src/uu/hexdump" }
|
||||
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
|
||||
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
|
||||
lsipc = { optional = true, version = "0.0.1", package = "uu_lsipc", path = "src/uu/lsipc" }
|
||||
|
||||
16
src/uu/hexdump/Cargo.toml
Normal file
16
src/uu/hexdump/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "uu_hexdump"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
description = "hexdump ~ display file contents in hexadecimal, decimal, octal, or ascii"
|
||||
|
||||
[lib]
|
||||
path = "src/hexdump.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "hexdump"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true, features = ["parser"] }
|
||||
7
src/uu/hexdump/hexdump.md
Normal file
7
src/uu/hexdump/hexdump.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# hexdump
|
||||
|
||||
```
|
||||
hexdump [options] <file>...
|
||||
```
|
||||
|
||||
Display file contents in hexadecimal, decimal, octal, or ascii
|
||||
474
src/uu/hexdump/src/hexdump.rs
Normal file
474
src/uu/hexdump/src/hexdump.rs
Normal file
@@ -0,0 +1,474 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Seek, SeekFrom};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::{
|
||||
error::{set_exit_code, UResult, USimpleError},
|
||||
format_usage, help_about, help_usage,
|
||||
parser::parse_size,
|
||||
};
|
||||
|
||||
struct ChainedFileReader {
|
||||
file_paths: Vec<String>,
|
||||
current_file: Option<BufReader<File>>,
|
||||
current_file_index: usize,
|
||||
remaining_bytes: Option<u64>,
|
||||
open_error_count: usize,
|
||||
}
|
||||
|
||||
impl ChainedFileReader {
|
||||
fn new(file_paths: Vec<String>, length_limit: Option<u64>) -> Self {
|
||||
Self {
|
||||
file_paths,
|
||||
current_file: None,
|
||||
current_file_index: 0,
|
||||
remaining_bytes: length_limit,
|
||||
open_error_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_current_file(&mut self) -> bool {
|
||||
if self.current_file.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
while self.current_file_index < self.file_paths.len() {
|
||||
let file_path = &self.file_paths[self.current_file_index];
|
||||
match File::open(file_path) {
|
||||
Ok(file) => {
|
||||
self.current_file = Some(BufReader::new(file));
|
||||
return true;
|
||||
}
|
||||
Err(e) => {
|
||||
uucore::show_error!("cannot open '{}': {}", file_path, e);
|
||||
set_exit_code(1);
|
||||
self.open_error_count += 1;
|
||||
self.current_file_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn current_file(&mut self) -> &mut BufReader<File> {
|
||||
self.current_file.as_mut().unwrap()
|
||||
}
|
||||
|
||||
// Note: Callers assume partial reads won't happen (except on EOF)
|
||||
fn read(&mut self, buf: &mut [u8]) -> usize {
|
||||
if self.remaining_bytes == Some(0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
while self.ensure_current_file() {
|
||||
let remaining_in_buffer = (buf.len() - offset) as u64;
|
||||
let nbytes = remaining_in_buffer.min(self.remaining_bytes.unwrap_or(u64::MAX)) as usize;
|
||||
if nbytes == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
match self.current_file().read(&mut buf[offset..offset + nbytes]) {
|
||||
Ok(0) => {
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
}
|
||||
Ok(n) => {
|
||||
offset += n;
|
||||
self.remaining_bytes = self.remaining_bytes.map(|x| x - n as u64);
|
||||
}
|
||||
Err(e) => {
|
||||
uucore::show_error!(
|
||||
"cannot read '{}': {}",
|
||||
self.file_paths[self.current_file_index],
|
||||
e
|
||||
);
|
||||
set_exit_code(1);
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
fn skip_bytes(&mut self, bytes_to_skip: u64) {
|
||||
let mut remaining = bytes_to_skip;
|
||||
|
||||
while remaining > 0 && self.ensure_current_file() {
|
||||
match self.current_file().seek(SeekFrom::End(0)) {
|
||||
Ok(file_size) => {
|
||||
if remaining >= file_size {
|
||||
remaining -= file_size;
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
} else {
|
||||
match self.current_file().seek(SeekFrom::Start(remaining)) {
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
uucore::show_error!(
|
||||
"cannot seek '{}': {}",
|
||||
self.file_paths[self.current_file_index],
|
||||
e
|
||||
);
|
||||
set_exit_code(1);
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// This file doesn't support seeking, fall back to dummy reads
|
||||
while remaining > 0 {
|
||||
let mut dummy_buf = vec![0u8; remaining.min(65536) as usize];
|
||||
match self.current_file().read(&mut dummy_buf) {
|
||||
Ok(0) => {
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
break;
|
||||
}
|
||||
Ok(n) => remaining -= n as u64,
|
||||
Err(e) => {
|
||||
uucore::show_error!(
|
||||
"cannot read '{}': {}",
|
||||
self.file_paths[self.current_file_index],
|
||||
e
|
||||
);
|
||||
set_exit_code(1);
|
||||
self.current_file = None;
|
||||
self.current_file_index += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ABOUT: &str = help_about!("hexdump.md");
|
||||
const USAGE: &str = help_usage!("hexdump.md");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum DisplayFormat {
|
||||
Canonical, // -C: hex + ASCII display
|
||||
OneByteOctal, // -b: one-byte octal display
|
||||
OneByteHex, // -X: one-byte hex display
|
||||
OneByteChar, // -c: one-byte character display
|
||||
TwoBytesDecimal, // -d: two-byte decimal display
|
||||
TwoBytesOctal, // -o: two-byte octal display
|
||||
TwoBytesHex, // -x: two-byte hex display
|
||||
TwoBytesHexDefault, // default: two-byte hex display with compact spacing
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HexdumpOptions {
|
||||
formats: Vec<DisplayFormat>,
|
||||
length: Option<u64>,
|
||||
skip: u64,
|
||||
no_squeezing: bool,
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
// Collect formats in same order they were in command line
|
||||
let format_mappings = [
|
||||
("canonical", DisplayFormat::Canonical),
|
||||
("one-byte-octal", DisplayFormat::OneByteOctal),
|
||||
("one-byte-hex", DisplayFormat::OneByteHex),
|
||||
("one-byte-char", DisplayFormat::OneByteChar),
|
||||
("two-bytes-decimal", DisplayFormat::TwoBytesDecimal),
|
||||
("two-bytes-octal", DisplayFormat::TwoBytesOctal),
|
||||
("two-bytes-hex", DisplayFormat::TwoBytesHex),
|
||||
];
|
||||
|
||||
let mut formats = BTreeMap::new();
|
||||
for (id, format) in format_mappings {
|
||||
if matches.value_source(id) == Some(clap::parser::ValueSource::CommandLine) {
|
||||
for index in matches.indices_of(id).unwrap() {
|
||||
formats.insert(index, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
if formats.is_empty() {
|
||||
formats.insert(0, DisplayFormat::TwoBytesHexDefault);
|
||||
}
|
||||
|
||||
let options = HexdumpOptions {
|
||||
formats: formats.into_values().collect(),
|
||||
length: matches.get_one::<u64>("length").copied(),
|
||||
skip: matches.get_one::<u64>("skip").copied().unwrap_or(0),
|
||||
no_squeezing: matches.get_flag("no-squeezing"),
|
||||
// TODO: /dev/stdin doesn't work on Windows
|
||||
files: matches
|
||||
.get_many::<String>("files")
|
||||
.map(|files| files.cloned().collect())
|
||||
.unwrap_or_else(|| vec!["/dev/stdin".to_string()]),
|
||||
};
|
||||
run_hexdump(options)
|
||||
}
|
||||
|
||||
fn run_hexdump(options: HexdumpOptions) -> UResult<()> {
|
||||
let mut reader = ChainedFileReader::new(options.files.clone(), options.length);
|
||||
reader.skip_bytes(options.skip);
|
||||
|
||||
let mut offset = options.skip;
|
||||
let mut last_line: Vec<u8> = Vec::new();
|
||||
let mut squeezing = false;
|
||||
|
||||
loop {
|
||||
let mut line_data = [0u8; 16];
|
||||
let bytes_read = reader.read(&mut line_data);
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
let line_data = &line_data[..bytes_read];
|
||||
|
||||
// Handle line squeezing (consolidate identical lines into one '*')
|
||||
if !options.no_squeezing && last_line == line_data {
|
||||
if !squeezing {
|
||||
println!("*");
|
||||
squeezing = true;
|
||||
}
|
||||
} else {
|
||||
for format in &options.formats {
|
||||
print_hexdump_line(*format, offset, line_data);
|
||||
}
|
||||
last_line.clear();
|
||||
last_line.extend_from_slice(line_data);
|
||||
squeezing = false;
|
||||
}
|
||||
|
||||
offset += line_data.len() as u64;
|
||||
}
|
||||
|
||||
if offset != 0 {
|
||||
// Formatting of end offset must match last format's
|
||||
print_offset(offset, *options.formats.last().unwrap());
|
||||
println!();
|
||||
}
|
||||
|
||||
if reader.open_error_count == reader.file_paths.len() {
|
||||
Err(USimpleError::new(1, "all input file arguments failed"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_hexdump_line(format: DisplayFormat, offset: u64, line_data: &[u8]) {
|
||||
print_offset(offset, format);
|
||||
match format {
|
||||
DisplayFormat::Canonical => print_canonical(line_data),
|
||||
DisplayFormat::OneByteOctal => print_bytes(line_data, |b| print!(" {:03o}", b)),
|
||||
DisplayFormat::OneByteHex => print_bytes(line_data, |b| print!(" {:02x}", b)),
|
||||
DisplayFormat::OneByteChar => print_bytes(line_data, print_char_byte),
|
||||
DisplayFormat::TwoBytesDecimal => print_words(line_data, 8, |w| print!(" {:05}", w)),
|
||||
DisplayFormat::TwoBytesOctal => print_words(line_data, 8, |w| print!(" {:06o}", w)),
|
||||
DisplayFormat::TwoBytesHex => print_words(line_data, 8, |w| print!(" {:04x}", w)),
|
||||
DisplayFormat::TwoBytesHexDefault => print_words(line_data, 5, |w| print!(" {:04x}", w)),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_offset(offset: u64, format: DisplayFormat) {
|
||||
if format == DisplayFormat::Canonical {
|
||||
print!("{:08x}", offset);
|
||||
} else {
|
||||
print!("{:07x}", offset);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_canonical(line_data: &[u8]) {
|
||||
print!(" ");
|
||||
|
||||
for i in 0..16 {
|
||||
// Extra space between halfs
|
||||
if i == 8 {
|
||||
print!(" ");
|
||||
}
|
||||
|
||||
if i < line_data.len() {
|
||||
print!("{:02x} ", line_data[i]);
|
||||
} else {
|
||||
print!(" ");
|
||||
}
|
||||
}
|
||||
|
||||
print!(" |");
|
||||
|
||||
for &byte in line_data {
|
||||
if byte.is_ascii_graphic() || byte == b' ' {
|
||||
print!("{}", byte as char);
|
||||
} else {
|
||||
print!(".");
|
||||
}
|
||||
}
|
||||
println!("|");
|
||||
}
|
||||
|
||||
fn print_bytes<F>(line_data: &[u8], byte_printer: F)
|
||||
where
|
||||
F: Fn(u8),
|
||||
{
|
||||
for &byte in line_data {
|
||||
byte_printer(byte);
|
||||
}
|
||||
// Original hexdump pads all lines to same length
|
||||
println!("{:width$}", "", width = (16 - line_data.len()) * 4);
|
||||
}
|
||||
|
||||
fn print_char_byte(byte: u8) {
|
||||
match byte {
|
||||
b'\0' => print!(" \\0"),
|
||||
b'\x07' => print!(" \\a"),
|
||||
b'\x08' => print!(" \\b"),
|
||||
b'\t' => print!(" \\t"),
|
||||
b'\n' => print!(" \\n"),
|
||||
b'\x0B' => print!(" \\v"),
|
||||
b'\x0C' => print!(" \\f"),
|
||||
b'\r' => print!(" \\r"),
|
||||
b if b.is_ascii_graphic() || b == b' ' => print!(" {}", b as char),
|
||||
b => print!(" {:03o}", b),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_words<F>(line_data: &[u8], chars_per_word: usize, word_printer: F)
|
||||
where
|
||||
F: Fn(u16),
|
||||
{
|
||||
for i in 0..(line_data.len() / 2) {
|
||||
word_printer(u16::from_le_bytes([line_data[i * 2], line_data[i * 2 + 1]]));
|
||||
}
|
||||
|
||||
if line_data.len() % 2 == 1 {
|
||||
word_printer(*line_data.last().unwrap() as u16);
|
||||
}
|
||||
|
||||
// Original hexdump pads all lines to same length
|
||||
let word_count = line_data.len().div_ceil(2);
|
||||
println!("{:width$}", "", width = (8 - word_count) * chars_per_word);
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
Arg::new("one-byte-octal")
|
||||
.short('b')
|
||||
.long("one-byte-octal")
|
||||
.help("one-byte octal display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("one-byte-hex")
|
||||
.short('X')
|
||||
.long("one-byte-hex")
|
||||
.help("one-byte hexadecimal display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("one-byte-char")
|
||||
.short('c')
|
||||
.long("one-byte-char")
|
||||
.help("one-byte character display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("canonical")
|
||||
.short('C')
|
||||
.long("canonical")
|
||||
.help("canonical hex+ASCII display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("two-bytes-decimal")
|
||||
.short('d')
|
||||
.long("two-bytes-decimal")
|
||||
.help("two-byte decimal display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("two-bytes-octal")
|
||||
.short('o')
|
||||
.long("two-bytes-octal")
|
||||
.help("two-byte octal display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("two-bytes-hex")
|
||||
.short('x')
|
||||
.long("two-bytes-hex")
|
||||
.help("two-byte hexadecimal display")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0)
|
||||
.default_value("0")
|
||||
.default_missing_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("length")
|
||||
.short('n')
|
||||
.long("length")
|
||||
.help("interpret only length bytes of input")
|
||||
.action(ArgAction::Set)
|
||||
.num_args(1)
|
||||
.value_parser(|s: &str| -> Result<u64, String> {
|
||||
parse_size::parse_size_u64(s).map_err(|e| format!("invalid length: {}", e))
|
||||
}),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("skip")
|
||||
.short('s')
|
||||
.long("skip")
|
||||
.help("skip offset bytes from the beginning")
|
||||
.action(ArgAction::Set)
|
||||
.num_args(1)
|
||||
.value_parser(|s: &str| -> Result<u64, String> {
|
||||
parse_size::parse_size_u64(s).map_err(|e| format!("invalid skip: {}", e))
|
||||
}),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-squeezing")
|
||||
.short('v')
|
||||
.long("no-squeezing")
|
||||
.help("output identical lines")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("files")
|
||||
.help("input files")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(0..),
|
||||
)
|
||||
}
|
||||
1
src/uu/hexdump/src/main.rs
Normal file
1
src/uu/hexdump/src/main.rs
Normal file
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_hexdump);
|
||||
312
tests/by-util/test_hexdump.rs
Normal file
312
tests/by-util/test_hexdump.rs
Normal file
@@ -0,0 +1,312 @@
|
||||
// This file is part of the uutils util-linux package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use uutests::{new_ucmd, util::TestScenario};
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_empty_input() {
|
||||
new_ucmd!().pipe_in("").succeeds().stdout_is("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_default_format() {
|
||||
new_ucmd!()
|
||||
.pipe_in("ABCD")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 4241 4443 \n0000004\n");
|
||||
|
||||
new_ucmd!()
|
||||
.pipe_in("ABC")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 4241 0043 \n0000003\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_canonical_format() {
|
||||
new_ucmd!()
|
||||
.arg("-C")
|
||||
.pipe_in("Hello World!")
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |Hello World!|\n0000000c\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_one_byte_char() {
|
||||
let input = b"Hello \t\n\0\x07\x08\x0B\x0C\r\x80\xFF";
|
||||
let expected = vec![
|
||||
"0000000 H e l l o \\t \\n \\0 \\a \\b \\v \\f \\r 200 377\n",
|
||||
"0000010\n",
|
||||
];
|
||||
|
||||
new_ucmd!()
|
||||
.arg("-c")
|
||||
.pipe_in(input)
|
||||
.succeeds()
|
||||
.stdout_is(expected.join(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_one_byte_octal() {
|
||||
new_ucmd!().arg("-b").pipe_in("ABC").succeeds().stdout_is(
|
||||
"0000000 101 102 103 \n0000003\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_one_byte_hex() {
|
||||
new_ucmd!().arg("-X").pipe_in("ABC").succeeds().stdout_is(
|
||||
"0000000 41 42 43 \n0000003\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_two_bytes_hex() {
|
||||
new_ucmd!().arg("-x").pipe_in("ABCD").succeeds().stdout_is(
|
||||
"0000000 4241 4443 \n0000004\n",
|
||||
);
|
||||
|
||||
new_ucmd!().arg("-x").pipe_in("ABC").succeeds().stdout_is(
|
||||
"0000000 4241 0043 \n0000003\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_two_bytes_decimal() {
|
||||
new_ucmd!().arg("-d").pipe_in("ABCD").succeeds().stdout_is(
|
||||
"0000000 16961 17475 \n0000004\n",
|
||||
);
|
||||
|
||||
new_ucmd!().arg("-d").pipe_in("ABC").succeeds().stdout_is(
|
||||
"0000000 16961 00067 \n0000003\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_two_bytes_octal() {
|
||||
new_ucmd!().arg("-o").pipe_in("ABCD").succeeds().stdout_is(
|
||||
"0000000 041101 042103 \n0000004\n",
|
||||
);
|
||||
|
||||
new_ucmd!().arg("-o").pipe_in("ABC").succeeds().stdout_is(
|
||||
"0000000 041101 000103 \n0000003\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_multiple_formats() {
|
||||
let expected = vec![
|
||||
"00000000 41 42 |AB|\n",
|
||||
"0000000 4241 \n",
|
||||
"00000000 41 42 |AB|\n",
|
||||
"00000002\n",
|
||||
];
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-C", "-x", "-C"])
|
||||
.pipe_in("AB")
|
||||
.succeeds()
|
||||
.stdout_is(expected.join(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_squeezing() {
|
||||
let input = vec![
|
||||
"AAAAAAAAAAAAAAAA",
|
||||
"AAAAAAAAAAAAAAAA",
|
||||
"AAAAAAAAAAAAAAAA",
|
||||
"AAAAAAAA",
|
||||
];
|
||||
|
||||
let expected_no_squeezing = vec![
|
||||
"00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
|
||||
"00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
|
||||
"00000020 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
|
||||
"00000030 41 41 41 41 41 41 41 41 |AAAAAAAA|\n",
|
||||
"00000038\n",
|
||||
];
|
||||
let expected_with_squeezing = vec![
|
||||
"00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
|
||||
"*\n",
|
||||
"00000030 41 41 41 41 41 41 41 41 |AAAAAAAA|\n",
|
||||
"00000038\n",
|
||||
];
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-C", "-v"])
|
||||
.pipe_in(input.join(""))
|
||||
.succeeds()
|
||||
.stdout_is(expected_no_squeezing.join(""));
|
||||
|
||||
new_ucmd!()
|
||||
.arg("-C")
|
||||
.pipe_in(input.join(""))
|
||||
.succeeds()
|
||||
.stdout_is(expected_with_squeezing.join(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_multiple_files() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("file1.txt", "hello");
|
||||
scene.fixtures.write("file2.txt", "world");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "file1.txt", "file2.txt"])
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"00000000 68 65 6c 6c 6f 77 6f 72 6c 64 |helloworld|\n0000000a\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_multiple_files_with_skip() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("file1.txt", "abc");
|
||||
scene.fixtures.write("file2.txt", "def");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "-s", "2", "file1.txt", "file2.txt"])
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"00000002 63 64 65 66 |cdef|\n00000006\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_multiple_files_with_length() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("file1.txt", "abcdefgh");
|
||||
scene.fixtures.write("file2.txt", "ijklmnop");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "-n", "10", "file1.txt", "file2.txt"])
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"00000000 61 62 63 64 65 66 67 68 69 6a |abcdefghij|\n0000000a\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_multiple_files_with_skip_and_length() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("file1.txt", "abcdefgh");
|
||||
scene.fixtures.write("file2.txt", "ijklmnop");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "-s", "3", "-n", "6", "file1.txt", "file2.txt"])
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"00000003 64 65 66 67 68 69 |defghi|\n00000009\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_open_error() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("valid1.txt", "ABC");
|
||||
scene.fixtures.write("valid2.txt", "DEF");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "valid1.txt", "nonexistent.txt", "valid2.txt"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains("cannot open 'nonexistent.txt'")
|
||||
.stdout_is(
|
||||
"00000000 41 42 43 44 45 46 |ABCDEF|\n00000006\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_hexdump_read_error() {
|
||||
let scene = TestScenario::new("hexdump");
|
||||
scene.fixtures.write("file1.txt", "hello");
|
||||
scene.fixtures.write("file2.txt", "world");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["-C", "file1.txt", "/proc/self/mem", "file2.txt"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains("cannot read '/proc/self/mem'")
|
||||
.stdout_is(
|
||||
"00000000 68 65 6c 6c 6f 77 6f 72 6c 64 |helloworld|\n0000000a\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_all_files_nonexistent() {
|
||||
new_ucmd!()
|
||||
.args(&["missing1.txt", "missing2.txt"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains("cannot open 'missing1.txt'")
|
||||
.stderr_contains("cannot open 'missing2.txt'")
|
||||
.stderr_contains("all input file arguments failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_size_and_length_suffixes() {
|
||||
new_ucmd!()
|
||||
.args(&["-n", "1K"])
|
||||
.pipe_in("A".repeat(2048))
|
||||
.succeeds()
|
||||
.stdout_is("0000000 4141 4141 4141 4141 4141 4141 4141 4141\n*\n0000400\n");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-s", "1K"])
|
||||
.pipe_in("A".repeat(2048))
|
||||
.succeeds()
|
||||
.stdout_is("0000400 4141 4141 4141 4141 4141 4141 4141 4141\n*\n0000800\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hexdump_invalid_skip_and_length() {
|
||||
new_ucmd!()
|
||||
.args(&["-C", "-s", "invalid"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains("invalid skip");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-C", "-n", "invalid"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_contains("invalid length");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_hexdump_skip_beyond_file() {
|
||||
new_ucmd!()
|
||||
.args(&["-C", "-s", "100"])
|
||||
.pipe_in("ABC")
|
||||
.succeeds()
|
||||
.stdout_is("00000064\n");
|
||||
}
|
||||
@@ -79,6 +79,10 @@ mod test_dmesg;
|
||||
#[path = "by-util/test_fsfreeze.rs"]
|
||||
mod test_fsfreeze;
|
||||
|
||||
#[cfg(feature = "hexdump")]
|
||||
#[path = "by-util/test_hexdump.rs"]
|
||||
mod test_hexdump;
|
||||
|
||||
#[cfg(feature = "mcookie")]
|
||||
#[path = "by-util/test_mcookie.rs"]
|
||||
mod test_mcookie;
|
||||
|
||||
Reference in New Issue
Block a user