1#[cfg(feature = "serde")]
14use serde::Serialize;
15#[cfg(feature = "serde")]
16use serde::Deserialize;
17
18use std::path::Path;
20use std::io::BufRead as _;
21use std::io::BufReader;
22use std::io::ErrorKind;
23use std::process::Command;
24use std::ops::Not;
25use std::fs::File;
26
27
28#[derive(Debug, Clone)]
30#[cfg_attr(feature = "serde", derive(Serialize))]
31#[cfg_attr(feature = "serde", derive(Deserialize))]
32pub enum DiffOp {
33 None,
35
36 Internal,
38
39 Subprocess {
41 command: &'static str,
43 args: Vec<&'static str>,
45 },
46}
47
48
49impl DiffOp {
50 #[must_use]
52 pub fn posix_diff() -> Self {
53 Self::Subprocess {
54 command: "diff",
55 args: vec![],
56 }
57 }
58
59 #[must_use]
61 pub fn posix_cmp() -> Self {
62 Self::Subprocess {
63 command: "cmp",
64 args: vec!["-s"],
65 }
66 }
67
68
69 pub fn diff(&self, a: &Path, b: &Path) -> Result<bool, std::io::Error> {
71 match self {
72 Self::None => Ok(a != b),
73
74 Self::Internal => {
75 let file_a = match File::options().read(true).open(a) {
76 Ok(f) => Some(f),
77 Err(e) if matches!(e.kind(), ErrorKind::NotFound) => None,
78 Err(e) => return Err(e),
79 };
80
81 let file_b = match File::options().read(true).open(b) {
82 Ok(f) => Some(f),
83 Err(e) if matches!(e.kind(), ErrorKind::NotFound) => None,
84 Err(e) => return Err(e),
85 };
86
87 match (file_a, file_b) {
88 (Some(a), Some(b)) => {
89 let meta_a = a.metadata().expect("get file metadata");
90 let meta_b = b.metadata().expect("get file metadata");
91
92 if meta_a.len() != meta_b.len()
93 || meta_a.is_symlink()
94 || meta_b.is_symlink()
95 || meta_a.file_type() != meta_b.file_type()
96 {
97 Ok(false)
98 } else {
99 Self::internal_eq(&a, &b)
100 .map(bool::not)
101 }
102 },
103
104 (None, None) => Ok(true),
105 _ => Ok(false),
106 }
107 },
108
109 Self::Subprocess { command, args } => {
110 let status = Command::new(command)
111 .args(args)
112 .arg(a)
113 .arg(b)
114 .status()?;
115
116 match status.code() {
117 Some(0) => Ok(false),
118 Some(1) => Ok(true),
119 Some(_) => Err(std::io::Error::from(ErrorKind::Other)),
120 None => Err(std::io::Error::from(ErrorKind::Interrupted)),
121 }
122 },
123
124 }
125 }
126
127 fn internal_eq(a: &File, b: &File) -> Result<bool, std::io::Error> {
136 let mut buf_reader_a = BufReader::new(a);
137 let mut buf_reader_b = BufReader::new(b);
138
139 loop {
140 let buf_a = buf_reader_a.fill_buf()?;
141 let buf_b = buf_reader_b.fill_buf()?;
142
143 if buf_a.is_empty() && buf_b.is_empty() {
144 return Ok(true);
145 }
146
147 let read_len = if buf_a.len() <= buf_b.len() {
148 buf_a.len()
149 } else {
150 buf_b.len()
151 };
152
153 if buf_a[0..read_len] != buf_b[0..read_len] {
154 return Ok(false);
155 }
156
157 buf_reader_a.consume(read_len);
158 buf_reader_b.consume(read_len);
159 }
160 }
161}