1mod blocks;
9mod bufferedoutput;
10mod conversion_tables;
11mod datastructures;
12mod numbers;
13mod parseargs;
14mod progress;
15
16use crate::bufferedoutput::BufferedOutput;
17use blocks::conv_block_unblock_helper;
18use datastructures::*;
19#[cfg(any(target_os = "linux", target_os = "android"))]
20use nix::fcntl::FcntlArg::F_SETFL;
21#[cfg(any(target_os = "linux", target_os = "android"))]
22use nix::fcntl::OFlag;
23use parseargs::Parser;
24use progress::ProgUpdateType;
25use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater};
26use uucore::io::OwnedFileDescriptorOrHandle;
27use uucore::translate;
28
29use std::cmp;
30use std::env;
31use std::ffi::OsString;
32use std::fs::{File, OpenOptions};
33use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
34#[cfg(any(target_os = "linux", target_os = "android"))]
35use std::os::fd::AsFd;
36#[cfg(any(target_os = "linux", target_os = "android"))]
37use std::os::unix::fs::OpenOptionsExt;
38#[cfg(unix)]
39use std::os::unix::{
40 fs::FileTypeExt,
41 io::{AsRawFd, FromRawFd},
42};
43#[cfg(windows)]
44use std::os::windows::{fs::MetadataExt, io::AsHandle};
45use std::path::Path;
46use std::sync::atomic::AtomicU8;
47use std::sync::{Arc, atomic::Ordering::Relaxed, mpsc};
48use std::thread;
49use std::time::{Duration, Instant};
50
51use clap::{Arg, Command};
52use gcd::Gcd;
53#[cfg(target_os = "linux")]
54use nix::{
55 errno::Errno,
56 fcntl::{PosixFadviseAdvice, posix_fadvise},
57};
58use uucore::display::Quotable;
59use uucore::error::{FromIo, UResult};
60#[cfg(unix)]
61use uucore::error::{USimpleError, set_exit_code};
62#[cfg(target_os = "linux")]
63use uucore::show_if_err;
64use uucore::{format_usage, show_error};
65
66const BUF_INIT_BYTE: u8 = 0xDD;
67
68#[derive(Default)]
70struct Settings {
71 infile: Option<String>,
72 outfile: Option<String>,
73 ibs: usize,
74 obs: usize,
75 skip: u64,
76 seek: u64,
77 count: Option<Num>,
78 iconv: IConvFlags,
79 iflags: IFlags,
80 oconv: OConvFlags,
81 oflags: OFlags,
82 status: Option<StatusLevel>,
83 buffered: bool,
85}
86
87pub struct Alarm {
99 interval: Duration,
100 trigger: Arc<AtomicU8>,
101}
102
103pub const ALARM_TRIGGER_NONE: u8 = 0;
104pub const ALARM_TRIGGER_TIMER: u8 = 1;
105pub const ALARM_TRIGGER_SIGNAL: u8 = 2;
106
107impl Alarm {
108 pub fn with_interval(interval: Duration) -> Self {
110 let trigger = Arc::new(AtomicU8::default());
111
112 let weak_trigger = Arc::downgrade(&trigger);
113 thread::spawn(move || {
114 while let Some(trigger) = weak_trigger.upgrade() {
115 thread::sleep(interval);
116 trigger.store(ALARM_TRIGGER_TIMER, Relaxed);
117 }
118 });
119
120 Self { interval, trigger }
121 }
122
123 pub fn manual_trigger_fn(&self) -> Box<dyn Send + Sync + Fn()> {
129 let weak_trigger = Arc::downgrade(&self.trigger);
130 Box::new(move || {
131 if let Some(trigger) = weak_trigger.upgrade() {
132 trigger.store(ALARM_TRIGGER_SIGNAL, Relaxed);
133 }
134 })
135 }
136
137 pub fn get_trigger(&self) -> u8 {
144 self.trigger.swap(ALARM_TRIGGER_NONE, Relaxed)
145 }
146
147 pub fn get_interval(&self) -> Duration {
149 self.interval
150 }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq)]
159enum Num {
160 Blocks(u64),
161 Bytes(u64),
162}
163
164impl Default for Num {
165 fn default() -> Self {
166 Self::Blocks(0)
167 }
168}
169
170impl Num {
171 fn force_bytes_if(self, force: bool) -> Self {
172 match self {
173 Self::Blocks(n) if force => Self::Bytes(n),
174 count => count,
175 }
176 }
177
178 fn to_bytes(self, block_size: u64) -> u64 {
179 match self {
180 Self::Blocks(n) => n * block_size,
181 Self::Bytes(n) => n,
182 }
183 }
184}
185
186enum Source {
191 #[cfg(not(unix))]
193 Stdin(io::Stdin),
194
195 File(File),
197
198 #[cfg(unix)]
200 StdinFile(File),
201
202 #[cfg(unix)]
204 Fifo(File),
205}
206
207impl Source {
208 #[cfg(unix)]
216 fn stdin_as_file() -> Self {
217 let fd = io::stdin().as_raw_fd();
218 let f = unsafe { File::from_raw_fd(fd) };
219 Self::StdinFile(f)
220 }
221
222 fn len(&self) -> io::Result<i64> {
226 #[allow(clippy::match_wildcard_for_single_variants)]
227 match self {
228 Self::File(f) => Ok(f.metadata()?.len().try_into().unwrap_or(i64::MAX)),
229 _ => Ok(0),
230 }
231 }
232
233 fn skip(&mut self, n: u64) -> io::Result<u64> {
234 match self {
235 #[cfg(not(unix))]
236 Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
237 Ok(m) if m < n => {
238 show_error!(
239 "{}",
240 translate!("dd-error-cannot-skip-offset", "file" => "standard input")
241 );
242 Ok(m)
243 }
244 Ok(m) => Ok(m),
245 Err(e) => Err(e),
246 },
247 #[cfg(unix)]
248 Self::StdinFile(f) => {
249 if let Ok(Some(len)) = try_get_len_of_block_device(f) {
250 if len < n {
251 show_error!(
254 "{}",
255 translate!("dd-error-cannot-skip-invalid", "file" => "standard input")
256 );
257 set_exit_code(1);
258 return Ok(len);
259 }
260 }
261 match io::copy(&mut f.take(n), &mut io::sink()) {
262 Ok(m) if m < n => {
263 show_error!(
264 "{}",
265 translate!("dd-error-cannot-skip-offset", "file" => "standard input")
266 );
267 Ok(m)
268 }
269 Ok(m) => Ok(m),
270 Err(e) => Err(e),
271 }
272 }
273 Self::File(f) => f.seek(SeekFrom::Current(n.try_into().unwrap())),
274 #[cfg(unix)]
275 Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
276 }
277 }
278
279 #[cfg(target_os = "linux")]
286 fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> {
287 #[allow(clippy::match_wildcard_for_single_variants)]
288 match self {
289 Self::File(f) => {
290 let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED;
291 posix_fadvise(f.as_fd(), offset, len, advice)
292 }
293 _ => Err(Errno::ESPIPE), }
295 }
296}
297
298impl Read for Source {
299 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
300 match self {
301 #[cfg(not(unix))]
302 Self::Stdin(stdin) => stdin.read(buf),
303 Self::File(f) => f.read(buf),
304 #[cfg(unix)]
305 Self::StdinFile(f) => f.read(buf),
306 #[cfg(unix)]
307 Self::Fifo(f) => f.read(buf),
308 }
309 }
310}
311
312struct Input<'a> {
319 src: Source,
321
322 settings: &'a Settings,
324}
325
326impl<'a> Input<'a> {
327 fn new_stdin(settings: &'a Settings) -> UResult<Self> {
329 #[cfg(not(unix))]
330 let mut src = {
331 let f = File::from(io::stdin().as_handle().try_clone_to_owned()?);
332 let is_file = if let Ok(metadata) = f.metadata() {
333 metadata.creation_time() != 0
339 } else {
340 false
341 };
342 if is_file {
343 Source::File(f)
344 } else {
345 Source::Stdin(io::stdin())
346 }
347 };
348 #[cfg(unix)]
349 let mut src = Source::stdin_as_file();
350 #[cfg(unix)]
351 if let Source::StdinFile(f) = &src {
352 if settings.iflags.directory && !f.metadata()?.is_dir() {
353 return Err(USimpleError::new(
354 1,
355 translate!("dd-error-not-directory", "file" => "standard input"),
356 ));
357 }
358 }
359 if settings.skip > 0 {
360 src.skip(settings.skip)?;
361 }
362 Ok(Self { src, settings })
363 }
364
365 fn new_file(filename: &Path, settings: &'a Settings) -> UResult<Self> {
367 let src = {
368 let mut opts = OpenOptions::new();
369 opts.read(true);
370
371 #[cfg(any(target_os = "linux", target_os = "android"))]
372 if let Some(libc_flags) = make_linux_iflags(&settings.iflags) {
373 opts.custom_flags(libc_flags);
374 }
375
376 opts.open(filename).map_err_context(
377 || translate!("dd-error-failed-to-open", "path" => filename.quote()),
378 )?
379 };
380
381 let mut src = Source::File(src);
382 if settings.skip > 0 {
383 src.skip(settings.skip)?;
384 }
385 Ok(Self { src, settings })
386 }
387
388 #[cfg(unix)]
390 fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
391 let mut opts = OpenOptions::new();
392 opts.read(true);
393 #[cfg(any(target_os = "linux", target_os = "android"))]
394 opts.custom_flags(make_linux_iflags(&settings.iflags).unwrap_or(0));
395 let mut src = Source::Fifo(opts.open(filename)?);
396 if settings.skip > 0 {
397 src.skip(settings.skip)?;
398 }
399 Ok(Self { src, settings })
400 }
401}
402
403#[cfg(any(target_os = "linux", target_os = "android"))]
404fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
405 let mut flag = 0;
406
407 if iflags.direct {
408 flag |= libc::O_DIRECT;
409 }
410 if iflags.directory {
411 flag |= libc::O_DIRECTORY;
412 }
413 if iflags.dsync {
414 flag |= libc::O_DSYNC;
415 }
416 if iflags.noatime {
417 flag |= libc::O_NOATIME;
418 }
419 if iflags.noctty {
420 flag |= libc::O_NOCTTY;
421 }
422 if iflags.nofollow {
423 flag |= libc::O_NOFOLLOW;
424 }
425 if iflags.nonblock {
426 flag |= libc::O_NONBLOCK;
427 }
428 if iflags.sync {
429 flag |= libc::O_SYNC;
430 }
431
432 if flag == 0 { None } else { Some(flag) }
433}
434
435impl Read for Input<'_> {
436 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
437 let mut base_idx = 0;
438 let target_len = buf.len();
439 loop {
440 match self.src.read(&mut buf[base_idx..]) {
441 Ok(0) => return Ok(base_idx),
442 Ok(rlen) if self.settings.iflags.fullblock => {
443 base_idx += rlen;
444
445 if base_idx >= target_len {
446 return Ok(target_len);
447 }
448 }
449 Ok(len) => return Ok(len),
450 Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
451 Err(_) if self.settings.iconv.noerror => return Ok(base_idx),
452 Err(e) => return Err(e),
453 }
454 }
455 }
456}
457
458impl Input<'_> {
459 #[cfg_attr(not(target_os = "linux"), allow(clippy::unused_self, unused_variables))]
467 fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
468 #[cfg(target_os = "linux")]
469 {
470 show_if_err!(
471 self.src
472 .discard_cache(offset, len)
473 .map_err_context(|| translate!("dd-error-failed-discard-cache-input"))
474 );
475 }
476 #[cfg(not(target_os = "linux"))]
477 {
478 }
481 }
482
483 fn fill_consecutive(&mut self, buf: &mut Vec<u8>) -> io::Result<ReadStat> {
487 let mut reads_complete = 0;
488 let mut reads_partial = 0;
489 let mut bytes_total = 0;
490
491 for chunk in buf.chunks_mut(self.settings.ibs) {
492 match self.read(chunk)? {
493 rlen if rlen == self.settings.ibs => {
494 bytes_total += rlen;
495 reads_complete += 1;
496 }
497 rlen if rlen > 0 => {
498 bytes_total += rlen;
499 reads_partial += 1;
500 }
501 _ => break,
502 }
503 }
504 buf.truncate(bytes_total);
505 Ok(ReadStat {
506 reads_complete,
507 reads_partial,
508 records_truncated: 0,
510 bytes_total: bytes_total.try_into().unwrap(),
511 })
512 }
513
514 fn fill_blocks(&mut self, buf: &mut Vec<u8>, pad: u8) -> io::Result<ReadStat> {
518 let mut reads_complete = 0;
519 let mut reads_partial = 0;
520 let mut base_idx = 0;
521 let mut bytes_total = 0;
522
523 while base_idx < buf.len() {
524 let next_blk = cmp::min(base_idx + self.settings.ibs, buf.len());
525 let target_len = next_blk - base_idx;
526
527 match self.read(&mut buf[base_idx..next_blk])? {
528 0 => break,
529 rlen if rlen < target_len => {
530 bytes_total += rlen;
531 reads_partial += 1;
532 let padding = vec![pad; target_len - rlen];
533 buf.splice(base_idx + rlen..next_blk, padding.into_iter());
534 }
535 rlen => {
536 bytes_total += rlen;
537 reads_complete += 1;
538 }
539 }
540
541 base_idx += self.settings.ibs;
542 }
543
544 buf.truncate(base_idx);
545 Ok(ReadStat {
546 reads_complete,
547 reads_partial,
548 records_truncated: 0,
549 bytes_total: bytes_total.try_into().unwrap(),
550 })
551 }
552}
553
554enum Density {
555 Sparse,
556 Dense,
557}
558
559enum Dest {
561 Stdout(Stdout),
563
564 File(File, Density),
569
570 #[cfg(unix)]
572 Fifo(File),
573
574 #[cfg(unix)]
576 Sink,
577}
578
579impl Dest {
580 fn fsync(&mut self) -> io::Result<()> {
581 match self {
582 Self::Stdout(stdout) => stdout.flush(),
583 Self::File(f, _) => {
584 f.flush()?;
585 f.sync_all()
586 }
587 #[cfg(unix)]
588 Self::Fifo(f) => {
589 f.flush()?;
590 f.sync_all()
591 }
592 #[cfg(unix)]
593 Self::Sink => Ok(()),
594 }
595 }
596
597 fn fdatasync(&mut self) -> io::Result<()> {
598 match self {
599 Self::Stdout(stdout) => stdout.flush(),
600 Self::File(f, _) => {
601 f.flush()?;
602 f.sync_data()
603 }
604 #[cfg(unix)]
605 Self::Fifo(f) => {
606 f.flush()?;
607 f.sync_data()
608 }
609 #[cfg(unix)]
610 Self::Sink => Ok(()),
611 }
612 }
613
614 fn seek(&mut self, n: u64) -> io::Result<u64> {
615 match self {
616 Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
617 Self::File(f, _) => {
618 #[cfg(unix)]
619 if let Ok(Some(len)) = try_get_len_of_block_device(f) {
620 if len < n {
621 show_error!(
624 "{}",
625 translate!("dd-error-cannot-seek-invalid", "output" => "standard output")
626 );
627 set_exit_code(1);
628 return Ok(len);
629 }
630 }
631 f.seek(SeekFrom::Current(n.try_into().unwrap()))
632 }
633 #[cfg(unix)]
634 Self::Fifo(f) => {
635 io::copy(&mut f.take(n), &mut io::sink())
637 }
638 #[cfg(unix)]
639 Self::Sink => Ok(0),
640 }
641 }
642
643 fn truncate(&mut self) -> io::Result<()> {
645 #[allow(clippy::match_wildcard_for_single_variants)]
646 match self {
647 Self::File(f, _) => {
648 let pos = f.stream_position()?;
649 f.set_len(pos)
650 }
651 _ => Ok(()),
652 }
653 }
654
655 #[cfg(target_os = "linux")]
662 fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) -> nix::Result<()> {
663 match self {
664 Self::File(f, _) => {
665 let advice = PosixFadviseAdvice::POSIX_FADV_DONTNEED;
666 posix_fadvise(f.as_fd(), offset, len, advice)
667 }
668 _ => Err(Errno::ESPIPE), }
670 }
671
672 fn len(&self) -> io::Result<i64> {
676 #[allow(clippy::match_wildcard_for_single_variants)]
677 match self {
678 Self::File(f, _) => Ok(f.metadata()?.len().try_into().unwrap_or(i64::MAX)),
679 _ => Ok(0),
680 }
681 }
682}
683
684fn is_sparse(buf: &[u8]) -> bool {
686 buf.iter().all(|&e| e == 0u8)
687}
688
689impl Write for Dest {
690 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
691 match self {
692 Self::File(f, Density::Sparse) if is_sparse(buf) => {
693 let seek_amt: i64 = buf
694 .len()
695 .try_into()
696 .expect("Internal dd Error: Seek amount greater than signed 64-bit integer");
697 f.seek(SeekFrom::Current(seek_amt))?;
698 Ok(buf.len())
699 }
700 Self::File(f, _) => f.write(buf),
701 Self::Stdout(stdout) => stdout.write(buf),
702 #[cfg(unix)]
703 Self::Fifo(f) => f.write(buf),
704 #[cfg(unix)]
705 Self::Sink => Ok(buf.len()),
706 }
707 }
708
709 fn flush(&mut self) -> io::Result<()> {
710 match self {
711 Self::Stdout(stdout) => stdout.flush(),
712 Self::File(f, _) => f.flush(),
713 #[cfg(unix)]
714 Self::Fifo(f) => f.flush(),
715 #[cfg(unix)]
716 Self::Sink => Ok(()),
717 }
718 }
719}
720
721struct Output<'a> {
728 dst: Dest,
730
731 settings: &'a Settings,
733}
734
735impl<'a> Output<'a> {
736 fn new_stdout(settings: &'a Settings) -> UResult<Self> {
738 let mut dst = Dest::Stdout(io::stdout());
739 dst.seek(settings.seek)
740 .map_err_context(|| translate!("dd-error-write-error"))?;
741 Ok(Self { dst, settings })
742 }
743
744 fn new_file(filename: &Path, settings: &'a Settings) -> UResult<Self> {
746 fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result<File, io::Error> {
747 let mut opts = OpenOptions::new();
748 opts.write(true)
749 .create(!cflags.nocreat)
750 .create_new(cflags.excl)
751 .append(oflags.append);
752
753 #[cfg(any(target_os = "linux", target_os = "android"))]
754 if let Some(libc_flags) = make_linux_oflags(oflags) {
755 opts.custom_flags(libc_flags);
756 }
757
758 opts.open(path)
759 }
760
761 let dst = open_dst(filename, &settings.oconv, &settings.oflags).map_err_context(
762 || translate!("dd-error-failed-to-open", "path" => filename.quote()),
763 )?;
764
765 if !settings.oconv.notrunc {
774 dst.set_len(settings.seek).ok();
775 }
776
777 Self::prepare_file(dst, settings)
778 }
779
780 fn prepare_file(dst: File, settings: &'a Settings) -> UResult<Self> {
781 let density = if settings.oconv.sparse {
782 Density::Sparse
783 } else {
784 Density::Dense
785 };
786 let mut dst = Dest::File(dst, density);
787 dst.seek(settings.seek)
788 .map_err_context(|| translate!("dd-error-failed-to-seek"))?;
789 Ok(Self { dst, settings })
790 }
791
792 fn new_file_from_stdout(settings: &'a Settings) -> UResult<Self> {
798 let fx = OwnedFileDescriptorOrHandle::from(io::stdout())?;
799 #[cfg(any(target_os = "linux", target_os = "android"))]
800 if let Some(libc_flags) = make_linux_oflags(&settings.oflags) {
801 nix::fcntl::fcntl(
802 fx.as_raw().as_fd(),
803 F_SETFL(OFlag::from_bits_retain(libc_flags)),
804 )?;
805 }
806
807 Self::prepare_file(fx.into_file(), settings)
808 }
809
810 #[cfg(unix)]
812 fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
813 if settings.seek > 0 {
817 Dest::Fifo(File::open(filename)?).seek(settings.seek)?;
818 }
819 if let Some(Num::Blocks(0) | Num::Bytes(0)) = settings.count {
823 let dst = Dest::Sink;
824 return Ok(Self { dst, settings });
825 }
826 let mut opts = OpenOptions::new();
829 opts.write(true)
830 .create(!settings.oconv.nocreat)
831 .create_new(settings.oconv.excl)
832 .append(settings.oflags.append);
833 #[cfg(any(target_os = "linux", target_os = "android"))]
834 opts.custom_flags(make_linux_oflags(&settings.oflags).unwrap_or(0));
835 let dst = Dest::Fifo(opts.open(filename)?);
836 Ok(Self { dst, settings })
837 }
838
839 #[cfg_attr(not(target_os = "linux"), allow(clippy::unused_self, unused_variables))]
847 fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
848 #[cfg(target_os = "linux")]
849 {
850 show_if_err!(
851 self.dst
852 .discard_cache(offset, len)
853 .map_err_context(|| { translate!("dd-error-failed-discard-cache-output") })
854 );
855 }
856 #[cfg(not(target_os = "linux"))]
857 {
858 }
861 }
862
863 fn write_block(&mut self, chunk: &[u8]) -> io::Result<usize> {
870 let full_len = chunk.len();
871 let mut base_idx = 0;
872 loop {
873 match self.dst.write(&chunk[base_idx..]) {
874 Ok(wlen) => {
875 base_idx += wlen;
876 if (base_idx >= full_len) || !self.settings.iflags.fullblock {
878 return Ok(base_idx);
879 }
880 }
881 Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
882 Err(e) => return Err(e),
883 }
884 }
885 }
886
887 fn write_blocks(&mut self, buf: &[u8]) -> io::Result<WriteStat> {
895 let mut writes_complete = 0;
896 let mut writes_partial = 0;
897 let mut bytes_total = 0;
898
899 for chunk in buf.chunks(self.settings.obs) {
900 let wlen = self.write_block(chunk)?;
901 if wlen < self.settings.obs {
902 writes_partial += 1;
903 } else {
904 writes_complete += 1;
905 }
906 bytes_total += wlen;
907 }
908
909 Ok(WriteStat {
910 writes_complete,
911 writes_partial,
912 bytes_total: bytes_total.try_into().unwrap_or(0u128),
913 })
914 }
915
916 fn sync(&mut self) -> io::Result<()> {
918 if self.settings.oconv.fsync {
919 self.dst.fsync()
920 } else if self.settings.oconv.fdatasync {
921 self.dst.fdatasync()
922 } else {
923 Ok(())
925 }
926 }
927
928 fn truncate(&mut self) -> io::Result<()> {
930 self.dst.truncate()
931 }
932}
933
934enum BlockWriter<'a> {
936 Buffered(BufferedOutput<'a>),
940
941 Unbuffered(Output<'a>),
945}
946
947impl BlockWriter<'_> {
948 fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
949 match self {
950 Self::Unbuffered(o) => o.discard_cache(offset, len),
951 Self::Buffered(o) => o.discard_cache(offset, len),
952 }
953 }
954
955 fn flush(&mut self) -> io::Result<WriteStat> {
956 match self {
957 Self::Unbuffered(_) => Ok(WriteStat::default()),
958 Self::Buffered(o) => o.flush(),
959 }
960 }
961
962 fn sync(&mut self) -> io::Result<()> {
963 match self {
964 Self::Unbuffered(o) => o.sync(),
965 Self::Buffered(o) => o.sync(),
966 }
967 }
968
969 fn truncate(&mut self) {
971 match self {
978 Self::Unbuffered(o) => o.truncate().ok(),
979 Self::Buffered(o) => o.truncate().ok(),
980 };
981 }
982
983 fn write_blocks(&mut self, buf: &[u8]) -> io::Result<WriteStat> {
984 match self {
985 Self::Unbuffered(o) => o.write_blocks(buf),
986 Self::Buffered(o) => o.write_blocks(buf),
987 }
988 }
989}
990
991fn flush_caches_full_length(i: &Input, o: &Output) -> io::Result<()> {
994 if i.settings.iflags.nocache {
996 let offset = 0;
997 #[allow(clippy::useless_conversion)]
998 let len = i.src.len()?.try_into().unwrap();
999 i.discard_cache(offset, len);
1000 }
1001 if i.settings.oflags.nocache {
1005 let offset = 0;
1006 #[allow(clippy::useless_conversion)]
1007 let len = o.dst.len()?.try_into().unwrap();
1008 o.discard_cache(offset, len);
1009 }
1010
1011 Ok(())
1012}
1013
1014fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
1026 let mut rstat = ReadStat::default();
1032 let mut wstat = WriteStat::default();
1033
1034 let start = Instant::now();
1042
1043 let bsize = calc_bsize(i.settings.ibs, o.settings.obs);
1048
1049 let (prog_tx, rx) = mpsc::channel();
1060 let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status));
1061
1062 let truncate = !o.settings.oconv.notrunc;
1064
1065 if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count {
1068 flush_caches_full_length(&i, &o)?;
1074 return finalize(
1075 BlockWriter::Unbuffered(o),
1076 rstat,
1077 wstat,
1078 start,
1079 &prog_tx,
1080 output_thread,
1081 truncate,
1082 );
1083 }
1084
1085 let mut buf = vec![BUF_INIT_BYTE; bsize];
1088
1089 let alarm = Alarm::with_interval(Duration::from_secs(1));
1094
1095 #[cfg(target_os = "linux")]
1099 let signal_handler = progress::SignalHandler::install_signal_handler(alarm.manual_trigger_fn());
1100 #[cfg(target_os = "linux")]
1101 if let Err(e) = &signal_handler {
1102 if Some(StatusLevel::None) != i.settings.status {
1103 eprintln!("{}\n\t{e}", translate!("dd-warning-signal-handler"));
1104 }
1105 }
1106
1107 let mut read_offset = 0;
1112 let mut write_offset = 0;
1113
1114 let input_nocache = i.settings.iflags.nocache;
1115 let output_nocache = o.settings.oflags.nocache;
1116
1117 let mut o = if o.settings.buffered {
1119 BlockWriter::Buffered(BufferedOutput::new(o))
1120 } else {
1121 BlockWriter::Unbuffered(o)
1122 };
1123
1124 while below_count_limit(i.settings.count, &rstat) {
1131 let loop_bsize = calc_loop_bsize(i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
1137 let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
1138 if rstat_update.is_empty() {
1139 break;
1140 }
1141 let wstat_update = o.write_blocks(&buf)?;
1142
1143 let read_len = rstat_update.bytes_total;
1148 if input_nocache {
1149 let offset = read_offset.try_into().unwrap();
1150 let len = read_len.try_into().unwrap();
1151 i.discard_cache(offset, len);
1152 }
1153 read_offset += read_len;
1154
1155 let write_len = wstat_update.bytes_total;
1160 if output_nocache {
1161 let offset = write_offset.try_into().unwrap();
1162 let len = write_len.try_into().unwrap();
1163 o.discard_cache(offset, len);
1164 }
1165 write_offset += write_len;
1166
1167 rstat += rstat_update;
1174 wstat += wstat_update;
1175 match alarm.get_trigger() {
1176 ALARM_TRIGGER_NONE => {}
1177 t @ (ALARM_TRIGGER_TIMER | ALARM_TRIGGER_SIGNAL) => {
1178 let tp = match t {
1179 ALARM_TRIGGER_TIMER => ProgUpdateType::Periodic,
1180 _ => ProgUpdateType::Signal,
1181 };
1182 let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), tp);
1183 prog_tx.send(prog_update).unwrap_or(());
1184 }
1185 _ => {}
1186 }
1187 }
1188
1189 finalize(o, rstat, wstat, start, &prog_tx, output_thread, truncate)
1190}
1191
1192fn finalize<T>(
1194 mut output: BlockWriter,
1195 rstat: ReadStat,
1196 wstat: WriteStat,
1197 start: Instant,
1198 prog_tx: &mpsc::Sender<ProgUpdate>,
1199 output_thread: thread::JoinHandle<T>,
1200 truncate: bool,
1201) -> io::Result<()> {
1202 let wstat_update = output.flush()?;
1205
1206 output.sync()?;
1208
1209 if truncate {
1211 output.truncate();
1212 }
1213
1214 let wstat = wstat + wstat_update;
1216 let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), ProgUpdateType::Final);
1217 prog_tx.send(prog_update).unwrap_or(());
1218 output_thread
1220 .join()
1221 .expect("Failed to join with the output thread.");
1222
1223 Ok(())
1224}
1225
1226#[cfg(any(target_os = "linux", target_os = "android"))]
1227#[allow(clippy::cognitive_complexity)]
1228fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
1229 let mut flag = 0;
1230
1231 if oflags.append {
1233 flag |= libc::O_APPEND;
1234 }
1235 if oflags.direct {
1236 flag |= libc::O_DIRECT;
1237 }
1238 if oflags.directory {
1239 flag |= libc::O_DIRECTORY;
1240 }
1241 if oflags.dsync {
1242 flag |= libc::O_DSYNC;
1243 }
1244 if oflags.noatime {
1245 flag |= libc::O_NOATIME;
1246 }
1247 if oflags.noctty {
1248 flag |= libc::O_NOCTTY;
1249 }
1250 if oflags.nofollow {
1251 flag |= libc::O_NOFOLLOW;
1252 }
1253 if oflags.nonblock {
1254 flag |= libc::O_NONBLOCK;
1255 }
1256 if oflags.sync {
1257 flag |= libc::O_SYNC;
1258 }
1259
1260 if flag == 0 { None } else { Some(flag) }
1261}
1262
1263fn read_helper(i: &mut Input, buf: &mut Vec<u8>, bsize: usize) -> io::Result<ReadStat> {
1270 fn perform_swab(buf: &mut [u8]) {
1272 for base in (1..buf.len()).step_by(2) {
1273 buf.swap(base, base - 1);
1274 }
1275 }
1276 buf.resize(bsize, BUF_INIT_BYTE);
1280
1281 let mut rstat = match i.settings.iconv.sync {
1282 Some(ch) => i.fill_blocks(buf, ch)?,
1283 _ => i.fill_consecutive(buf)?,
1284 };
1285 if rstat.reads_complete == 0 && rstat.reads_partial == 0 {
1287 return Ok(rstat);
1288 }
1289
1290 if i.settings.iconv.swab {
1292 perform_swab(buf);
1293 }
1294
1295 match i.settings.iconv.mode {
1296 Some(ref mode) => {
1297 *buf = conv_block_unblock_helper(buf.clone(), mode, &mut rstat);
1298 Ok(rstat)
1299 }
1300 None => Ok(rstat),
1301 }
1302}
1303
1304fn calc_bsize(ibs: usize, obs: usize) -> usize {
1311 let gcd = Gcd::gcd(ibs, obs);
1312 (ibs / gcd) * obs
1314}
1315
1316fn calc_loop_bsize(
1319 count: Option<Num>,
1320 rstat: &ReadStat,
1321 wstat: &WriteStat,
1322 ibs: usize,
1323 ideal_bsize: usize,
1324) -> usize {
1325 match count {
1326 Some(Num::Blocks(rmax)) => {
1327 let rsofar = rstat.reads_complete + rstat.reads_partial;
1328 let rremain = rmax - rsofar;
1329 cmp::min(ideal_bsize as u64, rremain * ibs as u64) as usize
1330 }
1331 Some(Num::Bytes(bmax)) => {
1332 let bmax: u128 = bmax.into();
1333 let bremain: u128 = bmax - wstat.bytes_total;
1334 cmp::min(ideal_bsize as u128, bremain) as usize
1335 }
1336 None => ideal_bsize,
1337 }
1338}
1339
1340fn below_count_limit(count: Option<Num>, rstat: &ReadStat) -> bool {
1343 match count {
1344 Some(Num::Blocks(n)) => rstat.reads_complete + rstat.reads_partial < n,
1345 Some(Num::Bytes(n)) => rstat.bytes_total < n,
1346 None => true,
1347 }
1348}
1349
1350fn stdout_canonicalized() -> OsString {
1358 match Path::new("/dev/stdout").canonicalize() {
1359 Ok(p) => p.into_os_string(),
1360 Err(_) => OsString::from("/dev/stdout"),
1361 }
1362}
1363
1364fn is_stdout_redirected_to_seekable_file() -> bool {
1381 let s = stdout_canonicalized();
1382 let p = Path::new(&s);
1383 match File::open(p) {
1384 Ok(mut f) => {
1385 f.stream_position().is_ok() && f.seek(SeekFrom::End(0)).is_ok() && f.rewind().is_ok()
1386 }
1387 Err(_) => false,
1388 }
1389}
1390
1391#[cfg(unix)]
1393fn try_get_len_of_block_device(file: &mut File) -> io::Result<Option<u64>> {
1394 let ftype = file.metadata()?.file_type();
1395 if !ftype.is_block_device() {
1396 return Ok(None);
1397 }
1398
1399 let len = file.seek(SeekFrom::End(0))?;
1401 file.rewind()?;
1402 Ok(Some(len))
1403}
1404
1405#[cfg(unix)]
1407fn is_fifo(filename: &str) -> bool {
1408 if let Ok(metadata) = std::fs::metadata(filename) {
1409 if metadata.file_type().is_fifo() {
1410 return true;
1411 }
1412 }
1413 false
1414}
1415
1416#[uucore::main]
1417pub fn uumain(args: impl uucore::Args) -> UResult<()> {
1418 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
1419
1420 let settings: Settings = Parser::new().parse(
1421 matches
1422 .get_many::<String>(options::OPERANDS)
1423 .unwrap_or_default(),
1424 )?;
1425
1426 let i = match settings.infile {
1427 #[cfg(unix)]
1428 Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,
1429 Some(ref infile) => Input::new_file(Path::new(&infile), &settings)?,
1430 None => Input::new_stdin(&settings)?,
1431 };
1432 let o = match settings.outfile {
1433 #[cfg(unix)]
1434 Some(ref outfile) if is_fifo(outfile) => Output::new_fifo(Path::new(&outfile), &settings)?,
1435 Some(ref outfile) => Output::new_file(Path::new(&outfile), &settings)?,
1436 None if is_stdout_redirected_to_seekable_file() => Output::new_file_from_stdout(&settings)?,
1437 None => Output::new_stdout(&settings)?,
1438 };
1439 dd_copy(i, o).map_err_context(|| translate!("dd-error-io-error"))
1440}
1441
1442pub fn uu_app() -> Command {
1443 Command::new(uucore::util_name())
1444 .version(uucore::crate_version!())
1445 .help_template(uucore::localized_help_template(uucore::util_name()))
1446 .about(translate!("dd-about"))
1447 .override_usage(format_usage(&translate!("dd-usage")))
1448 .after_help(translate!("dd-after-help"))
1449 .infer_long_args(true)
1450 .arg(Arg::new(options::OPERANDS).num_args(1..))
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use crate::{Output, Parser, calc_bsize};
1456
1457 use std::path::Path;
1458
1459 #[test]
1460 fn bsize_test_primes() {
1461 let (n, m) = (7901, 7919);
1462 let res = calc_bsize(n, m);
1463 assert_eq!(res % n, 0);
1464 assert_eq!(res % m, 0);
1465
1466 assert_eq!(res, n * m);
1467 }
1468
1469 #[test]
1470 fn bsize_test_rel_prime_obs_greater() {
1471 let (n, m) = (7 * 5119, 13 * 5119);
1472 let res = calc_bsize(n, m);
1473 assert_eq!(res % n, 0);
1474 assert_eq!(res % m, 0);
1475
1476 assert_eq!(res, 7 * 13 * 5119);
1477 }
1478
1479 #[test]
1480 fn bsize_test_rel_prime_ibs_greater() {
1481 let (n, m) = (13 * 5119, 7 * 5119);
1482 let res = calc_bsize(n, m);
1483 assert_eq!(res % n, 0);
1484 assert_eq!(res % m, 0);
1485
1486 assert_eq!(res, 7 * 13 * 5119);
1487 }
1488
1489 #[test]
1490 fn bsize_test_3fac_rel_prime() {
1491 let (n, m) = (11 * 13 * 5119, 7 * 11 * 5119);
1492 let res = calc_bsize(n, m);
1493 assert_eq!(res % n, 0);
1494 assert_eq!(res % m, 0);
1495
1496 assert_eq!(res, 7 * 11 * 13 * 5119);
1497 }
1498
1499 #[test]
1500 fn bsize_test_ibs_greater() {
1501 let (n, m) = (512 * 1024, 256 * 1024);
1502 let res = calc_bsize(n, m);
1503 assert_eq!(res % n, 0);
1504 assert_eq!(res % m, 0);
1505
1506 assert_eq!(res, n);
1507 }
1508
1509 #[test]
1510 fn bsize_test_obs_greater() {
1511 let (n, m) = (256 * 1024, 512 * 1024);
1512 let res = calc_bsize(n, m);
1513 assert_eq!(res % n, 0);
1514 assert_eq!(res % m, 0);
1515
1516 assert_eq!(res, m);
1517 }
1518
1519 #[test]
1520 fn bsize_test_bs_eq() {
1521 let (n, m) = (1024, 1024);
1522 let res = calc_bsize(n, m);
1523 assert_eq!(res % n, 0);
1524 assert_eq!(res % m, 0);
1525
1526 assert_eq!(res, m);
1527 }
1528
1529 #[test]
1530 fn test_nocreat_causes_failure_when_ofile_doesnt_exist() {
1531 let args = &["conv=nocreat", "of=not-a-real.file"];
1532 let settings = Parser::new().parse(args).unwrap();
1533 assert!(
1534 Output::new_file(Path::new(settings.outfile.as_ref().unwrap()), &settings).is_err()
1535 );
1536 }
1537}