1use std::fs::{self, File, OpenOptions};
2use std::io::{self, Read, Write};
3use std::path::{Path, PathBuf};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8
9pub const SECRET_FILE_MODE: u32 = 0o600;
10
11pub fn sha256_file(path: &Path) -> io::Result<String> {
12 let mut file = File::open(path)?;
13 let mut hasher = Sha256::new();
14 let mut buf = [0u8; 8192];
15
16 loop {
17 let read = file.read(&mut buf)?;
18 if read == 0 {
19 break;
20 }
21 hasher.update(&buf[..read]);
22 }
23
24 Ok(hex_encode(&hasher.finalize()))
25}
26
27pub fn write_atomic(path: &Path, contents: &[u8], mode: u32) -> io::Result<()> {
28 if let Some(parent) = path.parent() {
29 fs::create_dir_all(parent)?;
30 }
31
32 let mut attempt = 0u32;
33 loop {
34 let tmp_path = temp_path(path, attempt);
35 match OpenOptions::new()
36 .write(true)
37 .create_new(true)
38 .open(&tmp_path)
39 {
40 Ok(mut file) => {
41 file.write_all(contents)?;
42 let _ = file.flush();
43 set_permissions(&tmp_path, mode)?;
44 drop(file);
45
46 fs::rename(&tmp_path, path)?;
47 set_permissions(path, mode)?;
48 return Ok(());
49 }
50 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
51 attempt += 1;
52 if attempt > 10 {
53 return Err(io::Error::new(
54 io::ErrorKind::AlreadyExists,
55 format!("failed to create unique temp file for {}", path.display()),
56 ));
57 }
58 }
59 Err(err) => return Err(err),
60 }
61 }
62}
63
64pub fn write_timestamp(path: &Path, iso: Option<&str>) -> io::Result<()> {
65 if let Some(parent) = path.parent() {
66 fs::create_dir_all(parent)?;
67 }
68
69 if let Some(raw) = iso {
70 let trimmed = raw.split(&['\n', '\r'][..]).next().unwrap_or("");
71 if !trimmed.is_empty() {
72 fs::write(path, trimmed)?;
73 return Ok(());
74 }
75 }
76
77 match fs::remove_file(path) {
78 Ok(()) => Ok(()),
79 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()),
80 Err(err) => Err(err),
81 }
82}
83
84#[cfg(unix)]
85fn set_permissions(path: &Path, mode: u32) -> io::Result<()> {
86 let perm = fs::Permissions::from_mode(mode);
87 fs::set_permissions(path, perm)
88}
89
90#[cfg(not(unix))]
91fn set_permissions(_path: &Path, _mode: u32) -> io::Result<()> {
92 Ok(())
93}
94
95fn temp_path(path: &Path, attempt: u32) -> PathBuf {
96 let filename = path
97 .file_name()
98 .and_then(|name| name.to_str())
99 .unwrap_or("tmp");
100 let pid = std::process::id();
101 let nanos = SystemTime::now()
102 .duration_since(UNIX_EPOCH)
103 .map(|duration| duration.as_nanos())
104 .unwrap_or(0);
105 let tmp_name = format!(".{filename}.tmp-{pid}-{nanos}-{attempt}");
106 path.with_file_name(tmp_name)
107}
108
109fn hex_encode(bytes: &[u8]) -> String {
110 const HEX: &[u8; 16] = b"0123456789abcdef";
111
112 let mut out = String::with_capacity(bytes.len() * 2);
113 for byte in bytes {
114 out.push(HEX[(byte >> 4) as usize] as char);
115 out.push(HEX[(byte & 0x0f) as usize] as char);
116 }
117 out
118}
119
120struct Sha256 {
121 state: [u32; 8],
122 buffer: [u8; 64],
123 buffer_len: usize,
124 total_len: u64,
125}
126
127impl Sha256 {
128 fn new() -> Self {
129 Self {
130 state: [
131 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
132 0x5be0cd19,
133 ],
134 buffer: [0u8; 64],
135 buffer_len: 0,
136 total_len: 0,
137 }
138 }
139
140 fn update(&mut self, mut data: &[u8]) {
141 self.total_len = self.total_len.wrapping_add(data.len() as u64);
142
143 if self.buffer_len > 0 {
144 let need = 64 - self.buffer_len;
145 let take = need.min(data.len());
146 self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&data[..take]);
147 self.buffer_len += take;
148 data = &data[take..];
149
150 if self.buffer_len == 64 {
151 let block = self.buffer;
152 self.compress(&block);
153 self.buffer_len = 0;
154 }
155 }
156
157 while data.len() >= 64 {
158 let block: [u8; 64] = data[..64].try_into().expect("64-byte block");
159 self.compress(&block);
160 data = &data[64..];
161 }
162
163 if !data.is_empty() {
164 self.buffer[..data.len()].copy_from_slice(data);
165 self.buffer_len = data.len();
166 }
167 }
168
169 fn finalize(mut self) -> [u8; 32] {
170 let bit_len = self.total_len.wrapping_mul(8);
171
172 self.buffer[self.buffer_len] = 0x80;
173 self.buffer_len += 1;
174
175 if self.buffer_len > 56 {
176 self.buffer[self.buffer_len..].fill(0);
177 let block = self.buffer;
178 self.compress(&block);
179 self.buffer = [0u8; 64];
180 self.buffer_len = 0;
181 }
182
183 self.buffer[self.buffer_len..56].fill(0);
184 self.buffer[56..64].copy_from_slice(&bit_len.to_be_bytes());
185 let block = self.buffer;
186 self.compress(&block);
187
188 let mut out = [0u8; 32];
189 for (index, chunk) in out.chunks_exact_mut(4).enumerate() {
190 chunk.copy_from_slice(&self.state[index].to_be_bytes());
191 }
192 out
193 }
194
195 fn compress(&mut self, block: &[u8; 64]) {
196 let mut schedule = [0u32; 64];
197 for (index, word) in schedule.iter_mut().take(16).enumerate() {
198 let offset = index * 4;
199 *word = u32::from_be_bytes([
200 block[offset],
201 block[offset + 1],
202 block[offset + 2],
203 block[offset + 3],
204 ]);
205 }
206
207 for index in 16..64 {
208 let s0 = schedule[index - 15].rotate_right(7)
209 ^ schedule[index - 15].rotate_right(18)
210 ^ (schedule[index - 15] >> 3);
211 let s1 = schedule[index - 2].rotate_right(17)
212 ^ schedule[index - 2].rotate_right(19)
213 ^ (schedule[index - 2] >> 10);
214 schedule[index] = schedule[index - 16]
215 .wrapping_add(s0)
216 .wrapping_add(schedule[index - 7])
217 .wrapping_add(s1);
218 }
219
220 let mut a = self.state[0];
221 let mut b = self.state[1];
222 let mut c = self.state[2];
223 let mut d = self.state[3];
224 let mut e = self.state[4];
225 let mut f = self.state[5];
226 let mut g = self.state[6];
227 let mut h = self.state[7];
228
229 for index in 0..64 {
230 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
231 let choice = (e & f) ^ ((!e) & g);
232 let t1 = h
233 .wrapping_add(s1)
234 .wrapping_add(choice)
235 .wrapping_add(ROUND_CONSTANTS[index])
236 .wrapping_add(schedule[index]);
237 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
238 let majority = (a & b) ^ (a & c) ^ (b & c);
239 let t2 = s0.wrapping_add(majority);
240
241 h = g;
242 g = f;
243 f = e;
244 e = d.wrapping_add(t1);
245 d = c;
246 c = b;
247 b = a;
248 a = t1.wrapping_add(t2);
249 }
250
251 self.state[0] = self.state[0].wrapping_add(a);
252 self.state[1] = self.state[1].wrapping_add(b);
253 self.state[2] = self.state[2].wrapping_add(c);
254 self.state[3] = self.state[3].wrapping_add(d);
255 self.state[4] = self.state[4].wrapping_add(e);
256 self.state[5] = self.state[5].wrapping_add(f);
257 self.state[6] = self.state[6].wrapping_add(g);
258 self.state[7] = self.state[7].wrapping_add(h);
259 }
260}
261
262const ROUND_CONSTANTS: [u32; 64] = [
263 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
264 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
265 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
266 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
267 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
268 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
269 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
270 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
271];