libcdio_rs/iso9660/
rock.rs1use std::{ffi::CStr, mem::MaybeUninit};
21
22use file_mode::Mode;
23use libcdio_sys::{bool_3way_t_nope, bool_3way_t_yep, iso_rock_time_s};
24use thiserror::Error;
25use time::OffsetDateTime;
26
27use crate::iso9660::{Iso, entry::IsoEntry, util};
28
29impl Iso {
30 pub fn have_rock_ridge(&self, file_limit: Option<u64>) -> Result<bool, RockRidgeSearchError> {
35 let file_limit = file_limit.unwrap_or(u64::MAX);
36 let result = unsafe { libcdio_sys::iso9660_have_rr(self.ptr.as_ptr(), file_limit) };
37
38 #[allow(non_upper_case_globals)]
39 match result {
40 bool_3way_t_yep => Ok(true),
41 bool_3way_t_nope => Ok(false),
42 _ => Err(RockRidgeSearchError),
43 }
44 }
45}
46
47#[non_exhaustive]
48#[derive(Debug, Error)]
49#[error("error searching for rock ridge extensions: file limit reached")]
50pub struct RockRidgeSearchError;
51
52impl IsoEntry<'_> {
53 pub fn rock_ridge(&self) -> Option<RockRidgeAttributes> {
55 let rock = unsafe { (*self.stat.as_ptr()).rr };
56 if rock.b3_rock != bool_3way_t_yep {
57 return None;
58 }
59
60 Some(RockRidgeAttributes {
61 create_time: convert_rock_timefield(rock.create),
62 group_id: rock.st_gid,
63 hard_links: rock.st_nlinks,
64 mode: Mode::new(rock.st_mode, u32::MAX),
65 modify_time: convert_rock_timefield(rock.modify),
66 symlink_to: {
67 if rock.psz_symlink.is_null() {
68 None
69 } else {
70 let symlink = unsafe { CStr::from_ptr(rock.psz_symlink) };
71 symlink
72 .to_str()
73 .ok()
74 .filter(|link| !link.is_empty())
75 .map(ToString::to_string)
76 }
77 },
78 user_id: rock.st_uid,
79 })
80 }
81}
82
83#[derive(Clone, Debug)]
85#[non_exhaustive]
86pub struct RockRidgeAttributes {
87 pub create_time: Option<OffsetDateTime>,
88 pub group_id: u32,
89 pub hard_links: u32,
90 pub mode: Mode,
91 pub modify_time: Option<OffsetDateTime>,
92 pub symlink_to: Option<String>,
93 pub user_id: u32,
94}
95
96fn convert_rock_timefield(field: iso_rock_time_s) -> Option<OffsetDateTime> {
97 if !field.b_used {
98 return None;
99 };
100
101 let mut tm = MaybeUninit::uninit();
102 if field.b_longdate {
103 unsafe { libcdio_sys::iso9660_get_ltime(&raw const field.t.ltime, tm.as_mut_ptr()) };
105 } else {
106 unsafe { libcdio_sys::iso9660_get_dtime(&raw const field.t.dtime, true, tm.as_mut_ptr()) };
108 }
109 let tm = unsafe { tm.assume_init() };
111
112 util::convert_tm_local(tm).ok()
113}
114
115#[cfg(test)]
116mod tests {
117 use time::macros::datetime;
118
119 use crate::iso9660::tests::{test_joliet_file, test_rockridge_file};
120
121 use super::*;
122
123 #[test]
124 fn have_rock_ridge() {
125 let iso = Iso::new(test_rockridge_file()).unwrap();
126 assert!(iso.have_rock_ridge(None).unwrap());
127 }
128
129 #[test]
130 fn rock_ridge() {
131 let iso = Iso::new(test_rockridge_file()).unwrap();
132 let entry = iso.entry("/COPYING".to_string()).unwrap();
133 assert!(entry.rock_ridge().is_some());
134
135 let iso = Iso::new(test_joliet_file()).unwrap();
136 let entry = iso.entry("/libcdio/COPYING".to_string()).unwrap();
137 assert!(entry.rock_ridge().is_none());
138 }
139
140 #[test]
141 fn mode() {
142 let iso = Iso::new(test_rockridge_file()).unwrap();
143
144 let entry = iso.entry("/zero".to_string()).unwrap();
145 let mode = entry.rock_ridge().unwrap().mode;
146 assert_eq!(&mode.to_string(), "cr--r--r--");
147
148 let entry = iso.entry("/fd0".to_string()).unwrap();
149 let mode = entry.rock_ridge().unwrap().mode;
150 assert_eq!(&mode.to_string(), "br--r--r--");
151
152 let entry = iso.entry("/Copy2".to_string()).unwrap();
153 let mode = entry.rock_ridge().unwrap().mode;
154 assert_eq!(&mode.to_string(), "lr-xr-xr-x");
155
156 let entry = iso.entry("/copy".to_string()).unwrap();
157 let mode = entry.rock_ridge().unwrap().mode;
158 assert_eq!(&mode.to_string(), "dr-xr-xr-x");
159 }
160
161 #[test]
162 fn symlink_to() {
163 let iso = Iso::new(test_rockridge_file()).unwrap();
164
165 let entry = iso.entry("/COPYING".to_string()).unwrap();
166 let rock = entry.rock_ridge().unwrap();
167 assert!(rock.symlink_to.is_none());
168
169 let entry = iso.entry("/Copy2".to_string()).unwrap();
170 let rock = entry.rock_ridge().unwrap();
171 assert_eq!(rock.symlink_to.unwrap(), "COPYING");
172
173 let entry = iso.entry("/tmp/COPYING".to_string()).unwrap();
174 let rock = entry.rock_ridge().unwrap();
175 assert_eq!(rock.symlink_to.unwrap(), "../copying/COPYING");
176 }
177
178 #[test]
179 fn hard_links() {
180 let iso = Iso::new(test_rockridge_file()).unwrap();
181 let entry = iso.entry("/COPYING".to_string()).unwrap();
182 let rock = entry.rock_ridge().unwrap();
183 assert_eq!(rock.hard_links, 1);
184
185 let entry = iso.entry("/copy".to_string()).unwrap();
186 let rock = entry.rock_ridge().unwrap();
187 assert_eq!(rock.hard_links, 2);
188 }
189
190 #[test]
191 fn user_id() {
192 let iso = Iso::new(test_rockridge_file()).unwrap();
193 let entry = iso.entry("/COPYING".to_string()).unwrap();
194 let rock = entry.rock_ridge().unwrap();
195 assert_eq!(rock.user_id, 0);
196 }
197
198 #[test]
199 fn group_id() {
200 let iso = Iso::new(test_rockridge_file()).unwrap();
201 let entry = iso.entry("/COPYING".to_string()).unwrap();
202 let rock = entry.rock_ridge().unwrap();
203 assert_eq!(rock.group_id, 0);
204 }
205
206 #[test]
207 fn time() {
208 let iso = Iso::new(test_rockridge_file()).unwrap();
209 let entry = iso.entry("/COPYING".to_string()).unwrap();
210 let rock = entry.rock_ridge().unwrap();
211 assert_eq!(
212 rock.modify_time.unwrap(),
213 datetime!(2005-03-05 20:55:51.0 +05:30:00)
214 );
215 }
216}