1use std::io::{self, Read, Write};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum AddressRadix {
6 Octal,
7 Decimal,
8 Hex,
9 None,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Endian {
15 Little,
16 Big,
17 Native,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum OutputFormat {
23 NamedChar,
25 PrintableChar,
27 SignedDec(usize),
29 Float(usize),
31 Octal(usize),
33 UnsignedDec(usize),
35 Hex(usize),
37}
38
39#[derive(Debug, Clone)]
41pub struct OdConfig {
42 pub address_radix: AddressRadix,
43 pub formats: Vec<OutputFormat>,
44 pub z_flags: Vec<bool>,
46 pub skip_bytes: u64,
47 pub read_bytes: Option<u64>,
48 pub width: usize,
49 pub show_duplicates: bool,
50 pub endian: Endian,
51}
52
53impl Default for OdConfig {
54 fn default() -> Self {
55 Self {
56 address_radix: AddressRadix::Octal,
57 formats: vec![OutputFormat::Octal(2)],
58 z_flags: vec![false],
59 skip_bytes: 0,
60 read_bytes: None,
61 width: 16,
62 show_duplicates: false,
63 endian: Endian::Native,
64 }
65 }
66}
67
68const NAMED_CHARS: [&str; 128] = [
71 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", " bs", " ht", " nl", " vt", " ff",
72 " cr", " so", " si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can", " em",
73 "sub", "esc", " fs", " gs", " rs", " us", " sp", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
74 "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<",
75 "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
76 "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b",
77 "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
78 "v", "w", "x", "y", "z", "{", "|", "}", "~", "del",
79];
80
81fn field_width(fmt: OutputFormat) -> usize {
84 match fmt {
85 OutputFormat::NamedChar => 4, OutputFormat::PrintableChar => 4, OutputFormat::Octal(1) => 4, OutputFormat::Octal(2) => 7, OutputFormat::Octal(4) => 12, OutputFormat::Octal(8) => 23, OutputFormat::Hex(1) => 3, OutputFormat::Hex(2) => 5, OutputFormat::Hex(4) => 9, OutputFormat::Hex(8) => 17, OutputFormat::UnsignedDec(1) => 4, OutputFormat::UnsignedDec(2) => 6, OutputFormat::UnsignedDec(4) => 11, OutputFormat::UnsignedDec(8) => 21, OutputFormat::SignedDec(1) => 5, OutputFormat::SignedDec(2) => 7, OutputFormat::SignedDec(4) => 12, OutputFormat::SignedDec(8) => 21, OutputFormat::Float(4) => 16, OutputFormat::Float(8) => 25, _ => 4,
106 }
107}
108
109fn element_size(fmt: OutputFormat) -> usize {
111 match fmt {
112 OutputFormat::NamedChar | OutputFormat::PrintableChar => 1,
113 OutputFormat::SignedDec(s)
114 | OutputFormat::Float(s)
115 | OutputFormat::Octal(s)
116 | OutputFormat::UnsignedDec(s)
117 | OutputFormat::Hex(s) => s,
118 }
119}
120
121fn snprintf_g(v: f64, precision: usize) -> String {
124 let precision = precision.min(50);
125 #[cfg(unix)]
126 {
127 static FMT_STRINGS: &[&std::ffi::CStr] = &[
129 c"%.0g", c"%.1g", c"%.2g", c"%.3g", c"%.4g", c"%.5g", c"%.6g", c"%.7g", c"%.8g",
130 c"%.9g", c"%.10g", c"%.11g", c"%.12g", c"%.13g", c"%.14g", c"%.15g", c"%.16g",
131 c"%.17g", c"%.18g", c"%.19g", c"%.20g",
132 ];
133 let mut buf = [0u8; 64];
134 let fmt_cstr: std::ffi::CString;
135 let fmt_ptr = if precision < FMT_STRINGS.len() {
136 FMT_STRINGS[precision].as_ptr()
137 } else {
138 fmt_cstr = std::ffi::CString::new(format!("%.{}g", precision)).unwrap();
139 fmt_cstr.as_ptr()
140 };
141 let len =
142 unsafe { libc::snprintf(buf.as_mut_ptr() as *mut libc::c_char, buf.len(), fmt_ptr, v) };
143 if len > 0 && (len as usize) < buf.len() {
144 return String::from_utf8_lossy(&buf[..len as usize]).into_owned();
145 }
146 }
147 let s = format!("{:.prec$e}", v, prec = precision.saturating_sub(1));
149 if let Some(e_pos) = s.find('e') {
151 let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0);
152 if exp >= -(precision as i32) && exp < precision as i32 {
153 let fixed = format!(
155 "{:.prec$}",
156 v,
157 prec = (precision as i32 - 1 - exp).max(0) as usize
158 );
159 if fixed.contains('.') {
161 let trimmed = fixed.trim_end_matches('0').trim_end_matches('.');
162 return trimmed.to_string();
163 }
164 return fixed;
165 }
166 }
167 format!("{:.*e}", precision.saturating_sub(1), v)
168}
169
170fn format_float_f32(v: f32) -> String {
172 for prec in 6usize..=9 {
175 let s = snprintf_g(v as f64, prec);
176 if let Ok(reparsed) = s.trim().parse::<f32>() {
177 if reparsed == v {
178 return s;
179 }
180 }
181 }
182 snprintf_g(v as f64, 9)
183}
184
185fn format_float_f64(v: f64) -> String {
188 for prec in 15usize..=17 {
189 let s = snprintf_g(v, prec);
190 if let Ok(reparsed) = s.trim().parse::<f64>() {
191 if reparsed.to_bits() == v.to_bits() {
192 return s;
193 }
194 }
195 }
196 snprintf_g(v, 17)
197}
198
199#[inline]
201fn read_u16(bytes: &[u8], endian: Endian) -> u16 {
202 let arr: [u8; 2] = bytes[..2].try_into().unwrap();
203 match endian {
204 Endian::Big => u16::from_be_bytes(arr),
205 Endian::Little | Endian::Native => u16::from_le_bytes(arr),
206 }
207}
208
209#[inline]
211fn read_u32(bytes: &[u8], endian: Endian) -> u32 {
212 let arr: [u8; 4] = bytes[..4].try_into().unwrap();
213 match endian {
214 Endian::Big => u32::from_be_bytes(arr),
215 Endian::Little | Endian::Native => u32::from_le_bytes(arr),
216 }
217}
218
219#[inline]
221fn read_u64(bytes: &[u8], endian: Endian) -> u64 {
222 let arr: [u8; 8] = bytes[..8].try_into().unwrap();
223 match endian {
224 Endian::Big => u64::from_be_bytes(arr),
225 Endian::Little | Endian::Native => u64::from_le_bytes(arr),
226 }
227}
228
229static HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
231
232#[inline]
235fn fmt_octal(mut v: u64, buf: &mut [u8], digits: usize) -> usize {
236 let mut i = digits;
237 while i > 0 {
238 i -= 1;
239 buf[i] = b'0' + (v & 7) as u8;
240 v >>= 3;
241 }
242 digits
243}
244
245#[inline]
248fn fmt_hex(mut v: u64, buf: &mut [u8], digits: usize) -> usize {
249 let mut i = digits;
250 while i > 0 {
251 i -= 1;
252 buf[i] = HEX_DIGITS[(v & 0xF) as usize];
253 v >>= 4;
254 }
255 digits
256}
257
258#[inline]
261fn fmt_unsigned(mut v: u64, buf: &mut [u8]) -> usize {
262 if v == 0 {
263 buf[0] = b'0';
264 return 1;
265 }
266 let mut i = 0;
267 while v > 0 {
268 buf[i] = b'0' + (v % 10) as u8;
269 v /= 10;
270 i += 1;
271 }
272 buf[..i].reverse();
274 i
275}
276
277#[inline]
280fn fmt_signed(v: i64, buf: &mut [u8]) -> usize {
281 if v >= 0 {
282 return fmt_unsigned(v as u64, buf);
283 }
284 buf[0] = b'-';
285 let len = fmt_unsigned((-(v as i128)) as u64, &mut buf[1..]);
286 1 + len
287}
288
289#[inline]
292fn write_padded(
293 out: &mut impl Write,
294 value_buf: &[u8],
295 value_len: usize,
296 width: usize,
297) -> io::Result<()> {
298 const SPACES: [u8; 32] = [b' '; 32];
299 let pad = width.saturating_sub(value_len);
300 let mut remaining = pad;
301 while remaining > 0 {
302 let chunk = remaining.min(SPACES.len());
303 out.write_all(&SPACES[..chunk])?;
304 remaining -= chunk;
305 }
306 out.write_all(&value_buf[..value_len])
307}
308
309#[inline]
311fn write_value(
312 out: &mut impl Write,
313 bytes: &[u8],
314 fmt: OutputFormat,
315 width: usize,
316 endian: Endian,
317) -> io::Result<()> {
318 let mut buf = [0u8; 24]; match fmt {
320 OutputFormat::NamedChar => {
321 let b = bytes[0];
322 if b < 128 {
323 let s = NAMED_CHARS[b as usize].as_bytes();
324 write_padded(out, s, s.len(), width)
325 } else {
326 let len = fmt_octal(b as u64, &mut buf, 3);
327 write_padded(out, &buf, len, width)
328 }
329 }
330 OutputFormat::PrintableChar => {
331 let b = bytes[0];
332 let s: &[u8] = match b {
333 0x00 => b"\\0",
334 0x07 => b"\\a",
335 0x08 => b"\\b",
336 0x09 => b"\\t",
337 0x0a => b"\\n",
338 0x0b => b"\\v",
339 0x0c => b"\\f",
340 0x0d => b"\\r",
341 _ => b"",
342 };
343 if !s.is_empty() {
344 write_padded(out, s, s.len(), width)
345 } else if (0x20..=0x7e).contains(&b) {
346 buf[0] = b;
347 write_padded(out, &buf, 1, width)
348 } else {
349 buf[0] = b'0' + (b >> 6);
350 buf[1] = b'0' + ((b >> 3) & 7);
351 buf[2] = b'0' + (b & 7);
352 write_padded(out, &buf, 3, width)
353 }
354 }
355 OutputFormat::Octal(size) => {
356 let (v, digits) = match size {
357 1 => (bytes[0] as u64, 3),
358 2 => (read_u16(bytes, endian) as u64, 6),
359 4 => (read_u32(bytes, endian) as u64, 11),
360 8 => (read_u64(bytes, endian), 22),
361 _ => return Ok(()),
362 };
363 let len = fmt_octal(v, &mut buf, digits);
364 write_padded(out, &buf, len, width)
365 }
366 OutputFormat::Hex(size) => {
367 let (v, digits) = match size {
368 1 => (bytes[0] as u64, 2),
369 2 => (read_u16(bytes, endian) as u64, 4),
370 4 => (read_u32(bytes, endian) as u64, 8),
371 8 => (read_u64(bytes, endian), 16),
372 _ => return Ok(()),
373 };
374 let len = fmt_hex(v, &mut buf, digits);
375 write_padded(out, &buf, len, width)
376 }
377 OutputFormat::UnsignedDec(size) => {
378 let v = match size {
379 1 => bytes[0] as u64,
380 2 => read_u16(bytes, endian) as u64,
381 4 => read_u32(bytes, endian) as u64,
382 8 => read_u64(bytes, endian),
383 _ => return Ok(()),
384 };
385 let len = fmt_unsigned(v, &mut buf);
386 write_padded(out, &buf, len, width)
387 }
388 OutputFormat::SignedDec(size) => {
389 let v: i64 = match size {
390 1 => bytes[0] as i8 as i64,
391 2 => read_u16(bytes, endian) as i16 as i64,
392 4 => read_u32(bytes, endian) as i32 as i64,
393 8 => read_u64(bytes, endian) as i64,
394 _ => return Ok(()),
395 };
396 let len = fmt_signed(v, &mut buf);
397 write_padded(out, &buf, len, width)
398 }
399 OutputFormat::Float(size) => match size {
400 4 => {
401 let v = f32::from_bits(read_u32(bytes, endian));
402 write!(out, "{:>w$}", format_float_f32(v), w = width)
403 }
404 8 => {
405 let v = f64::from_bits(read_u64(bytes, endian));
406 write!(out, "{:>w$}", format_float_f64(v), w = width)
407 }
408 _ => Ok(()),
409 },
410 }
411}
412
413fn compute_effective_widths(formats: &[OutputFormat], line_width: usize) -> Vec<usize> {
417 if formats.len() <= 1 {
418 return formats.iter().map(|f| field_width(*f)).collect();
419 }
420
421 let mut max_chars_per_block = 0usize;
422 for fmt in formats {
423 let es = element_size(*fmt);
424 let fw = field_width(*fmt);
425 let num_elems = line_width / es;
426 let chars = num_elems * fw;
427 if chars > max_chars_per_block {
428 max_chars_per_block = chars;
429 }
430 }
431
432 formats
434 .iter()
435 .map(|fmt| {
436 let es = element_size(*fmt);
437 let num_elems = line_width / es;
438 if num_elems > 0 {
439 max_chars_per_block / num_elems
440 } else {
441 field_width(*fmt)
442 }
443 })
444 .collect()
445}
446
447fn write_format_line(
449 out: &mut impl Write,
450 chunk: &[u8],
451 fmt: OutputFormat,
452 line_width: usize,
453 is_first_format: bool,
454 radix: AddressRadix,
455 offset: u64,
456 z_annotate: bool,
457 effective_fw: usize,
458 endian: Endian,
459) -> io::Result<()> {
460 if is_first_format {
462 let mut addr_buf = [0u8; 22];
463 match radix {
464 AddressRadix::Octal => {
465 let len = fmt_octal(offset, &mut addr_buf, 7);
466 out.write_all(&addr_buf[..len])?;
467 }
468 AddressRadix::Decimal => {
469 let mut tmp = [0u8; 20];
470 let vlen = fmt_unsigned(offset, &mut tmp);
471 let pad = 7usize.saturating_sub(vlen);
473 for b in addr_buf.iter_mut().take(pad) {
474 *b = b'0';
475 }
476 addr_buf[pad..pad + vlen].copy_from_slice(&tmp[..vlen]);
477 out.write_all(&addr_buf[..pad + vlen])?;
478 }
479 AddressRadix::Hex => {
480 let len = fmt_hex(offset, &mut addr_buf, 6);
481 out.write_all(&addr_buf[..len])?;
482 }
483 AddressRadix::None => {}
484 }
485 } else if radix != AddressRadix::None {
486 let addr_width = match radix {
487 AddressRadix::Octal | AddressRadix::Decimal => 7,
488 AddressRadix::Hex => 6,
489 AddressRadix::None => 0,
490 };
491 for _ in 0..addr_width {
492 out.write_all(b" ")?;
493 }
494 }
495
496 let elem_sz = element_size(fmt);
497 let fw = effective_fw;
498 let num_elems = line_width / elem_sz;
499 let actual_full = chunk.len() / elem_sz;
500 let remainder = chunk.len() % elem_sz;
501
502 for i in 0..num_elems {
503 if i < actual_full {
504 let start = i * elem_sz;
505 let end = start + elem_sz;
506 write_value(out, &chunk[start..end], fmt, fw, endian)?;
507 } else if i == actual_full && remainder > 0 {
508 let start = i * elem_sz;
509 let mut padded = [0u8; 8]; padded[..remainder].copy_from_slice(&chunk[start..]);
511 write_value(out, &padded[..elem_sz], fmt, fw, endian)?;
512 }
513 }
514
515 if z_annotate {
517 let used_cols = actual_full + if remainder > 0 { 1 } else { 0 };
519 for _ in used_cols..num_elems {
520 for _ in 0..fw {
521 out.write_all(b" ")?;
522 }
523 }
524 out.write_all(b" >")?;
525 for &b in chunk {
526 if b.is_ascii_graphic() || b == b' ' {
527 out.write_all(&[b])?;
528 } else {
529 out.write_all(b".")?;
530 }
531 }
532 out.write_all(b"<")?;
533 }
534
535 writeln!(out)?;
536 Ok(())
537}
538
539pub fn parse_format_type(s: &str) -> Result<(OutputFormat, bool), String> {
542 if s.is_empty() {
543 return Err("empty format string".to_string());
544 }
545
546 let (s, z_annotate) = if s.len() > 1 && s.ends_with('z') {
548 (&s[..s.len() - 1], true)
549 } else {
550 (s, false)
551 };
552
553 let mut chars = s.chars();
554 let type_char = chars.next().unwrap();
555 let size_str: String = chars.collect();
556
557 let fmt = match type_char {
558 'a' => Ok(OutputFormat::NamedChar),
559 'c' => Ok(OutputFormat::PrintableChar),
560 'd' => {
561 let size = if size_str.is_empty() {
562 4
563 } else {
564 parse_size_spec(&size_str, "d")?
565 };
566 Ok(OutputFormat::SignedDec(size))
567 }
568 'f' => {
569 let size = if size_str.is_empty() {
570 4
571 } else {
572 parse_float_size(&size_str)?
573 };
574 Ok(OutputFormat::Float(size))
575 }
576 'o' => {
577 let size = if size_str.is_empty() {
578 2
579 } else {
580 parse_size_spec(&size_str, "o")?
581 };
582 Ok(OutputFormat::Octal(size))
583 }
584 'u' => {
585 let size = if size_str.is_empty() {
586 4
587 } else {
588 parse_size_spec(&size_str, "u")?
589 };
590 Ok(OutputFormat::UnsignedDec(size))
591 }
592 'x' => {
593 let size = if size_str.is_empty() {
594 2
595 } else {
596 parse_size_spec(&size_str, "x")?
597 };
598 Ok(OutputFormat::Hex(size))
599 }
600 _ => Err(format!("invalid type string '{}'", s)),
601 }?;
602 Ok((fmt, z_annotate))
603}
604
605fn parse_size_spec(s: &str, type_name: &str) -> Result<usize, String> {
606 match s {
608 "C" => Ok(1),
609 "S" => Ok(2),
610 "I" => Ok(4),
611 "L" => Ok(8),
612 _ => {
613 let n: usize = s
614 .parse()
615 .map_err(|_| format!("invalid type string '{}{}': invalid size", type_name, s))?;
616 match n {
617 1 | 2 | 4 | 8 => Ok(n),
618 _ => Err(format!(
619 "invalid type string '{}{}': invalid size",
620 type_name, s
621 )),
622 }
623 }
624 }
625}
626
627fn parse_float_size(s: &str) -> Result<usize, String> {
628 match s {
629 "F" | "4" => Ok(4),
630 "D" | "8" => Ok(8),
631 "L" | "16" => Err("16-byte float not supported".to_string()),
632 _ => {
633 let n: usize = s
634 .parse()
635 .map_err(|_| format!("invalid float size '{}'", s))?;
636 match n {
637 4 | 8 => Ok(n),
638 _ => Err(format!("invalid float size '{}'", s)),
639 }
640 }
641 }
642}
643
644pub fn od_process<R: Read, W: Write>(
646 mut input: R,
647 output: &mut W,
648 config: &OdConfig,
649) -> io::Result<()> {
650 if config.skip_bytes > 0 {
652 let mut to_skip = config.skip_bytes;
653 let mut skip_buf = [0u8; 8192];
654 while to_skip > 0 {
655 let chunk_size = std::cmp::min(to_skip, skip_buf.len() as u64) as usize;
656 let n = input.read(&mut skip_buf[..chunk_size])?;
657 if n == 0 {
658 break;
659 }
660 to_skip -= n as u64;
661 }
662 }
663
664 let data = match config.read_bytes {
666 Some(limit) => {
667 let mut buf = Vec::new();
668 let mut limited = input.take(limit);
669 limited.read_to_end(&mut buf)?;
670 buf
671 }
672 None => {
673 let mut buf = Vec::new();
674 input.read_to_end(&mut buf)?;
675 buf
676 }
677 };
678
679 let width = config.width;
680 let mut offset = config.skip_bytes;
681 let mut prev_chunk: Option<Vec<u8>> = None;
682 let mut star_printed = false;
683
684 let effective_widths = compute_effective_widths(&config.formats, width);
686
687 let mut pos = 0;
688 while pos < data.len() {
689 let end = std::cmp::min(pos + width, data.len());
690 let chunk = &data[pos..end];
691
692 if !config.show_duplicates && chunk.len() == width {
694 if let Some(ref prev) = prev_chunk {
695 if prev.as_slice() == chunk {
696 if !star_printed {
697 writeln!(output, "*")?;
698 star_printed = true;
699 }
700 pos += width;
701 offset += width as u64;
702 continue;
703 }
704 }
705 }
706
707 star_printed = false;
708
709 for (i, fmt) in config.formats.iter().enumerate() {
710 let z = config.z_flags.get(i).copied().unwrap_or(false);
711 let ew = effective_widths[i];
712 write_format_line(
713 output,
714 chunk,
715 *fmt,
716 width,
717 i == 0,
718 config.address_radix,
719 offset,
720 z,
721 ew,
722 config.endian,
723 )?;
724 }
725
726 prev_chunk = Some(chunk.to_vec());
727 pos += width;
728 offset += width as u64;
729 }
730
731 if config.address_radix != AddressRadix::None {
733 let final_offset = config.skip_bytes + data.len() as u64;
734 match config.address_radix {
735 AddressRadix::Octal => writeln!(output, "{:07o}", final_offset)?,
736 AddressRadix::Decimal => writeln!(output, "{:07}", final_offset)?,
737 AddressRadix::Hex => writeln!(output, "{:06x}", final_offset)?,
738 AddressRadix::None => {}
739 }
740 }
741
742 Ok(())
743}