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_ctrlaltdel",
|
||||||
"uu_dmesg",
|
"uu_dmesg",
|
||||||
"uu_fsfreeze",
|
"uu_fsfreeze",
|
||||||
|
"uu_hexdump",
|
||||||
"uu_last",
|
"uu_last",
|
||||||
"uu_lscpu",
|
"uu_lscpu",
|
||||||
"uu_lsipc",
|
"uu_lsipc",
|
||||||
@@ -1443,6 +1444,14 @@ dependencies = [
|
|||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uu_hexdump"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"uucore",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_last"
|
name = "uu_last"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ feat_common_core = [
|
|||||||
"ctrlaltdel",
|
"ctrlaltdel",
|
||||||
"dmesg",
|
"dmesg",
|
||||||
"fsfreeze",
|
"fsfreeze",
|
||||||
|
"hexdump",
|
||||||
"last",
|
"last",
|
||||||
"lscpu",
|
"lscpu",
|
||||||
"lsipc",
|
"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" }
|
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" }
|
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" }
|
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" }
|
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" }
|
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" }
|
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"]
|
#[path = "by-util/test_fsfreeze.rs"]
|
||||||
mod test_fsfreeze;
|
mod test_fsfreeze;
|
||||||
|
|
||||||
|
#[cfg(feature = "hexdump")]
|
||||||
|
#[path = "by-util/test_hexdump.rs"]
|
||||||
|
mod test_hexdump;
|
||||||
|
|
||||||
#[cfg(feature = "mcookie")]
|
#[cfg(feature = "mcookie")]
|
||||||
#[path = "by-util/test_mcookie.rs"]
|
#[path = "by-util/test_mcookie.rs"]
|
||||||
mod test_mcookie;
|
mod test_mcookie;
|
||||||
|
|||||||
Reference in New Issue
Block a user