add_determinism/add_det/handlers/
ar.rs1use anyhow::Result;
4use log::debug;
5use std::fs::File;
6use std::io::{BufReader, BufWriter, Read, Seek, Write, ErrorKind};
7use std::path::Path;
8use std::sync::Arc;
9
10use super::{config, InputOutputHelper};
11
12const MAGIC: &[u8] = b"!<arch>\n";
13
14const FILE_HEADER_LENGTH: usize = 60;
15const FILE_HEADER_MAGIC: &[u8] = &[0o140, 0o012];
16
17pub struct Ar {
18 config: Arc<config::Config>,
19}
20
21impl Ar {
22 pub fn boxed(config: &Arc<config::Config>) -> Box<dyn super::Processor + Send + Sync> {
23 Box::new(Self { config: config.clone() })
24 }
25}
26
27fn read_exact_or_zero(
29 input: &mut BufReader<File>,
30 buf: &mut [u8],
31) -> Result<bool> {
32
33 let pos = input.stream_position()?;
34
35 let n = input.read(buf)?;
37 if n == 0 {
38 return Ok(false);
39 }
40 if let Err(e) = input.read_exact(&mut buf[n..]) {
41 if e.kind() == ErrorKind::UnexpectedEof {
42 return Err(super::Error::UnexpectedEOF(pos, FILE_HEADER_LENGTH).into());
43 } else {
44 return Err(e.into());
45 }
46 }
47 Ok(true)
48}
49
50impl super::Processor for Ar {
51 fn name(&self) -> &str {
52 "ar"
53 }
54
55 fn filter(&self, path: &Path) -> Result<bool> {
56 Ok(self.config.ignore_extension || path.extension().is_some_and(|x| x == "a"))
57 }
58
59 fn process(&self, input_path: &Path) -> Result<super::ProcessResult> {
60 let mut have_mod = false;
61 let (mut io, mut input) = InputOutputHelper::open(input_path, self.config.check, true)?;
62
63 let mut buf = [0; MAGIC.len()];
64 input.read_exact(&mut buf)?;
65 if buf != MAGIC {
66 return Err(super::Error::BadMagic(0, buf.to_vec(), MAGIC).into());
67 }
68
69 io.open_output(false)?;
70 let mut output = BufWriter::new(io.output.as_mut().unwrap().as_file_mut());
71
72 output.write_all(&buf)?;
73
74 loop {
75 let pos = input.stream_position()?;
76 let mut buf = [0; FILE_HEADER_LENGTH];
77
78 debug!("{}: reading file header at offset {pos}", io.input_path.display());
79 if !read_exact_or_zero(&mut input, &mut buf)? {
80 break;
81 }
82
83 if &buf[58..] != FILE_HEADER_MAGIC {
94 return Err(
95 super::Error::BadMagic(pos, buf[58..].to_vec(), FILE_HEADER_MAGIC).into());
96 }
97
98 let name = std::str::from_utf8(&buf[0..16])?.trim_end_matches(' ');
99
100 let size = std::str::from_utf8(&buf[48..58])?.trim_end_matches(' ');
101 let size = size.parse::<u32>()?;
102
103 if name == "//" {
104 debug!("{}: long filename index, size={}", io.input_path.display(), size);
106 } else {
107 let mtime = std::str::from_utf8(&buf[16..28])?.trim_end_matches(' ');
108 let mtime = mtime.parse::<i64>()?;
109
110 let uid = std::str::from_utf8(&buf[28..34])?.trim_end_matches(' ');
111 let uid = uid.parse::<u64>()?;
112
113 let gid = std::str::from_utf8(&buf[34..40])?.trim_end_matches(' ');
114 let gid = gid.parse::<u64>()?;
115
116 let mode = std::str::from_utf8(&buf[40..48])?.trim_end_matches(' ');
117 let mode = mode.parse::<u64>()?;
118
119 debug!("{}: file {:?}, mtime={}, {}:{}, mode={:o}, size={}",
120 io.input_path.display(), name, mtime, uid, gid, mode, size);
121
122 if self.config.source_date_epoch.is_some() && mtime > self.config.source_date_epoch.unwrap() {
123 let source_date_epoch_str = format!("{:<12}", self.config.source_date_epoch.unwrap());
124
125 buf[16..28].copy_from_slice(source_date_epoch_str.as_bytes());
126 have_mod = true;
127 }
128
129 if uid != 0 || gid != 0 {
130 buf[28..34].copy_from_slice(b"0 ");
131 buf[34..40].copy_from_slice(b"0 ");
132 have_mod = true;
133 }
134 }
135
136 output.write_all(&buf)?;
137
138 let padded_size = size + size % 2;
139
140 let mut buf = vec![0; padded_size.try_into().unwrap()];
141 input.read_exact(&mut buf)?;
142
143 output.write_all(&buf)?;
144 }
145
146 output.flush()?;
147 drop(output);
148 io.finalize(have_mod)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn filter_a() {
158 let cfg = Arc::new(config::Config::empty(0, true));
159 let h = Ar::boxed(&cfg);
160
161 assert!( h.filter(Path::new("/some/path/libfoobar.a")).unwrap());
162 assert!(!h.filter(Path::new("/some/path/libfoobar.aa")).unwrap());
163 assert!( h.filter(Path::new("/some/path/libfoobar.a.a")).unwrap());
164 assert!(!h.filter(Path::new("/some/path/libfoobara")).unwrap());
165 assert!(!h.filter(Path::new("/some/path/a")).unwrap());
166 assert!(!h.filter(Path::new("/some/path/a_a")).unwrap());
167 assert!(!h.filter(Path::new("/")).unwrap());
168 }
169}