1use std::{fs::Metadata, io::Write};
4
5use cfg_if::cfg_if;
6use tokio::io::AsyncWrite;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct ExtraFields {
16 pub(crate) values: Vec<ExtraField>,
17}
18
19impl ExtraFields {
20 pub unsafe fn new<I>(fields: I) -> Self
26 where
27 I: IntoIterator<Item=ExtraField>,
28 {
29 Self {
30 values: fields.into_iter().collect(),
31 }
32 }
33
34 pub fn new_from_fs(metadata: &Metadata) -> Self {
40 cfg_if! {
41 if #[cfg(target_os = "windows")] {
42 Self::new_windows(metadata)
43 } else if #[cfg(target_os = "linux")] {
44 Self::new_linux(metadata)
45 } else if #[cfg(all(unix, not(target_os = "linux")))] {
46 Self::new_unix(metadata)
47 } else {
48 Self::default()
49 }
50 }
51 }
52
53 #[cfg(target_os = "linux")]
54 fn new_linux(metadata: &Metadata) -> Self {
55 use std::os::linux::fs::MetadataExt;
56
57 let mod_time = Some(metadata.st_mtime() as i32);
58 let ac_time = Some(metadata.st_atime() as i32);
59 let cr_time = Some(metadata.st_ctime() as i32);
60
61 let uid = metadata.st_uid();
62 let gid = metadata.st_gid();
63
64 Self {
65 values: vec![
66 ExtraField::UnixExtendedTimestamp {
67 mod_time,
68 ac_time,
69 cr_time,
70 },
71 ExtraField::UnixAttrs { uid, gid },
72 ],
73 }
74 }
75
76 #[cfg(all(unix, not(target_os = "linux")))]
77 #[allow(dead_code)]
78 fn new_unix(metadata: &Metadata) -> Self {
79 use std::os::unix::fs::MetadataExt;
80
81 let mod_time = Some(metadata.mtime() as i32);
82 let ac_time = Some(metadata.atime() as i32);
83 let cr_time = Some(metadata.ctime() as i32);
84
85 let uid = metadata.uid();
86 let gid = metadata.gid();
87
88 Self {
89 values: vec![
90 ExtraField::UnixExtendedTimestamp {
91 mod_time,
92 ac_time,
93 cr_time,
94 },
95 ExtraField::UnixAttrs { uid, gid },
96 ],
97 }
98 }
99
100 #[cfg(target_os = "windows")]
101 fn new_windows(metadata: &Metadata) -> Self {
102 use std::os::windows::fs::MetadataExt;
103
104 let mtime = metadata.last_write_time();
105 let atime = metadata.last_access_time();
106 let ctime = metadata.creation_time();
107
108 Self {
109 values: vec![ExtraField::Ntfs {
110 mtime,
111 atime,
112 ctime,
113 }],
114 }
115 }
116
117 pub(crate) fn data_length<const CENTRAL_HEADER: bool>(&self) -> u16 {
118 self.values
119 .iter()
120 .map(|f| 4 + f.field_size::<CENTRAL_HEADER>())
121 .sum()
122 }
123
124 pub(crate) fn write<W: Write, const CENTRAL_HEADER: bool>(
125 &self,
126 writer: &mut W,
127 ) -> std::io::Result<()> {
128 for field in &self.values {
129 field.write::<_, CENTRAL_HEADER>(writer)?;
130 }
131 Ok(())
132 }
133
134 pub(crate) async fn write_with_tokio<W: AsyncWrite + Unpin, const CENTRAL_HEADER: bool>(
135 &self,
136 writer: &mut W,
137 ) -> std::io::Result<()> {
138 for field in &self.values {
139 field.write_with_tokio::<_, CENTRAL_HEADER>(writer).await?;
140 }
141 Ok(())
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum ExtraField {
150 Ntfs {
152 mtime: u64,
154 atime: u64,
156 ctime: u64,
158 },
159 UnixExtendedTimestamp {
162 mod_time: Option<i32>,
164 ac_time: Option<i32>,
166 cr_time: Option<i32>,
168 },
169 UnixAttrs {
171 uid: u32,
173 gid: u32,
175 },
176}
177
178const MOD_TIME_PRESENT: u8 = 1;
179const AC_TIME_PRESENT: u8 = 1 << 1;
180const CR_TIME_PRESENT: u8 = 1 << 2;
181
182impl ExtraField {
183 #[inline]
184 fn header_id(&self) -> u16 {
185 match self {
186 Self::Ntfs {
187 mtime: _,
188 atime: _,
189 ctime: _,
190 } => 0x000a,
191 Self::UnixExtendedTimestamp {
192 mod_time: _,
193 ac_time: _,
194 cr_time: _,
195 } => 0x5455,
196 Self::UnixAttrs { uid: _, gid: _ } => 0x7875,
197 }
198 }
199
200 #[inline]
201 const fn optional_field_size<T: Sized>(field: &Option<T>) -> u16 {
202 match field {
203 Some(_) => std::mem::size_of::<T>() as u16,
204 None => 0,
205 }
206 }
207
208 #[inline]
209 const fn field_size<const CENTRAL_HEADER: bool>(&self) -> u16 {
210 match self {
211 Self::Ntfs {
212 mtime: _,
213 atime: _,
214 ctime: _,
215 } => 32,
216 Self::UnixExtendedTimestamp {
217 mod_time,
218 ac_time,
219 cr_time,
220 } => {
221 1 + Self::optional_field_size(mod_time) + {
222 if !CENTRAL_HEADER {
223 Self::optional_field_size(ac_time) + Self::optional_field_size(cr_time)
224 } else {
225 0
226 }
227 }
228 }
229 Self::UnixAttrs { uid: _, gid: _ } => 11,
230 }
231 }
232
233 #[inline]
234 const fn if_present(val: Option<i32>, if_present: u8) -> u8 {
235 match val {
236 Some(_) => if_present,
237 None => 0,
238 }
239 }
240
241 const NTFS_FIELD_LEN: usize = 32;
242 const UNIX_ATTRS_LEN: usize = 11;
243
244 pub(crate) fn write<W: Write, const CENTRAL_HEADER: bool>(
245 self,
246 writer: &mut W,
247 ) -> std::io::Result<()> {
248 writer.write_all(&self.header_id().to_le_bytes())?;
250 writer.write_all(&self.field_size::<CENTRAL_HEADER>().to_le_bytes())?;
252
253 match self {
254 Self::Ntfs {
255 mtime,
256 atime,
257 ctime,
258 } => {
259 let mut field = [0; Self::NTFS_FIELD_LEN];
261 {
262 let mut field_buf: &mut [u8] = &mut field;
263
264 field_buf.write_all(&0_u32.to_le_bytes())?;
266
267 field_buf.write_all(&1_u16.to_le_bytes())?;
269 field_buf.write_all(&24_u16.to_le_bytes())?;
271
272 field_buf.write_all(&mtime.to_le_bytes())?;
274 field_buf.write_all(&atime.to_le_bytes())?;
276 field_buf.write_all(&ctime.to_le_bytes())?;
278 }
279
280 writer.write_all(&field)?;
281 }
282 Self::UnixExtendedTimestamp {
283 mod_time,
284 ac_time,
285 cr_time,
286 } => {
287 let flags = Self::if_present(mod_time, MOD_TIME_PRESENT)
288 | Self::if_present(ac_time, AC_TIME_PRESENT)
289 | Self::if_present(cr_time, CR_TIME_PRESENT);
290 writer.write_all(&[flags])?;
291 if let Some(mod_time) = mod_time {
292 writer.write_all(&mod_time.to_le_bytes())?;
293 }
294 if !CENTRAL_HEADER {
295 if let Some(ac_time) = ac_time {
296 writer.write_all(&ac_time.to_le_bytes())?;
297 }
298 if let Some(cr_time) = cr_time {
299 writer.write_all(&cr_time.to_le_bytes())?;
300 }
301 }
302 }
303 Self::UnixAttrs { uid, gid } => {
304 let mut field = [0; Self::UNIX_ATTRS_LEN];
306 {
307 let mut field_buf: &mut [u8] = &mut field;
308
309 field_buf.write_all(&[1])?;
311 field_buf.write_all(&[4])?;
313 field_buf.write_all(&uid.to_le_bytes())?;
315 field_buf.write_all(&[4])?;
317 field_buf.write_all(&gid.to_le_bytes())?;
319 }
320
321 writer.write_all(&field)?;
322 }
323 }
324
325 Ok(())
326 }
327
328 pub(crate) async fn write_with_tokio<W: AsyncWrite + Unpin, const CENTRAL_HEADER: bool>(
329 self,
330 writer: &mut W,
331 ) -> std::io::Result<()> {
332 use tokio::io::AsyncWriteExt;
333 writer.write_all(&self.header_id().to_le_bytes()).await?;
335 writer.write_all(&self.field_size::<CENTRAL_HEADER>().to_le_bytes()).await?;
337
338 match self {
339 Self::Ntfs {
340 mtime,
341 atime,
342 ctime,
343 } => {
344 let mut field = [0; Self::NTFS_FIELD_LEN];
346 {
347 let mut field_buf: &mut [u8] = &mut field;
348
349 field_buf.write_all(&0_u32.to_le_bytes())?;
351
352 field_buf.write_all(&1_u16.to_le_bytes())?;
354 field_buf.write_all(&24_u16.to_le_bytes())?;
356
357 field_buf.write_all(&mtime.to_le_bytes())?;
359 field_buf.write_all(&atime.to_le_bytes())?;
361 field_buf.write_all(&ctime.to_le_bytes())?;
363 }
364
365 writer.write_all(&field).await?;
366 }
367 Self::UnixExtendedTimestamp {
368 mod_time,
369 ac_time,
370 cr_time,
371 } => {
372 let flags = Self::if_present(mod_time, MOD_TIME_PRESENT)
373 | Self::if_present(ac_time, AC_TIME_PRESENT)
374 | Self::if_present(cr_time, CR_TIME_PRESENT);
375 writer.write_all(&[flags]).await?;
376 if let Some(mod_time) = mod_time {
377 writer.write_all(&mod_time.to_le_bytes()).await?;
378 }
379 if !CENTRAL_HEADER {
380 if let Some(ac_time) = ac_time {
381 writer.write_all(&ac_time.to_le_bytes()).await?;
382 }
383 if let Some(cr_time) = cr_time {
384 writer.write_all(&cr_time.to_le_bytes()).await?;
385 }
386 }
387 }
388 Self::UnixAttrs { uid, gid } => {
389 let mut field = [0; Self::UNIX_ATTRS_LEN];
391 {
392 let mut field_buf: &mut [u8] = &mut field;
393
394 field_buf.write_all(&[1])?;
396 field_buf.write_all(&[4])?;
398 field_buf.write_all(&uid.to_le_bytes())?;
400 field_buf.write_all(&[4])?;
402 field_buf.write_all(&gid.to_le_bytes())?;
404 }
405
406 writer.write_all(&field).await?;
407 }
408 }
409
410 Ok(())
411 }
412}