1use std::fs::{File, OpenOptions};
2use std::io::{self, Read, Seek, SeekFrom, Write};
3use std::time::Instant;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum StatusLevel {
8 #[default]
10 Default,
11 None,
13 Progress,
15 NoError,
17 NoXfer,
19}
20
21#[derive(Debug, Clone, Default)]
23pub struct DdConv {
24 pub lcase: bool,
26 pub ucase: bool,
28 pub swab: bool,
30 pub noerror: bool,
32 pub notrunc: bool,
34 pub sync: bool,
36 pub fdatasync: bool,
38 pub fsync: bool,
40 pub excl: bool,
42 pub nocreat: bool,
44 pub unblock: bool,
46 pub block: bool,
48}
49
50#[derive(Debug, Clone, Default)]
52pub struct DdFlags {
53 pub append: bool,
54 pub direct: bool,
55 pub directory: bool,
56 pub dsync: bool,
57 pub sync: bool,
58 pub fullblock: bool,
59 pub nonblock: bool,
60 pub noatime: bool,
61 pub nocache: bool,
62 pub noctty: bool,
63 pub nofollow: bool,
64 pub count_bytes: bool,
65 pub skip_bytes: bool,
66}
67
68#[derive(Debug, Clone)]
70pub struct DdConfig {
71 pub input: Option<String>,
73 pub output: Option<String>,
75 pub ibs: usize,
77 pub obs: usize,
79 pub cbs: usize,
81 pub count: Option<u64>,
83 pub skip: u64,
85 pub seek: u64,
87 pub conv: DdConv,
89 pub status: StatusLevel,
91 pub iflag: DdFlags,
93 pub oflag: DdFlags,
95}
96
97impl Default for DdConfig {
98 fn default() -> Self {
99 DdConfig {
100 input: None,
101 output: None,
102 ibs: 512,
103 obs: 512,
104 cbs: 0,
105 count: None,
106 skip: 0,
107 seek: 0,
108 conv: DdConv::default(),
109 status: StatusLevel::default(),
110 iflag: DdFlags::default(),
111 oflag: DdFlags::default(),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct DdStats {
119 pub records_in_full: u64,
121 pub records_in_partial: u64,
123 pub records_out_full: u64,
125 pub records_out_partial: u64,
127 pub bytes_copied: u64,
129}
130
131pub fn parse_size(s: &str) -> Result<u64, String> {
142 let s = s.trim();
143 if s.is_empty() {
144 return Err("empty size string".to_string());
145 }
146
147 if let Some(pos) = s.find('x') {
150 let left = parse_size_single(&s[..pos])?;
151 let right = parse_size(&s[pos + 1..])?;
152 return left
153 .checked_mul(right)
154 .ok_or_else(|| format!("size overflow: {} * {}", left, right));
155 }
156
157 parse_size_single(s)
158}
159
160fn parse_size_single(s: &str) -> Result<u64, String> {
161 if s.is_empty() {
162 return Err("empty size string".to_string());
163 }
164
165 let num_end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
167
168 if num_end == 0 {
169 return Err(format!("invalid number: '{}'", s));
170 }
171
172 let num: u64 = s[..num_end]
173 .parse()
174 .map_err(|e| format!("invalid number '{}': {}", &s[..num_end], e))?;
175
176 let suffix = &s[num_end..];
177 let multiplier: u64 = match suffix {
180 "" => 1,
181 "c" => 1,
182 "w" => 2,
183 "b" => 512,
184 "k" | "K" => 1024,
185 "kB" | "KB" => 1000,
186 "KiB" => 1024,
187 "M" => 1_048_576,
188 "MB" => 1_000_000,
189 "MiB" => 1_048_576,
190 "G" => 1_073_741_824,
191 "GB" => 1_000_000_000,
192 "GiB" => 1_073_741_824,
193 "T" => 1_099_511_627_776,
194 "TB" => 1_000_000_000_000,
195 "TiB" => 1_099_511_627_776,
196 "P" => 1_125_899_906_842_624,
197 "PB" => 1_000_000_000_000_000,
198 "PiB" => 1_125_899_906_842_624,
199 "E" => 1_152_921_504_606_846_976,
200 "EB" => 1_000_000_000_000_000_000,
201 "EiB" => 1_152_921_504_606_846_976,
202 _ => return Err(format!("invalid suffix: '{}'", suffix)),
203 };
204
205 num.checked_mul(multiplier)
206 .ok_or_else(|| format!("size overflow: {} * {}", num, multiplier))
207}
208
209pub fn parse_dd_args(args: &[String]) -> Result<DdConfig, String> {
211 let mut config = DdConfig::default();
212 let mut bs_set = false;
213
214 for arg in args {
215 if let Some((key, value)) = arg.split_once('=') {
216 match key {
217 "if" => config.input = Some(value.to_string()),
218 "of" => config.output = Some(value.to_string()),
219 "bs" => {
220 let size = parse_size(value)? as usize;
221 config.ibs = size;
222 config.obs = size;
223 bs_set = true;
224 }
225 "ibs" => {
226 if !bs_set {
227 config.ibs = parse_size(value)? as usize;
228 }
229 }
230 "obs" => {
231 if !bs_set {
232 config.obs = parse_size(value)? as usize;
233 }
234 }
235 "cbs" => config.cbs = parse_size(value)? as usize,
236 "count" => config.count = Some(parse_size(value)?),
237 "skip" => config.skip = parse_size(value)?,
238 "seek" => config.seek = parse_size(value)?,
239 "conv" => {
240 for flag in value.split(',') {
241 match flag {
242 "lcase" => config.conv.lcase = true,
243 "ucase" => config.conv.ucase = true,
244 "swab" => config.conv.swab = true,
245 "noerror" => config.conv.noerror = true,
246 "notrunc" => config.conv.notrunc = true,
247 "sync" => config.conv.sync = true,
248 "fdatasync" => config.conv.fdatasync = true,
249 "fsync" => config.conv.fsync = true,
250 "excl" => config.conv.excl = true,
251 "nocreat" => config.conv.nocreat = true,
252 "block" => config.conv.block = true,
253 "unblock" => config.conv.unblock = true,
254 "" => {}
255 _ => return Err(format!("invalid conversion: '{}'", flag)),
256 }
257 }
258 }
259 "iflag" => {
260 for flag in value.split(',') {
261 parse_flag(flag, &mut config.iflag)?;
262 }
263 }
264 "oflag" => {
265 for flag in value.split(',') {
266 parse_flag(flag, &mut config.oflag)?;
267 }
268 }
269 "status" => {
270 config.status = match value {
271 "none" => StatusLevel::None,
272 "noxfer" => StatusLevel::NoXfer,
273 "noerror" => StatusLevel::NoError,
274 "progress" => StatusLevel::Progress,
275 _ => return Err(format!("invalid status level: '{}'", value)),
276 };
277 }
278 _ => return Err(format!("unrecognized operand: '{}'", arg)),
279 }
280 } else {
281 return Err(format!("unrecognized operand: '{}'", arg));
282 }
283 }
284
285 if config.conv.lcase && config.conv.ucase {
287 return Err("conv=lcase and conv=ucase are mutually exclusive".to_string());
288 }
289 if config.conv.excl && config.conv.nocreat {
290 return Err("conv=excl and conv=nocreat are mutually exclusive".to_string());
291 }
292
293 Ok(config)
294}
295
296fn parse_flag(flag: &str, flags: &mut DdFlags) -> Result<(), String> {
298 match flag {
299 "append" => flags.append = true,
300 "direct" => flags.direct = true,
301 "directory" => flags.directory = true,
302 "dsync" => flags.dsync = true,
303 "sync" => flags.sync = true,
304 "fullblock" => flags.fullblock = true,
305 "nonblock" => flags.nonblock = true,
306 "noatime" => flags.noatime = true,
307 "nocache" => flags.nocache = true,
308 "noctty" => flags.noctty = true,
309 "nofollow" => flags.nofollow = true,
310 "count_bytes" => flags.count_bytes = true,
311 "skip_bytes" => flags.skip_bytes = true,
312 "" => {}
313 _ => return Err(format!("invalid flag: '{}'", flag)),
314 }
315 Ok(())
316}
317
318fn read_full_block(reader: &mut dyn Read, buf: &mut [u8]) -> io::Result<usize> {
321 let mut total = 0;
322 while total < buf.len() {
323 match reader.read(&mut buf[total..]) {
324 Ok(0) => break,
325 Ok(n) => total += n,
326 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
327 Err(e) => return Err(e),
328 }
329 }
330 Ok(total)
331}
332
333pub fn apply_conversions(data: &mut [u8], conv: &DdConv) {
335 if conv.swab {
336 let (prefix, chunks, suffix) = unsafe { data.align_to_mut::<u64>() };
339 let pairs_pre = prefix.len() / 2;
341 for i in 0..pairs_pre {
342 prefix.swap(i * 2, i * 2 + 1);
343 }
344 for w in chunks.iter_mut() {
348 let x = *w;
349 *w = ((x & 0xFF00FF00FF00FF00) >> 8) | ((x & 0x00FF00FF00FF00FF) << 8);
350 }
351 let pairs_suf = suffix.len() / 2;
353 for i in 0..pairs_suf {
354 suffix.swap(i * 2, i * 2 + 1);
355 }
356 }
357
358 if conv.lcase {
359 for b in data.iter_mut() {
360 b.make_ascii_lowercase();
361 }
362 } else if conv.ucase {
363 for b in data.iter_mut() {
364 b.make_ascii_uppercase();
365 }
366 }
367}
368
369fn skip_input(reader: &mut dyn Read, blocks: u64, block_size: usize) -> io::Result<()> {
371 let mut discard_buf = vec![0u8; block_size];
372 for _ in 0..blocks {
373 let n = read_full_block(reader, &mut discard_buf)?;
374 if n == 0 {
375 break;
376 }
377 }
378 Ok(())
379}
380
381fn skip_input_bytes(reader: &mut dyn Read, bytes: u64) -> io::Result<()> {
383 let mut remaining = bytes;
384 let mut discard_buf = [0u8; 8192];
385 while remaining > 0 {
386 let chunk = std::cmp::min(remaining, discard_buf.len() as u64) as usize;
387 let n = reader.read(&mut discard_buf[..chunk])?;
388 if n == 0 {
389 break;
390 }
391 remaining -= n as u64;
392 }
393 Ok(())
394}
395
396fn skip_input_seek(file: &mut File, blocks: u64, block_size: usize) -> io::Result<()> {
398 let offset = blocks * block_size as u64;
399 file.seek(SeekFrom::Start(offset))?;
400 Ok(())
401}
402
403fn seek_output(writer: &mut Box<dyn Write>, seek_blocks: u64, block_size: usize) -> io::Result<()> {
405 let zero_block = vec![0u8; block_size];
408 for _ in 0..seek_blocks {
409 writer.write_all(&zero_block)?;
410 }
411 Ok(())
412}
413
414fn seek_output_file(file: &mut File, seek_blocks: u64, block_size: usize) -> io::Result<()> {
416 let offset = seek_blocks * block_size as u64;
417 file.seek(SeekFrom::Start(offset))?;
418 Ok(())
419}
420
421#[cfg(target_os = "linux")]
423fn has_conversions(conv: &DdConv) -> bool {
424 conv.lcase || conv.ucase || conv.swab || conv.sync || conv.block || conv.unblock
425}
426
427#[cfg(target_os = "linux")]
431fn has_flags(flags: &DdFlags) -> bool {
432 flags.append
433 || flags.direct
434 || flags.directory
435 || flags.dsync
436 || flags.sync
437 || flags.nonblock
438 || flags.nocache
439 || flags.noctty
440 || flags.nofollow
441 || flags.count_bytes
442 || flags.skip_bytes
443}
444
445#[cfg(target_os = "linux")]
450fn try_raw_dd(config: &DdConfig) -> Option<io::Result<DdStats>> {
451 if config.input.is_none() || config.output.is_none() {
452 return None;
453 }
454 if has_conversions(&config.conv) || config.ibs != config.obs {
455 return None;
456 }
457 if has_flags(&config.iflag) || has_flags(&config.oflag) {
459 return None;
460 }
461
462 let start_time = Instant::now();
463 let in_path = config.input.as_ref().unwrap();
464 let out_path = config.output.as_ref().unwrap();
465
466 let in_cstr = match std::ffi::CString::new(in_path.as_str()) {
468 Ok(c) => c,
469 Err(_) => {
470 return Some(Err(io::Error::new(
471 io::ErrorKind::InvalidInput,
472 format!("input path contains NUL byte: '{}'", in_path),
473 )));
474 }
475 };
476 let out_cstr = match std::ffi::CString::new(out_path.as_str()) {
477 Ok(c) => c,
478 Err(_) => {
479 return Some(Err(io::Error::new(
480 io::ErrorKind::InvalidInput,
481 format!("output path contains NUL byte: '{}'", out_path),
482 )));
483 }
484 };
485
486 let in_fd = unsafe {
489 libc::open(
490 in_cstr.as_ptr(),
491 libc::O_RDONLY | libc::O_CLOEXEC | libc::O_NOATIME,
492 )
493 };
494 let in_fd = if in_fd < 0 {
495 let first_err = io::Error::last_os_error();
496 if first_err.raw_os_error() == Some(libc::EPERM) {
497 let fd = unsafe { libc::open(in_cstr.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
498 if fd < 0 {
499 return Some(Err(io::Error::last_os_error()));
500 }
501 fd
502 } else {
503 return Some(Err(first_err));
504 }
505 } else {
506 in_fd
507 };
508
509 let mut oflags = libc::O_WRONLY | libc::O_CLOEXEC;
511 if config.conv.excl {
512 oflags |= libc::O_CREAT | libc::O_EXCL;
513 } else if config.conv.nocreat {
514 } else {
516 oflags |= libc::O_CREAT;
517 }
518 if !config.conv.notrunc && !config.conv.excl {
519 oflags |= libc::O_TRUNC;
520 }
521
522 let out_fd = unsafe { libc::open(out_cstr.as_ptr(), oflags, 0o666 as libc::mode_t) };
523 if out_fd < 0 {
524 unsafe { libc::close(in_fd) };
525 return Some(Err(io::Error::last_os_error()));
526 }
527
528 {
531 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
532 if unsafe { libc::fstat(in_fd, &mut stat) } == 0
533 && (stat.st_mode & libc::S_IFMT) == libc::S_IFREG
534 {
535 unsafe {
536 libc::posix_fadvise(in_fd, 0, 0, libc::POSIX_FADV_SEQUENTIAL);
537 }
538 }
539 }
540
541 if config.skip > 0 {
543 let offset = match (config.skip as u64).checked_mul(config.ibs as u64) {
544 Some(o) if o <= i64::MAX as u64 => o as i64,
545 _ => {
546 unsafe {
547 libc::close(in_fd);
548 libc::close(out_fd);
549 }
550 return Some(Err(io::Error::new(
551 io::ErrorKind::InvalidInput,
552 "skip offset overflow",
553 )));
554 }
555 };
556 if unsafe { libc::lseek(in_fd, offset, libc::SEEK_SET) } < 0 {
557 let mut discard = vec![0u8; config.ibs];
559 'skip: for _ in 0..config.skip {
560 let mut skipped = 0usize;
561 while skipped < config.ibs {
562 let n = unsafe {
563 libc::read(
564 in_fd,
565 discard[skipped..].as_mut_ptr() as *mut _,
566 config.ibs - skipped,
567 )
568 };
569 if n > 0 {
570 skipped += n as usize;
571 } else if n == 0 {
572 break 'skip; } else {
574 let err = io::Error::last_os_error();
575 if err.kind() == io::ErrorKind::Interrupted {
576 continue;
577 }
578 eprintln!("dd: error skipping input: {}", err);
580 break 'skip;
581 }
582 }
583 }
584 }
585 }
586
587 if config.seek > 0 {
589 let offset = match (config.seek as u64).checked_mul(config.obs as u64) {
590 Some(o) if o <= i64::MAX as u64 => o as i64,
591 _ => {
592 unsafe {
593 libc::close(in_fd);
594 libc::close(out_fd);
595 }
596 return Some(Err(io::Error::new(
597 io::ErrorKind::InvalidInput,
598 "seek offset overflow",
599 )));
600 }
601 };
602 if unsafe { libc::lseek(out_fd, offset, libc::SEEK_SET) } < 0 {
603 let err = io::Error::last_os_error();
604 unsafe {
605 libc::close(in_fd);
606 libc::close(out_fd);
607 }
608 return Some(Err(err));
609 }
610 }
611
612 let mut stats = DdStats::default();
613 let bs = config.ibs;
614 #[allow(clippy::uninit_vec)]
615 let mut ibuf = unsafe {
619 let mut v = Vec::with_capacity(bs);
620 v.set_len(bs);
621 v
622 };
623 debug_assert_eq!(ibuf.len(), bs);
624 let count_limit = config.count;
625
626 loop {
627 if let Some(limit) = count_limit {
628 if stats.records_in_full + stats.records_in_partial >= limit {
629 break;
630 }
631 }
632
633 let mut total_read = 0usize;
635 let mut read_error = false;
636 while total_read < bs {
637 let ret = unsafe {
638 libc::read(
639 in_fd,
640 ibuf[total_read..].as_mut_ptr() as *mut _,
641 bs - total_read,
642 )
643 };
644 if ret > 0 {
645 total_read += ret as usize;
646 } else if ret == 0 {
647 break; } else {
649 let err = io::Error::last_os_error();
650 if err.kind() == io::ErrorKind::Interrupted {
651 continue;
652 }
653 if config.conv.noerror {
654 eprintln!("dd: error reading '{}': {}", in_path, err);
655 read_error = true;
656 break;
657 }
658 unsafe {
659 libc::close(in_fd);
660 libc::close(out_fd);
661 }
662 return Some(Err(err));
663 }
664 }
665
666 if read_error {
668 stats.records_in_partial += 1;
669 continue;
670 }
671
672 if total_read == 0 {
673 break;
674 }
675
676 if total_read == bs {
677 stats.records_in_full += 1;
678 } else {
679 stats.records_in_partial += 1;
680 }
681
682 let mut written = 0usize;
684 debug_assert!(total_read <= ibuf.len());
685 while written < total_read {
686 debug_assert!(written <= ibuf.len());
687 let ret = unsafe {
688 libc::write(
689 out_fd,
690 ibuf[written..].as_ptr() as *const _,
691 total_read - written,
692 )
693 };
694 if ret > 0 {
695 written += ret as usize;
696 } else if ret == 0 {
697 unsafe {
699 libc::close(in_fd);
700 libc::close(out_fd);
701 }
702 return Some(Err(io::Error::new(
703 io::ErrorKind::WriteZero,
704 "write returned 0",
705 )));
706 } else {
707 let err = io::Error::last_os_error();
708 if err.kind() == io::ErrorKind::Interrupted {
709 continue;
710 }
711 unsafe {
712 libc::close(in_fd);
713 libc::close(out_fd);
714 }
715 return Some(Err(err));
716 }
717 }
718
719 stats.bytes_copied += written as u64;
720 if written == bs {
721 stats.records_out_full += 1;
722 } else {
723 stats.records_out_partial += 1;
724 }
725 }
726
727 if config.conv.fsync {
729 if unsafe { libc::fsync(out_fd) } < 0 {
730 let err = io::Error::last_os_error();
731 unsafe {
732 libc::close(in_fd);
733 libc::close(out_fd);
734 }
735 return Some(Err(err));
736 }
737 } else if config.conv.fdatasync {
738 if unsafe { libc::fdatasync(out_fd) } < 0 {
739 let err = io::Error::last_os_error();
740 unsafe {
741 libc::close(in_fd);
742 libc::close(out_fd);
743 }
744 return Some(Err(err));
745 }
746 }
747
748 unsafe { libc::close(in_fd) };
749 if unsafe { libc::close(out_fd) } < 0 {
751 return Some(Err(io::Error::last_os_error()));
752 }
753
754 if config.status != StatusLevel::None {
755 print_stats(&stats, start_time.elapsed(), config.status);
756 }
757
758 Some(Ok(stats))
759}
760
761#[cfg(target_os = "linux")]
764fn try_copy_file_range_dd(config: &DdConfig) -> Option<io::Result<DdStats>> {
765 if config.input.is_none() || config.output.is_none() {
767 return None;
768 }
769 if has_conversions(&config.conv) || config.ibs != config.obs {
770 return None;
771 }
772
773 let start_time = Instant::now();
774 let in_path = config.input.as_ref().unwrap();
775 let out_path = config.output.as_ref().unwrap();
776
777 let in_file = match File::open(in_path) {
778 Ok(f) => f,
779 Err(e) => return Some(Err(e)),
780 };
781
782 {
784 use std::os::unix::io::AsRawFd;
785 unsafe {
786 libc::posix_fadvise(in_file.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL);
787 }
788 }
789
790 let mut out_opts = OpenOptions::new();
791 out_opts.write(true);
792 if config.conv.excl {
793 out_opts.create_new(true);
794 } else if !config.conv.nocreat {
795 out_opts.create(true);
796 }
797 if !config.conv.notrunc && !config.conv.excl {
798 out_opts.truncate(true);
799 }
800
801 let out_file = match out_opts.open(out_path) {
802 Ok(f) => f,
803 Err(e) => return Some(Err(e)),
804 };
805
806 use std::os::unix::io::AsRawFd;
807 let in_fd = in_file.as_raw_fd();
808 let out_fd = out_file.as_raw_fd();
809
810 let skip_bytes = config.skip * config.ibs as u64;
812 let seek_bytes = config.seek * config.obs as u64;
813 let mut in_off: i64 = skip_bytes as i64;
814 let mut out_off: i64 = seek_bytes as i64;
815
816 let mut stats = DdStats::default();
817 let block_size = config.ibs;
818
819 let total_to_copy = config.count.map(|count| count * block_size as u64);
821
822 let mut bytes_remaining = total_to_copy;
823 loop {
824 let chunk = match bytes_remaining {
825 Some(0) => break,
826 Some(r) => r.min(block_size as u64 * 1024) as usize, None => block_size * 1024,
828 };
829
830 let ret = unsafe {
835 libc::syscall(
836 libc::SYS_copy_file_range,
837 in_fd,
838 &mut in_off as *mut i64,
839 out_fd,
840 &mut out_off as *mut i64,
841 chunk,
842 0u32,
843 )
844 };
845
846 if ret < 0 {
847 let err = io::Error::last_os_error();
848 if err.raw_os_error() == Some(libc::EINVAL)
849 || err.raw_os_error() == Some(libc::ENOSYS)
850 || err.raw_os_error() == Some(libc::EXDEV)
851 {
852 return None; }
854 return Some(Err(err));
855 }
856 if ret == 0 {
857 break;
858 }
859
860 let copied = ret as u64;
861 stats.bytes_copied += copied;
862
863 let full_blocks = copied / block_size as u64;
865 let partial = copied % block_size as u64;
866 stats.records_in_full += full_blocks;
867 stats.records_out_full += full_blocks;
868 if partial > 0 {
869 stats.records_in_partial += 1;
870 stats.records_out_partial += 1;
871 }
872
873 if let Some(ref mut r) = bytes_remaining {
874 *r = r.saturating_sub(copied);
875 }
876 }
877
878 if config.conv.fsync {
880 if let Err(e) = out_file.sync_all() {
881 return Some(Err(e));
882 }
883 } else if config.conv.fdatasync {
884 if let Err(e) = out_file.sync_data() {
885 return Some(Err(e));
886 }
887 }
888
889 if config.status != StatusLevel::None {
890 print_stats(&stats, start_time.elapsed(), config.status);
891 }
892
893 Some(Ok(stats))
894}
895
896pub fn dd_copy(config: &DdConfig) -> io::Result<DdStats> {
898 #[cfg(target_os = "linux")]
900 {
901 if let Some(result) = try_copy_file_range_dd(config) {
902 return result;
903 }
904 }
905 #[cfg(target_os = "linux")]
907 {
908 if let Some(result) = try_raw_dd(config) {
909 return result;
910 }
911 }
912 let start_time = Instant::now();
913
914 let needs_input_seek = config.skip > 0;
916 let needs_output_seek = config.seek > 0;
917
918 let mut input_file: Option<File> = None;
919 let mut input: Box<dyn Read> = if let Some(ref path) = config.input {
920 let file = File::open(path)
921 .map_err(|e| io::Error::new(e.kind(), format!("failed to open '{}': {}", path, e)))?;
922 #[cfg(target_os = "linux")]
924 {
925 use std::os::unix::io::AsRawFd;
926 unsafe {
927 libc::posix_fadvise(file.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL);
928 }
929 }
930 if needs_input_seek {
931 input_file = Some(file.try_clone()?);
932 }
933 Box::new(file)
934 } else {
935 Box::new(io::stdin())
936 };
937
938 let mut output_file: Option<File> = None;
940 let mut output: Box<dyn Write> = if let Some(ref path) = config.output {
941 let mut opts = OpenOptions::new();
942 opts.write(true);
943
944 if config.conv.excl {
945 opts.create_new(true);
947 } else if config.conv.nocreat {
948 } else {
951 opts.create(true);
952 }
953
954 if config.conv.notrunc {
955 opts.truncate(false);
956 } else if !config.conv.excl {
957 opts.truncate(true);
959 }
960
961 let file = opts
962 .open(path)
963 .map_err(|e| io::Error::new(e.kind(), format!("failed to open '{}': {}", path, e)))?;
964 if needs_output_seek || config.conv.fsync || config.conv.fdatasync {
965 output_file = Some(file.try_clone()?);
969 }
970 Box::new(file)
971 } else {
972 Box::new(io::stdout())
973 };
974
975 if config.skip > 0 {
977 if config.iflag.skip_bytes {
978 if let Some(ref mut f) = input_file {
980 f.seek(SeekFrom::Start(config.skip))?;
981 let seeked = f.try_clone()?;
982 input = Box::new(seeked);
983 } else {
984 skip_input_bytes(&mut input, config.skip)?;
985 }
986 } else if let Some(ref mut f) = input_file {
987 skip_input_seek(f, config.skip, config.ibs)?;
988 let seeked = f.try_clone()?;
990 input = Box::new(seeked);
991 } else {
992 skip_input(&mut input, config.skip, config.ibs)?;
993 }
994 }
995
996 if config.seek > 0 {
998 if let Some(ref mut f) = output_file {
999 seek_output_file(f, config.seek, config.obs)?;
1000 let seeked = f.try_clone()?;
1002 output = Box::new(seeked);
1003 } else {
1004 seek_output(&mut output, config.seek, config.obs)?;
1005 }
1006 }
1007
1008 let mut stats = DdStats::default();
1009 let mut ibuf = vec![0u8; config.ibs];
1010 let mut obuf: Vec<u8> = Vec::with_capacity(config.obs);
1011 let mut unblock_buf: Vec<u8> = Vec::new();
1012 let mut bytes_read_total: u64 = 0;
1014
1015 loop {
1016 if let Some(count) = config.count {
1018 if config.iflag.count_bytes {
1019 if bytes_read_total >= count {
1020 break;
1021 }
1022 } else if stats.records_in_full + stats.records_in_partial >= count {
1023 break;
1024 }
1025 }
1026
1027 let read_size = if config.iflag.count_bytes {
1029 if let Some(count) = config.count {
1030 let remaining = count.saturating_sub(bytes_read_total);
1031 std::cmp::min(config.ibs, remaining as usize)
1032 } else {
1033 config.ibs
1034 }
1035 } else {
1036 config.ibs
1037 };
1038 if read_size == 0 {
1039 break;
1040 }
1041
1042 let n = match read_full_block(&mut input, &mut ibuf[..read_size]) {
1044 Ok(n) => n,
1045 Err(e) => {
1046 if config.conv.noerror {
1047 if config.status != StatusLevel::None {
1048 eprintln!("dd: error reading input: {}", e);
1049 }
1050 if config.conv.sync {
1052 ibuf.fill(0);
1053 config.ibs
1054 } else {
1055 continue;
1056 }
1057 } else {
1058 return Err(e);
1059 }
1060 }
1061 };
1062
1063 if n == 0 {
1064 break;
1065 }
1066
1067 bytes_read_total += n as u64;
1068
1069 if n == config.ibs {
1071 stats.records_in_full += 1;
1072 } else {
1073 stats.records_in_partial += 1;
1074 if config.conv.sync {
1076 let pad_byte = if config.conv.block || config.conv.unblock {
1077 b' '
1078 } else {
1079 0u8
1080 };
1081 ibuf[n..config.ibs].fill(pad_byte);
1082 }
1083 }
1084
1085 let effective_len = if config.conv.sync { config.ibs } else { n };
1087 apply_conversions(&mut ibuf[..effective_len], &config.conv);
1088
1089 let write_data: &[u8] = if config.conv.unblock && config.cbs > 0 {
1092 unblock_buf.clear();
1093 let data = &ibuf[..effective_len];
1094 let mut pos = 0;
1095 while pos < data.len() {
1096 let end = std::cmp::min(pos + config.cbs, data.len());
1097 let record = &data[pos..end];
1098 let trimmed_len = record
1100 .iter()
1101 .rposition(|&b| b != b' ')
1102 .map(|p| p + 1)
1103 .unwrap_or(0);
1104 unblock_buf.extend_from_slice(&record[..trimmed_len]);
1105 unblock_buf.push(b'\n');
1106 pos = end;
1107 }
1108 &unblock_buf
1109 } else {
1110 &ibuf[..effective_len]
1111 };
1112
1113 let wd_len = write_data.len();
1117 if config.ibs == config.obs && obuf.is_empty() && !config.conv.unblock {
1118 output.write_all(write_data)?;
1120 if wd_len == config.obs {
1121 stats.records_out_full += 1;
1122 } else {
1123 stats.records_out_partial += 1;
1124 }
1125 stats.bytes_copied += wd_len as u64;
1126 continue;
1128 }
1129
1130 let obs = config.obs;
1134 let mut wd_off = 0;
1135
1136 if !obuf.is_empty() {
1138 let need = obs - obuf.len();
1139 if write_data.len() >= need {
1140 obuf.extend_from_slice(&write_data[..need]);
1141 output.write_all(&obuf)?;
1142 stats.records_out_full += 1;
1143 stats.bytes_copied += obs as u64;
1144 obuf.clear();
1145 wd_off = need;
1146 } else {
1147 obuf.extend_from_slice(write_data);
1148 wd_off = write_data.len();
1149 }
1150 }
1151
1152 let remaining_wd = &write_data[wd_off..];
1154 let full_blocks = remaining_wd.len() / obs;
1155 if full_blocks > 0 {
1156 let full_len = full_blocks * obs;
1157 output.write_all(&remaining_wd[..full_len])?;
1158 stats.records_out_full += full_blocks as u64;
1159 stats.bytes_copied += full_len as u64;
1160 wd_off += full_len;
1161 }
1162
1163 let leftover = &write_data[wd_off..];
1165 if !leftover.is_empty() {
1166 obuf.extend_from_slice(leftover);
1167 }
1168 }
1169
1170 if !obuf.is_empty() {
1172 output.write_all(&obuf)?;
1173 stats.records_out_partial += 1;
1174 stats.bytes_copied += obuf.len() as u64;
1175 }
1176
1177 output.flush()?;
1179
1180 if let Some(ref f) = output_file {
1182 if config.conv.fsync {
1183 f.sync_all()?;
1184 } else if config.conv.fdatasync {
1185 f.sync_data()?;
1186 }
1187 }
1188
1189 let elapsed = start_time.elapsed();
1190
1191 if config.status != StatusLevel::None {
1193 print_stats(&stats, elapsed, config.status);
1194 }
1195
1196 Ok(stats)
1197}
1198
1199fn print_stats(stats: &DdStats, elapsed: std::time::Duration, status: StatusLevel) {
1201 eprintln!(
1202 "{}+{} records in",
1203 stats.records_in_full, stats.records_in_partial
1204 );
1205 eprintln!(
1206 "{}+{} records out",
1207 stats.records_out_full, stats.records_out_partial
1208 );
1209
1210 if status == StatusLevel::NoXfer {
1211 return;
1212 }
1213
1214 let secs = elapsed.as_secs_f64();
1215 if secs > 0.0 {
1216 let rate = stats.bytes_copied as f64 / secs;
1217 eprintln!(
1218 "{} bytes copied, {:.6} s, {}/s",
1219 stats.bytes_copied,
1220 secs,
1221 human_size(rate as u64)
1222 );
1223 } else {
1224 eprintln!("{} bytes copied", stats.bytes_copied);
1225 }
1226}
1227
1228fn human_size(bytes: u64) -> String {
1230 const UNITS: &[&str] = &["B", "kB", "MB", "GB", "TB", "PB", "EB"];
1231 let mut size = bytes as f64;
1232 for &unit in UNITS {
1233 if size < 1000.0 {
1234 if size == size.floor() {
1235 return format!("{} {}", size as u64, unit);
1236 }
1237 return format!("{:.1} {}", size, unit);
1238 }
1239 size /= 1000.0;
1240 }
1241 format!("{:.1} EB", size * 1000.0)
1242}
1243
1244pub fn print_help() {
1246 eprint!(
1247 "\
1248Usage: dd [OPERAND]...
1249 or: dd OPTION
1250Copy a file, converting and formatting according to the operands.
1251
1252 bs=BYTES read and write up to BYTES bytes at a time (default: 512)
1253 cbs=BYTES convert BYTES bytes at a time
1254 conv=CONVS convert the file as per the comma separated symbol list
1255 count=N copy only N input blocks
1256 ibs=BYTES read up to BYTES bytes at a time (default: 512)
1257 if=FILE read from FILE instead of stdin
1258 iflag=FLAGS read as per the comma separated symbol list
1259 obs=BYTES write BYTES bytes at a time (default: 512)
1260 of=FILE write to FILE instead of stdout
1261 oflag=FLAGS write as per the comma separated symbol list
1262 seek=N skip N obs-sized blocks at start of output
1263 skip=N skip N ibs-sized blocks at start of input
1264 status=LEVEL LEVEL of information to print to stderr;
1265 'none' suppresses everything but error messages,
1266 'noerror' suppresses the final transfer statistics,
1267 'progress' shows periodic transfer statistics
1268
1269 BLOCKS and BYTES may be followed by the following multiplicative suffixes:
1270 c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
1271 GB=1000*1000*1000, GiB=1024*1024*1024, and so on for T, P, E.
1272
1273Each CONV symbol may be:
1274
1275 lcase change upper case to lower case
1276 ucase change lower case to upper case
1277 swab swap every pair of input bytes
1278 sync pad every input block with NULs to ibs-size
1279 noerror continue after read errors
1280 notrunc do not truncate the output file
1281 fdatasync physically write output file data before finishing
1282 fsync likewise, but also write metadata
1283 excl fail if the output file already exists
1284 nocreat do not create the output file
1285
1286Each FLAG symbol may be:
1287
1288 append append mode (makes sense only for output; conv=notrunc suggested)
1289 direct use direct I/O for data
1290 directory fail unless a directory
1291 dsync use synchronized I/O for data
1292 sync likewise, but also for metadata
1293 fullblock accumulate full blocks of input (iflag only)
1294 nonblock use non-blocking I/O
1295 noatime do not update access time
1296 nocache Request to drop cache
1297 noctty do not assign controlling terminal from file
1298 nofollow do not follow symlinks
1299 count_bytes treat 'count=N' as a byte count (iflag only)
1300 skip_bytes treat 'skip=N' as a byte count (iflag only)
1301
1302 --help display this help and exit
1303 --version output version information and exit
1304"
1305 );
1306}
1307
1308pub fn print_version() {
1310 eprintln!("dd (fcoreutils) {}", env!("CARGO_PKG_VERSION"));
1311}