apple_bom/
path.rs

1// Copyright 2022 Gregory Szorc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use {
9    crate::error::Error,
10    chrono::{DateTime, TimeZone, Utc},
11    simple_file_manifest::{
12        S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
13    },
14    std::ffi::CString,
15};
16
17/// The type of path in a BOM.
18#[derive(Clone, Copy, Debug)]
19pub enum BomPathType {
20    /// A regular file.
21    File,
22
23    /// A directory.
24    Directory,
25
26    /// A symlink.
27    Link,
28
29    /// A device.
30    Dev,
31
32    /// Some other type we don't know about.
33    Other(u8),
34}
35
36impl From<u8> for BomPathType {
37    fn from(v: u8) -> Self {
38        match v {
39            1 => Self::File,
40            2 => Self::Directory,
41            3 => Self::Link,
42            4 => Self::Dev,
43            _ => Self::Other(v),
44        }
45    }
46}
47
48impl From<BomPathType> for u8 {
49    fn from(t: BomPathType) -> Self {
50        match t {
51            BomPathType::File => 1,
52            BomPathType::Directory => 2,
53            BomPathType::Link => 3,
54            BomPathType::Dev => 4,
55            BomPathType::Other(v) => v,
56        }
57    }
58}
59
60/// Represents a full path in a BOM.
61///
62/// This is a higher-level data structure with a Rust friendly API. It has
63/// fields for all the data constituting a path in a BOM.
64#[derive(Clone, Debug)]
65pub struct BomPath {
66    /// The type of path.
67    pub(crate) path_type: BomPathType,
68
69    /// The full path.
70    pub(crate) path: String,
71
72    pub(crate) file_mode: u16,
73    pub(crate) user_id: u32,
74    pub(crate) group_id: u32,
75    pub(crate) mtime: DateTime<Utc>,
76    pub(crate) size: usize,
77    pub(crate) crc32: Option<u32>,
78    pub(crate) link_name: Option<String>,
79}
80
81impl BomPath {
82    /// Construct an instance from a low-level BOM record.
83    pub fn from_record(
84        path: String,
85        record: &crate::format::BomBlockPathRecord,
86    ) -> Result<Self, Error> {
87        let mtime = Utc
88            .timestamp_opt(record.mtime as _, 0)
89            .single()
90            .ok_or(Error::BadTime)?;
91
92        let path_type = BomPathType::from(record.path_type);
93
94        let crc32 = match path_type {
95            BomPathType::File | BomPathType::Link => Some(record.checksum_or_type),
96            BomPathType::Directory | BomPathType::Dev | BomPathType::Other(_) => None,
97        };
98
99        Ok(Self {
100            path_type,
101            path,
102            file_mode: record.mode,
103            user_id: record.user,
104            group_id: record.group,
105            mtime,
106            size: record.size as _,
107            crc32,
108            link_name: record.string_link_name(),
109        })
110    }
111
112    /// The type of this path.
113    pub fn path_type(&self) -> BomPathType {
114        self.path_type
115    }
116
117    /// The full path of this instance.
118    pub fn path(&self) -> &str {
119        &self.path
120    }
121
122    /// File mode bitfield.
123    pub fn file_mode(&self) -> u16 {
124        self.file_mode
125    }
126
127    /// Set the file mode to an explicit value.
128    pub fn set_file_mode(&mut self, mode: u16) -> u16 {
129        let old = self.file_mode;
130        self.file_mode = mode;
131        old
132    }
133
134    /// Obtain the symbolic file mode for this path.
135    ///
136    /// e.g. a string like `drwxr-xr-x`.
137    pub fn symbolic_mode(&self) -> String {
138        let mut mode = String::with_capacity(10);
139
140        mode.push(match self.path_type {
141            BomPathType::Directory => 'd',
142            BomPathType::File => '-',
143            BomPathType::Link => 'l',
144            BomPathType::Dev => '?',
145            BomPathType::Other(_) => '?',
146        });
147
148        mode.push(if self.file_mode as u32 & S_IRUSR != 0 {
149            'r'
150        } else {
151            '-'
152        });
153        mode.push(if self.file_mode as u32 & S_IWUSR != 0 {
154            'w'
155        } else {
156            '-'
157        });
158        mode.push(if self.file_mode as u32 & S_IXUSR != 0 {
159            'x'
160        } else {
161            '-'
162        });
163        mode.push(if self.file_mode as u32 & S_IRGRP != 0 {
164            'r'
165        } else {
166            '-'
167        });
168        mode.push(if self.file_mode as u32 & S_IWGRP != 0 {
169            'w'
170        } else {
171            '-'
172        });
173        mode.push(if self.file_mode as u32 & S_IXGRP != 0 {
174            'x'
175        } else {
176            '-'
177        });
178        mode.push(if self.file_mode as u32 & S_IROTH != 0 {
179            'r'
180        } else {
181            '-'
182        });
183        mode.push(if self.file_mode as u32 & S_IWOTH != 0 {
184            'w'
185        } else {
186            '-'
187        });
188        mode.push(if self.file_mode as u32 & S_IXOTH != 0 {
189            'x'
190        } else {
191            '-'
192        });
193
194        mode
195    }
196
197    /// Numeric user identifier (UID) that owns this path.
198    pub fn user_id(&self) -> u32 {
199        self.user_id
200    }
201
202    /// Set the user identifier (UID) that owns this path.
203    pub fn set_user_id(&mut self, uid: u32) -> u32 {
204        let old = self.user_id;
205        self.user_id = uid;
206        old
207    }
208
209    /// Numeric group identifier (GID) that owns this path.
210    pub fn group_id(&self) -> u32 {
211        self.group_id
212    }
213
214    /// Set the group identifier (GID) that owns this path.
215    pub fn set_group_id(&mut self, gid: u32) -> u32 {
216        let old = self.user_id;
217        self.group_id = gid;
218        old
219    }
220
221    /// Modified time of this path.
222    pub fn modified_time(&self) -> &DateTime<Utc> {
223        &self.mtime
224    }
225
226    /// Set the modified time of this path.
227    pub fn set_modified_time(&mut self, mtime: DateTime<Utc>) -> DateTime<Utc> {
228        let old = self.mtime;
229        self.mtime = mtime;
230        old
231    }
232
233    /// Size of path in bytes.
234    pub fn size(&self) -> usize {
235        self.size
236    }
237
238    /// Set the size of this path.
239    pub fn set_size(&mut self, size: usize) -> usize {
240        let old = self.size;
241        self.size = size;
242        old
243    }
244
245    /// CRC32 of this path.
246    ///
247    /// Should only be set for files and links.
248    pub fn crc32(&self) -> Option<u32> {
249        self.crc32
250    }
251
252    /// Set the CRC32 of this path.
253    pub fn set_crc32(&mut self, value: Option<u32>) -> Option<u32> {
254        let old = self.crc32;
255        self.crc32 = value;
256        old
257    }
258
259    /// The path that this link refers to.
260    pub fn link_name(&self) -> Option<&str> {
261        self.link_name.as_deref()
262    }
263
264    /// The path that this link refers to, as a [CString].
265    pub fn link_name_cstring(&self) -> Option<CString> {
266        if let Some(link_name) = &self.link_name {
267            let mut data = Vec::<u8>::with_capacity(link_name.as_bytes().len() + 1);
268            data.extend(link_name.as_bytes());
269            data.push(0);
270
271            Some(CString::new(data).expect("should be valid C string"))
272        } else {
273            None
274        }
275    }
276
277    /// Set the link name for this path.
278    pub fn set_link_name(&mut self, value: Option<String>) -> Option<String> {
279        let old = self.link_name.clone();
280        self.link_name = value;
281        old
282    }
283}