add_determinism/add_det/handlers/
ar.rs

1/* SPDX-License-Identifier: GPL-3.0-or-later */
2
3use 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
27// Like `read_exact`, but EOF is not an error.
28fn 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    // End of stream is OK, we return an empty buffer
36    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            // https://en.wikipedia.org/wiki/Ar_(Unix)
84            // from   to     Name                      Format
85            // 0      15     File name                 ASCII
86            // 16     27     File modification date    Decimal
87            // 28     33     Owner ID                  Decimal
88            // 34     39     Group ID                  Decimal
89            // 40     47     File mode                 Octal
90            // 48     57     File size in bytes        Decimal
91            // 58     59     File magic                \140\012
92
93            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                // System V/GNU table of long filenames
105                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}