Skip to main content

libpna/entry/
meta.rs

1//! Metadata and permission types for archive entries.
2
3use crate::util::bounded::{LengthExceeded, str::BoundedString};
4use crate::{Duration, UnknownValueError};
5use std::io::{self, Read};
6use std::ops::Deref;
7use std::str;
8
9/// Metadata information about an entry.
10/// # Examples
11/// ```rust
12/// # use std::time::SystemTimeError;
13/// # fn main() -> Result<(), SystemTimeError> {
14/// use libpna::{Duration, Metadata};
15///
16/// let since_unix_epoch = Duration::seconds(1000);
17/// let metadata = Metadata::new()
18///     .with_accessed(Some(since_unix_epoch))
19///     .with_created(Some(since_unix_epoch))
20///     .with_modified(Some(since_unix_epoch));
21/// # Ok(())
22/// # }
23/// ```
24#[allow(deprecated)]
25#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
26pub struct Metadata {
27    pub(crate) raw_file_size: Option<u128>,
28    pub(crate) compressed_size: usize,
29    pub(crate) created: Option<Duration>,
30    pub(crate) modified: Option<Duration>,
31    pub(crate) accessed: Option<Duration>,
32    pub(crate) permission: Option<Permission>,
33    pub(crate) link_target_type: Option<LinkTargetType>,
34    pub(crate) owner_uid: Option<OwnerUid>,
35    pub(crate) owner_gid: Option<OwnerGid>,
36    pub(crate) owner_user_name: Option<OwnerUserName>,
37    pub(crate) owner_group_name: Option<OwnerGroupName>,
38    pub(crate) owner_user_sid: Option<OwnerUserSid>,
39    pub(crate) owner_group_sid: Option<OwnerGroupSid>,
40    pub(crate) permission_mode: Option<PermissionMode>,
41}
42
43impl Metadata {
44    /// Creates a new [`Metadata`].
45    #[inline]
46    pub const fn new() -> Self {
47        Self {
48            raw_file_size: Some(0),
49            compressed_size: 0,
50            created: None,
51            modified: None,
52            accessed: None,
53            permission: None,
54            link_target_type: None,
55            owner_uid: None,
56            owner_gid: None,
57            owner_user_name: None,
58            owner_group_name: None,
59            owner_user_sid: None,
60            owner_group_sid: None,
61            permission_mode: None,
62        }
63    }
64
65    /// Sets the created time as the duration since the Unix epoch.
66    ///
67    /// # Examples
68    /// ```rust
69    /// # use std::time::SystemTimeError;
70    /// # fn main() -> Result<(), SystemTimeError> {
71    /// use libpna::{Duration, Metadata};
72    ///
73    /// let since_unix_epoch = Duration::seconds(1000);
74    /// let metadata = Metadata::new().with_created(Some(since_unix_epoch));
75    /// # Ok(())
76    /// # }
77    /// ```
78    #[inline]
79    pub const fn with_created(mut self, created: Option<Duration>) -> Self {
80        self.created = created;
81        self
82    }
83
84    /// Sets the modified time as the duration since the Unix epoch.
85    ///
86    /// # Examples
87    /// ```rust
88    /// # use std::time::SystemTimeError;
89    /// # fn main() -> Result<(), SystemTimeError> {
90    /// use libpna::{Duration, Metadata};
91    ///
92    /// let since_unix_epoch = Duration::seconds(1000);
93    /// let metadata = Metadata::new().with_modified(Some(since_unix_epoch));
94    /// # Ok(())
95    /// # }
96    /// ```
97    #[inline]
98    pub const fn with_modified(mut self, modified: Option<Duration>) -> Self {
99        self.modified = modified;
100        self
101    }
102
103    /// Sets the accessed time as the duration since the Unix epoch.
104    ///
105    /// # Examples
106    /// ```rust
107    /// # use std::time::SystemTimeError;
108    /// # fn main() -> Result<(), SystemTimeError> {
109    /// use libpna::{Duration, Metadata};
110    ///
111    /// let since_unix_epoch = Duration::seconds(1000);
112    /// let metadata = Metadata::new().with_accessed(Some(since_unix_epoch));
113    /// # Ok(())
114    /// # }
115    /// ```
116    #[inline]
117    pub const fn with_accessed(mut self, accessed: Option<Duration>) -> Self {
118        self.accessed = accessed;
119        self
120    }
121
122    /// Sets the permission of the entry.
123    #[deprecated(
124        since = "0.34.0",
125        note = "the fPRM chunk is superseded by the owner facet chunks; use Metadata::with_owner_uid/with_owner_gid/with_owner_user_name/with_owner_group_name/with_owner_user_sid/with_owner_group_sid/with_permission_mode"
126    )]
127    #[allow(deprecated)]
128    #[inline]
129    pub fn with_permission(mut self, permission: Option<Permission>) -> Self {
130        self.permission = permission;
131        self
132    }
133
134    /// Sets the owner user id facet (`fUId`).
135    #[inline]
136    pub fn with_owner_uid(mut self, value: Option<OwnerUid>) -> Self {
137        self.owner_uid = value;
138        self
139    }
140    /// Sets the owner group id facet (`fGId`).
141    #[inline]
142    pub fn with_owner_gid(mut self, value: Option<OwnerGid>) -> Self {
143        self.owner_gid = value;
144        self
145    }
146    /// Sets the owner user name facet (`fONm`).
147    #[inline]
148    pub fn with_owner_user_name(mut self, value: Option<OwnerUserName>) -> Self {
149        self.owner_user_name = value;
150        self
151    }
152    /// Sets the owner group name facet (`fGNm`).
153    #[inline]
154    pub fn with_owner_group_name(mut self, value: Option<OwnerGroupName>) -> Self {
155        self.owner_group_name = value;
156        self
157    }
158    /// Sets the owner user SID facet (`fOSi`).
159    #[inline]
160    pub fn with_owner_user_sid(mut self, value: Option<OwnerUserSid>) -> Self {
161        self.owner_user_sid = value;
162        self
163    }
164    /// Sets the owner group SID facet (`fGSi`).
165    #[inline]
166    pub fn with_owner_group_sid(mut self, value: Option<OwnerGroupSid>) -> Self {
167        self.owner_group_sid = value;
168        self
169    }
170    /// Sets the POSIX permission mode facet (`fMOd`).
171    #[inline]
172    pub fn with_permission_mode(mut self, value: Option<PermissionMode>) -> Self {
173        self.permission_mode = value;
174        self
175    }
176
177    /// Sets the link target type of the entry.
178    /// Only meaningful for symbolic link and hard link entries.
179    #[inline]
180    pub const fn with_link_target_type(mut self, link_target_type: Option<LinkTargetType>) -> Self {
181        self.link_target_type = link_target_type;
182        self
183    }
184
185    /// Returns the raw file size of this entry's data in bytes.
186    #[inline]
187    pub const fn raw_file_size(&self) -> Option<u128> {
188        self.raw_file_size
189    }
190    /// Returns the compressed size of this entry's data in bytes.
191    #[inline]
192    pub const fn compressed_size(&self) -> usize {
193        self.compressed_size
194    }
195    /// Returns the created time since the Unix epoch for the entry.
196    #[inline]
197    pub const fn created(&self) -> Option<Duration> {
198        self.created
199    }
200    /// Returns the modified time since the Unix epoch for the entry.
201    #[inline]
202    pub const fn modified(&self) -> Option<Duration> {
203        self.modified
204    }
205    /// Returns the accessed time since the Unix epoch for the entry.
206    #[inline]
207    pub const fn accessed(&self) -> Option<Duration> {
208        self.accessed
209    }
210    /// Returns the owner, group, and permission bits for the entry.
211    #[deprecated(
212        since = "0.34.0",
213        note = "the fPRM chunk is superseded by the owner facet chunks; use Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode"
214    )]
215    #[allow(deprecated)]
216    #[inline]
217    pub const fn permission(&self) -> Option<&Permission> {
218        self.permission.as_ref()
219    }
220    /// Returns the owner user id facet (`fUId`), if recorded.
221    #[inline]
222    pub const fn owner_uid(&self) -> Option<OwnerUid> {
223        self.owner_uid
224    }
225    /// Returns the owner group id facet (`fGId`), if recorded.
226    #[inline]
227    pub const fn owner_gid(&self) -> Option<OwnerGid> {
228        self.owner_gid
229    }
230    /// Returns the owner user name facet (`fONm`), if recorded.
231    #[inline]
232    pub fn owner_user_name(&self) -> Option<&OwnerUserName> {
233        self.owner_user_name.as_ref()
234    }
235    /// Returns the owner group name facet (`fGNm`), if recorded.
236    #[inline]
237    pub fn owner_group_name(&self) -> Option<&OwnerGroupName> {
238        self.owner_group_name.as_ref()
239    }
240    /// Returns the owner user SID facet (`fOSi`), if recorded.
241    #[inline]
242    pub fn owner_user_sid(&self) -> Option<&OwnerUserSid> {
243        self.owner_user_sid.as_ref()
244    }
245    /// Returns the owner group SID facet (`fGSi`), if recorded.
246    #[inline]
247    pub fn owner_group_sid(&self) -> Option<&OwnerGroupSid> {
248        self.owner_group_sid.as_ref()
249    }
250    /// Returns the POSIX permission mode facet (`fMOd`), if recorded.
251    #[inline]
252    pub const fn permission_mode(&self) -> Option<PermissionMode> {
253        self.permission_mode
254    }
255
256    /// Returns the link target type for this entry, if present.
257    ///
258    /// - `None`: fLTP chunk was absent.
259    /// - `Some(Unknown)`: fLTP chunk present but target type undetermined.
260    /// - `Some(File)` / `Some(Directory)`: known target type.
261    #[inline]
262    pub const fn link_target_type(&self) -> Option<LinkTargetType> {
263        self.link_target_type
264    }
265}
266
267impl Default for Metadata {
268    #[inline]
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274/// Owner, group, and permission bits for an archive entry.
275#[deprecated(
276    since = "0.34.0",
277    note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
278)]
279#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
280pub struct Permission {
281    uid: u64,
282    uname: String,
283    gid: u64,
284    gname: String,
285    permission: u16,
286}
287
288#[allow(deprecated)]
289impl Permission {
290    /// Creates a new [`Permission`] with the given user, group, and permission bits.
291    ///
292    /// The `uid`/`gid` are numeric POSIX IDs, `uname`/`gname` are the
293    /// corresponding names, and `permission` holds the file mode bits (e.g. `0o755`).
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// # #![allow(deprecated)]
299    /// use libpna::Permission;
300    ///
301    /// let perm = Permission::new(1000, "user".into(), 100, "group".into(), 0o755);
302    /// ```
303    #[deprecated(
304        since = "0.34.0",
305        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
306    )]
307    #[inline]
308    pub const fn new(uid: u64, uname: String, gid: u64, gname: String, permission: u16) -> Self {
309        Self {
310            uid,
311            uname,
312            gid,
313            gname,
314            permission,
315        }
316    }
317    /// Returns the user ID associated with this permission.
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// # #![allow(deprecated)]
323    /// use libpna::Permission;
324    ///
325    /// let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
326    /// assert_eq!(perm.uid(), 1000);
327    /// ```
328    #[deprecated(
329        since = "0.34.0",
330        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
331    )]
332    #[inline]
333    pub const fn uid(&self) -> u64 {
334        self.uid
335    }
336
337    /// Returns the user name associated with this permission.
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// # #![allow(deprecated)]
343    /// use libpna::Permission;
344    ///
345    /// let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
346    /// assert_eq!(perm.uname(), "user1");
347    /// ```
348    #[deprecated(
349        since = "0.34.0",
350        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
351    )]
352    #[inline]
353    pub fn uname(&self) -> &str {
354        &self.uname
355    }
356
357    /// Returns the group ID associated with this permission.
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// # #![allow(deprecated)]
363    /// use libpna::Permission;
364    ///
365    /// let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
366    /// assert_eq!(perm.gid(), 100);
367    /// ```
368    #[deprecated(
369        since = "0.34.0",
370        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
371    )]
372    #[inline]
373    pub const fn gid(&self) -> u64 {
374        self.gid
375    }
376
377    /// Returns the group name associated with this permission.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// # #![allow(deprecated)]
383    /// use libpna::Permission;
384    ///
385    /// let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
386    /// assert_eq!(perm.gname(), "group1");
387    /// ```
388    #[deprecated(
389        since = "0.34.0",
390        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
391    )]
392    #[inline]
393    pub fn gname(&self) -> &str {
394        &self.gname
395    }
396
397    /// Returns the permission bits associated with this permission.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// # #![allow(deprecated)]
403    /// use libpna::Permission;
404    ///
405    /// let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
406    /// assert_eq!(perm.permissions(), 0o644);
407    /// ```
408    #[deprecated(
409        since = "0.34.0",
410        note = "the fPRM chunk is superseded by the owner facet chunks; use the owner facet API (Metadata::owner_uid/owner_gid/owner_user_name/owner_group_name/owner_user_sid/owner_group_sid/permission_mode and the matching EntryBuilder/with_* setters)"
411    )]
412    #[inline]
413    pub const fn permissions(&self) -> u16 {
414        self.permission
415    }
416
417    pub(crate) fn to_bytes(&self) -> Vec<u8> {
418        let mut bytes = Vec::with_capacity(20 + self.uname.len() + self.gname.len());
419        bytes.extend_from_slice(&self.uid.to_be_bytes());
420        bytes.extend_from_slice(&(self.uname.len() as u8).to_be_bytes());
421        bytes.extend_from_slice(self.uname.as_bytes());
422        bytes.extend_from_slice(&self.gid.to_be_bytes());
423        bytes.extend_from_slice(&(self.gname.len() as u8).to_be_bytes());
424        bytes.extend_from_slice(self.gname.as_bytes());
425        bytes.extend_from_slice(&self.permission.to_be_bytes());
426        bytes
427    }
428
429    pub(crate) fn try_from_bytes(mut bytes: &[u8]) -> io::Result<Self> {
430        let uid = u64::from_be_bytes({
431            let mut buf = [0; 8];
432            bytes.read_exact(&mut buf)?;
433            buf
434        });
435        let uname_len = {
436            let mut buf = [0; 1];
437            bytes.read_exact(&mut buf)?;
438            buf[0] as usize
439        };
440        let uname = String::from_utf8({
441            let mut buf = vec![0; uname_len];
442            bytes.read_exact(&mut buf)?;
443            buf
444        })
445        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
446        let gid = u64::from_be_bytes({
447            let mut buf = [0; 8];
448            bytes.read_exact(&mut buf)?;
449            buf
450        });
451        let gname_len = {
452            let mut buf = [0; 1];
453            bytes.read_exact(&mut buf)?;
454            buf[0] as usize
455        };
456        let gname = String::from_utf8({
457            let mut buf = vec![0; gname_len];
458            bytes.read_exact(&mut buf)?;
459            buf
460        })
461        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
462        let permission = u16::from_be_bytes({
463            let mut buf = [0; 2];
464            bytes.read_exact(&mut buf)?;
465            buf
466        });
467        Ok(Self {
468            uid,
469            uname,
470            gid,
471            gname,
472            permission,
473        })
474    }
475}
476
477/// Maximum owner-facet string byte length (the `fONm`/`fGNm`/`fOSi`/`fGSi`
478/// chunk Body uses a 1-byte length prefix).
479const OWNER_STR_MAX: usize = u8::MAX as usize;
480
481/// Owner user name (`fONm`).
482#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
483#[repr(transparent)]
484pub struct OwnerUserName(BoundedString<OWNER_STR_MAX>);
485
486impl OwnerUserName {
487    /// Constructs an [`OwnerUserName`].
488    ///
489    /// # Errors
490    ///
491    /// Returns [`LengthExceeded`] when the byte length exceeds 255.
492    #[inline]
493    pub fn new(value: impl Into<Box<str>>) -> Result<Self, LengthExceeded> {
494        BoundedString::new(value).map(Self)
495    }
496    /// Returns the name as a string slice.
497    #[inline]
498    #[must_use]
499    pub fn as_str(&self) -> &str {
500        self.0.as_str()
501    }
502    pub(crate) fn to_bytes(&self) -> Vec<u8> {
503        let b = self.0.as_str().as_bytes();
504        let mut v = Vec::with_capacity(1 + b.len());
505        // Type guarantees b.len() <= 255 (BoundedString<255> invariant).
506        v.push(b.len() as u8);
507        v.extend_from_slice(b);
508        v
509    }
510    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
511        let (&len, rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
512        let s = rest
513            .get(..len as usize)
514            .ok_or(io::ErrorKind::UnexpectedEof)?;
515        let s = str::from_utf8(s).map_err(|_| io::ErrorKind::InvalidData)?;
516        Self::new(s.to_owned()).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
517    }
518}
519
520impl Deref for OwnerUserName {
521    type Target = str;
522    #[inline]
523    fn deref(&self) -> &str {
524        self.0.as_str()
525    }
526}
527impl TryFrom<String> for OwnerUserName {
528    type Error = LengthExceeded;
529    #[inline]
530    fn try_from(value: String) -> Result<Self, Self::Error> {
531        Self::new(value)
532    }
533}
534impl TryFrom<&str> for OwnerUserName {
535    type Error = LengthExceeded;
536    #[inline]
537    fn try_from(value: &str) -> Result<Self, Self::Error> {
538        Self::new(value)
539    }
540}
541impl From<OwnerUserName> for String {
542    #[inline]
543    fn from(value: OwnerUserName) -> Self {
544        value.0.into()
545    }
546}
547
548/// Owner group name (`fGNm`).
549#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
550#[repr(transparent)]
551pub struct OwnerGroupName(BoundedString<OWNER_STR_MAX>);
552
553impl OwnerGroupName {
554    /// Constructs an [`OwnerGroupName`].
555    ///
556    /// # Errors
557    ///
558    /// Returns [`LengthExceeded`] when the byte length exceeds 255.
559    #[inline]
560    pub fn new(value: impl Into<Box<str>>) -> Result<Self, LengthExceeded> {
561        BoundedString::new(value).map(Self)
562    }
563    /// Returns the name as a string slice.
564    #[inline]
565    #[must_use]
566    pub fn as_str(&self) -> &str {
567        self.0.as_str()
568    }
569    pub(crate) fn to_bytes(&self) -> Vec<u8> {
570        let b = self.0.as_str().as_bytes();
571        let mut v = Vec::with_capacity(1 + b.len());
572        // Type guarantees b.len() <= 255 (BoundedString<255> invariant).
573        v.push(b.len() as u8);
574        v.extend_from_slice(b);
575        v
576    }
577    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
578        let (&len, rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
579        let s = rest
580            .get(..len as usize)
581            .ok_or(io::ErrorKind::UnexpectedEof)?;
582        let s = str::from_utf8(s).map_err(|_| io::ErrorKind::InvalidData)?;
583        Self::new(s.to_owned()).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
584    }
585}
586
587impl Deref for OwnerGroupName {
588    type Target = str;
589    #[inline]
590    fn deref(&self) -> &str {
591        self.0.as_str()
592    }
593}
594impl TryFrom<String> for OwnerGroupName {
595    type Error = LengthExceeded;
596    #[inline]
597    fn try_from(value: String) -> Result<Self, Self::Error> {
598        Self::new(value)
599    }
600}
601impl TryFrom<&str> for OwnerGroupName {
602    type Error = LengthExceeded;
603    #[inline]
604    fn try_from(value: &str) -> Result<Self, Self::Error> {
605        Self::new(value)
606    }
607}
608impl From<OwnerGroupName> for String {
609    #[inline]
610    fn from(value: OwnerGroupName) -> Self {
611        value.0.into()
612    }
613}
614
615/// Owner user SID (`fOSi`).
616#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
617#[repr(transparent)]
618pub struct OwnerUserSid(BoundedString<OWNER_STR_MAX>);
619
620impl OwnerUserSid {
621    /// Constructs an [`OwnerUserSid`].
622    ///
623    /// # Errors
624    ///
625    /// Returns [`LengthExceeded`] when the byte length exceeds 255.
626    #[inline]
627    pub fn new(value: impl Into<Box<str>>) -> Result<Self, LengthExceeded> {
628        BoundedString::new(value).map(Self)
629    }
630    /// Returns the name as a string slice.
631    #[inline]
632    #[must_use]
633    pub fn as_str(&self) -> &str {
634        self.0.as_str()
635    }
636    pub(crate) fn to_bytes(&self) -> Vec<u8> {
637        let b = self.0.as_str().as_bytes();
638        let mut v = Vec::with_capacity(1 + b.len());
639        // Type guarantees b.len() <= 255 (BoundedString<255> invariant).
640        v.push(b.len() as u8);
641        v.extend_from_slice(b);
642        v
643    }
644    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
645        let (&len, rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
646        let s = rest
647            .get(..len as usize)
648            .ok_or(io::ErrorKind::UnexpectedEof)?;
649        let s = str::from_utf8(s).map_err(|_| io::ErrorKind::InvalidData)?;
650        Self::new(s.to_owned()).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
651    }
652}
653
654impl Deref for OwnerUserSid {
655    type Target = str;
656    #[inline]
657    fn deref(&self) -> &str {
658        self.0.as_str()
659    }
660}
661impl TryFrom<String> for OwnerUserSid {
662    type Error = LengthExceeded;
663    #[inline]
664    fn try_from(value: String) -> Result<Self, Self::Error> {
665        Self::new(value)
666    }
667}
668impl TryFrom<&str> for OwnerUserSid {
669    type Error = LengthExceeded;
670    #[inline]
671    fn try_from(value: &str) -> Result<Self, Self::Error> {
672        Self::new(value)
673    }
674}
675impl From<OwnerUserSid> for String {
676    #[inline]
677    fn from(value: OwnerUserSid) -> Self {
678        value.0.into()
679    }
680}
681
682/// Owner group SID (`fGSi`).
683#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
684#[repr(transparent)]
685pub struct OwnerGroupSid(BoundedString<OWNER_STR_MAX>);
686
687impl OwnerGroupSid {
688    /// Constructs an [`OwnerGroupSid`].
689    ///
690    /// # Errors
691    ///
692    /// Returns [`LengthExceeded`] when the byte length exceeds 255.
693    #[inline]
694    pub fn new(value: impl Into<Box<str>>) -> Result<Self, LengthExceeded> {
695        BoundedString::new(value).map(Self)
696    }
697    /// Returns the name as a string slice.
698    #[inline]
699    #[must_use]
700    pub fn as_str(&self) -> &str {
701        self.0.as_str()
702    }
703    pub(crate) fn to_bytes(&self) -> Vec<u8> {
704        let b = self.0.as_str().as_bytes();
705        let mut v = Vec::with_capacity(1 + b.len());
706        // Type guarantees b.len() <= 255 (BoundedString<255> invariant).
707        v.push(b.len() as u8);
708        v.extend_from_slice(b);
709        v
710    }
711    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
712        let (&len, rest) = bytes.split_first().ok_or(io::ErrorKind::UnexpectedEof)?;
713        let s = rest
714            .get(..len as usize)
715            .ok_or(io::ErrorKind::UnexpectedEof)?;
716        let s = str::from_utf8(s).map_err(|_| io::ErrorKind::InvalidData)?;
717        Self::new(s.to_owned()).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
718    }
719}
720
721impl Deref for OwnerGroupSid {
722    type Target = str;
723    #[inline]
724    fn deref(&self) -> &str {
725        self.0.as_str()
726    }
727}
728impl TryFrom<String> for OwnerGroupSid {
729    type Error = LengthExceeded;
730    #[inline]
731    fn try_from(value: String) -> Result<Self, Self::Error> {
732        Self::new(value)
733    }
734}
735impl TryFrom<&str> for OwnerGroupSid {
736    type Error = LengthExceeded;
737    #[inline]
738    fn try_from(value: &str) -> Result<Self, Self::Error> {
739        Self::new(value)
740    }
741}
742impl From<OwnerGroupSid> for String {
743    #[inline]
744    fn from(value: OwnerGroupSid) -> Self {
745        value.0.into()
746    }
747}
748
749/// Owner user id (`fUId`).
750#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
751#[repr(transparent)]
752pub struct OwnerUid(u64);
753
754impl OwnerUid {
755    /// Returns the raw user id.
756    #[inline]
757    #[must_use]
758    pub const fn get(self) -> u64 {
759        self.0
760    }
761    pub(crate) fn to_bytes(self) -> [u8; 8] {
762        self.0.to_be_bytes()
763    }
764    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
765        let a: [u8; 8] = bytes
766            .try_into()
767            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "fUId must be 8 bytes"))?;
768        Ok(Self(u64::from_be_bytes(a)))
769    }
770}
771impl From<u64> for OwnerUid {
772    #[inline]
773    fn from(v: u64) -> Self {
774        Self(v)
775    }
776}
777
778/// Owner group id (`fGId`).
779#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
780#[repr(transparent)]
781pub struct OwnerGid(u64);
782
783impl OwnerGid {
784    /// Returns the raw group id.
785    #[inline]
786    #[must_use]
787    pub const fn get(self) -> u64 {
788        self.0
789    }
790    pub(crate) fn to_bytes(self) -> [u8; 8] {
791        self.0.to_be_bytes()
792    }
793    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
794        let a: [u8; 8] = bytes
795            .try_into()
796            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "fGId must be 8 bytes"))?;
797        Ok(Self(u64::from_be_bytes(a)))
798    }
799}
800impl From<u64> for OwnerGid {
801    #[inline]
802    fn from(v: u64) -> Self {
803        Self(v)
804    }
805}
806
807/// POSIX permission mode (`fMOd`). Reserved bits outside `0o7777`
808/// (the rwx + setuid/setgid/sticky bits) are masked to 0 on construction.
809#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
810#[repr(transparent)]
811pub struct PermissionMode(u16);
812
813impl PermissionMode {
814    /// Returns the permission bits (`0o7777`-masked).
815    #[inline]
816    #[must_use]
817    pub const fn get(self) -> u16 {
818        self.0
819    }
820    pub(crate) fn to_bytes(self) -> [u8; 2] {
821        self.0.to_be_bytes()
822    }
823    pub(crate) fn try_from_bytes(bytes: &[u8]) -> io::Result<Self> {
824        let a: [u8; 2] = bytes
825            .try_into()
826            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "fMOd must be 2 bytes"))?;
827        Ok(Self::from(u16::from_be_bytes(a)))
828    }
829}
830impl From<u16> for PermissionMode {
831    #[inline]
832    fn from(v: u16) -> Self {
833        Self(v & 0o7777)
834    }
835}
836
837/// Link target type for link entries.
838///
839/// Stored in the `fLTP` ancillary chunk. Indicates whether the link target
840/// is a file or a directory. The semantic interpretation depends on the
841/// entry's [`DataKind`](crate::DataKind):
842///
843/// | `DataKind` | `Unknown` | `File` | `Directory` |
844/// |---|---|---|---|
845/// | `SymbolicLink` | Symlink (target unknown) | File symlink | Directory symlink |
846/// | `HardLink` | Hard link (target unknown) | File hard link | Directory hard link |
847///
848/// `HardLink` + `Directory` represents a directory hard link — a hard link
849/// whose target is a directory. On systems that prohibit hard links to
850/// directories, implementations may fall back to a symbolic link.
851///
852/// # Value assignments
853///
854/// - `Unknown` (0): Explicit unknown — the target type was not determined.
855/// - `File` (1): Target is a file.
856/// - `Directory` (2): Target is a directory.
857/// - Values 3–63 are reserved for future public extensions.
858/// - Values 64–255 are reserved for private extensions.
859/// - Both ranges are currently unrecognized and fall back to `None`.
860#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
861#[repr(u8)]
862pub enum LinkTargetType {
863    /// Link target type is unknown.
864    Unknown = 0,
865    /// Link target is a file.
866    File = 1,
867    /// Link target is a directory.
868    Directory = 2,
869}
870
871impl TryFrom<u8> for LinkTargetType {
872    type Error = UnknownValueError;
873
874    #[inline]
875    fn try_from(value: u8) -> Result<Self, Self::Error> {
876        match value {
877            0 => Ok(Self::Unknown),
878            1 => Ok(Self::File),
879            2 => Ok(Self::Directory),
880            value => Err(UnknownValueError(value)),
881        }
882    }
883}
884
885impl LinkTargetType {
886    pub(crate) fn to_bytes(self) -> [u8; 1] {
887        [self as u8]
888    }
889
890    /// Parse fLTP chunk data.
891    ///
892    /// - Known values (0, 1, 2): `Ok(Some(variant))`
893    /// - Unrecognized values (3-255): `Ok(None)` (graceful fallback)
894    /// - Insufficient data: `Err`
895    pub(crate) fn try_from_bytes(mut bytes: &[u8]) -> io::Result<Option<Self>> {
896        let mut buf = [0u8; 1];
897        bytes.read_exact(&mut buf)?;
898        Ok(Self::try_from(buf[0]).ok())
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use super::*;
905    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
906    use wasm_bindgen_test::wasm_bindgen_test as test;
907
908    #[allow(deprecated)]
909    #[test]
910    fn permission() {
911        let perm = Permission::new(1000, "user1".into(), 100, "group1".into(), 0o644);
912        assert_eq!(perm, Permission::try_from_bytes(&perm.to_bytes()).unwrap());
913    }
914
915    #[test]
916    fn owner_string_newtype_bound_and_codec() {
917        use crate::entry::OwnerUserName;
918        assert!(OwnerUserName::new("").is_ok());
919        assert!(OwnerUserName::new("alice").is_ok());
920        assert!(OwnerUserName::new("a".repeat(255)).is_ok());
921        assert!(OwnerUserName::new("a".repeat(256)).is_err());
922        let n = OwnerUserName::new("alice").unwrap();
923        assert_eq!(n.to_bytes(), vec![5, b'a', b'l', b'i', b'c', b'e']);
924        assert_eq!(OwnerUserName::try_from_bytes(&n.to_bytes()).unwrap(), n);
925        assert_eq!(OwnerUserName::try_from_bytes(&[0]).unwrap().as_str(), "");
926        assert_eq!(
927            OwnerUserName::try_from_bytes(&[3, b'a', b'b', b'c', 0xFF])
928                .unwrap()
929                .as_str(),
930            "abc"
931        );
932        assert!(OwnerUserName::try_from_bytes(&[]).is_err());
933        assert!(OwnerUserName::try_from_bytes(&[5, b'a']).is_err());
934        assert!(OwnerUserName::try_from_bytes(&[1, 0xFF]).is_err());
935    }
936
937    #[test]
938    fn owner_uid_and_permission_mode_codec() {
939        use crate::entry::{OwnerUid, PermissionMode};
940        let u = OwnerUid::from(1000u64);
941        assert_eq!(u.get(), 1000);
942        assert_eq!(u.to_bytes(), 1000u64.to_be_bytes());
943        assert_eq!(OwnerUid::try_from_bytes(&u.to_bytes()).unwrap(), u);
944        assert!(OwnerUid::try_from_bytes(&[0, 0, 0]).is_err());
945        assert_eq!(PermissionMode::from(0o7777u16).get(), 0o7777);
946        assert_eq!(PermissionMode::from(0o170755u16).get(), 0o0755);
947        let m = PermissionMode::from(0o644u16);
948        assert_eq!(m.to_bytes(), 0o644u16.to_be_bytes());
949        assert_eq!(PermissionMode::try_from_bytes(&m.to_bytes()).unwrap(), m);
950        assert_eq!(
951            PermissionMode::try_from_bytes(&0o170644u16.to_be_bytes())
952                .unwrap()
953                .get(),
954            0o0644
955        );
956        assert!(PermissionMode::try_from_bytes(&[0]).is_err());
957    }
958
959    #[test]
960    fn metadata_owner_facets_default_none() {
961        let m = Metadata::new();
962        assert_eq!(m.owner_uid(), None);
963        assert_eq!(m.owner_gid(), None);
964        assert_eq!(m.owner_user_name(), None);
965        assert_eq!(m.owner_group_name(), None);
966        assert_eq!(m.owner_user_sid(), None);
967        assert_eq!(m.owner_group_sid(), None);
968        assert_eq!(m.permission_mode(), None);
969    }
970
971    #[test]
972    fn owner_id_facets_round_trip_via_entry() {
973        use crate::entry::{OwnerGid, OwnerUid};
974        use crate::{Archive, EntryBuilder, WriteOptions};
975        let mut buf = Vec::new();
976        {
977            let mut archive = Archive::write_header(&mut buf).unwrap();
978            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
979            b.owner_uid(OwnerUid::from(1000));
980            b.owner_gid(OwnerGid::from(2000));
981            let entry = b.build().unwrap();
982            archive.add_entry(entry).unwrap();
983            archive.finalize().unwrap();
984        }
985        let mut archive = Archive::read_header(&buf[..]).unwrap();
986        let entry = archive.entries().skip_solid().next().unwrap().unwrap();
987        let m = entry.metadata();
988        assert_eq!(m.owner_uid().map(|v| v.get()), Some(1000));
989        assert_eq!(m.owner_gid().map(|v| v.get()), Some(2000));
990    }
991
992    #[test]
993    fn owner_name_facets_round_trip_via_entry() {
994        use crate::entry::{OwnerGroupName, OwnerUserName};
995        use crate::{Archive, EntryBuilder, WriteOptions};
996        let mut buf = Vec::new();
997        {
998            let mut archive = Archive::write_header(&mut buf).unwrap();
999            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
1000            b.owner_user_name(OwnerUserName::new("alice").unwrap());
1001            b.owner_group_name(OwnerGroupName::new("").unwrap());
1002            let entry = b.build().unwrap();
1003            archive.add_entry(entry).unwrap();
1004            archive.finalize().unwrap();
1005        }
1006        let mut archive = Archive::read_header(&buf[..]).unwrap();
1007        let entry = archive.entries().skip_solid().next().unwrap().unwrap();
1008        let m = entry.metadata();
1009        assert_eq!(m.owner_user_name().map(|v| v.as_str()), Some("alice"));
1010        assert_eq!(m.owner_group_name().map(|v| v.as_str()), Some("")); // recorded empty name, NOT absent
1011    }
1012
1013    #[test]
1014    fn owner_sid_facets_round_trip_via_entry() {
1015        use crate::entry::{OwnerGroupSid, OwnerUserSid};
1016        use crate::{Archive, EntryBuilder, WriteOptions};
1017        let mut buf = Vec::new();
1018        {
1019            let mut archive = Archive::write_header(&mut buf).unwrap();
1020            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
1021            b.owner_user_sid(OwnerUserSid::new("S-1-5-21-1-2-3-1001").unwrap());
1022            b.owner_group_sid(OwnerGroupSid::new("S-1-5-32-544").unwrap());
1023            let entry = b.build().unwrap();
1024            archive.add_entry(entry).unwrap();
1025            archive.finalize().unwrap();
1026        }
1027        let mut archive = Archive::read_header(&buf[..]).unwrap();
1028        let entry = archive.entries().skip_solid().next().unwrap().unwrap();
1029        let m = entry.metadata();
1030        assert_eq!(
1031            m.owner_user_sid().map(|v| v.as_str()),
1032            Some("S-1-5-21-1-2-3-1001")
1033        );
1034        assert_eq!(
1035            m.owner_group_sid().map(|v| v.as_str()),
1036            Some("S-1-5-32-544")
1037        );
1038    }
1039
1040    #[test]
1041    fn fosi_length_prefixed_round_trip_and_empty() {
1042        use crate::entry::{OwnerGroupSid, OwnerUserSid};
1043        use crate::{Archive, EntryBuilder, WriteOptions};
1044        let mut buf = Vec::new();
1045        {
1046            let mut a = Archive::write_header(&mut buf).unwrap();
1047            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
1048            b.owner_user_sid(OwnerUserSid::new("S-1-5-21-1-2-3-1001").unwrap());
1049            b.owner_group_sid(OwnerGroupSid::new("").unwrap()); // empty -> [0] -> Some("")
1050            a.add_entry(b.build().unwrap()).unwrap();
1051            a.finalize().unwrap();
1052        }
1053        let mut a = Archive::read_header(&buf[..]).unwrap();
1054        let e = a.entries().skip_solid().next().unwrap().unwrap();
1055        assert_eq!(
1056            e.metadata().owner_user_sid().map(|v| v.as_str()),
1057            Some("S-1-5-21-1-2-3-1001")
1058        );
1059        assert_eq!(e.metadata().owner_group_sid().map(|v| v.as_str()), Some(""));
1060    }
1061
1062    #[test]
1063    fn permission_mode_facet_round_trip_via_entry() {
1064        use crate::entry::PermissionMode;
1065        use crate::{Archive, EntryBuilder, WriteOptions};
1066        let mut buf = Vec::new();
1067        {
1068            let mut archive = Archive::write_header(&mut buf).unwrap();
1069            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
1070            b.permission_mode(PermissionMode::from(0o750));
1071            let entry = b.build().unwrap();
1072            archive.add_entry(entry).unwrap();
1073            archive.finalize().unwrap();
1074        }
1075        let mut archive = Archive::read_header(&buf[..]).unwrap();
1076        let entry = archive.entries().skip_solid().next().unwrap().unwrap();
1077        assert_eq!(
1078            entry.metadata().permission_mode().map(|v| v.get()),
1079            Some(0o750)
1080        );
1081    }
1082
1083    #[test]
1084    fn link_target_type_roundtrip_unknown() {
1085        let ltp = LinkTargetType::Unknown;
1086        assert_eq!(
1087            Some(ltp),
1088            LinkTargetType::try_from_bytes(&ltp.to_bytes()).unwrap()
1089        );
1090    }
1091
1092    #[test]
1093    fn link_target_type_roundtrip_file() {
1094        let ltp = LinkTargetType::File;
1095        assert_eq!(
1096            Some(ltp),
1097            LinkTargetType::try_from_bytes(&ltp.to_bytes()).unwrap()
1098        );
1099    }
1100
1101    #[test]
1102    fn link_target_type_roundtrip_directory() {
1103        let ltp = LinkTargetType::Directory;
1104        assert_eq!(
1105            Some(ltp),
1106            LinkTargetType::try_from_bytes(&ltp.to_bytes()).unwrap()
1107        );
1108    }
1109
1110    #[test]
1111    fn link_target_type_unknown_values_return_none() {
1112        assert_eq!(LinkTargetType::try_from_bytes(&[0x03]).unwrap(), None);
1113        assert_eq!(LinkTargetType::try_from_bytes(&[0xFF]).unwrap(), None);
1114    }
1115
1116    #[test]
1117    fn link_target_type_empty_bytes() {
1118        assert!(LinkTargetType::try_from_bytes(&[]).is_err());
1119    }
1120
1121    #[test]
1122    fn link_target_type_try_from_u8() {
1123        assert_eq!(
1124            LinkTargetType::try_from(0u8).unwrap(),
1125            LinkTargetType::Unknown
1126        );
1127        assert_eq!(LinkTargetType::try_from(1u8).unwrap(), LinkTargetType::File);
1128        assert_eq!(
1129            LinkTargetType::try_from(2u8).unwrap(),
1130            LinkTargetType::Directory
1131        );
1132        assert!(LinkTargetType::try_from(3u8).is_err());
1133    }
1134
1135    #[test]
1136    fn link_target_type_trailing_bytes_ignored() {
1137        // read_exact reads only 1 byte; trailing bytes are silently ignored
1138        assert_eq!(
1139            LinkTargetType::try_from_bytes(&[0x01, 0xFF, 0xFF]).unwrap(),
1140            Some(LinkTargetType::File),
1141        );
1142    }
1143
1144    #[allow(deprecated)]
1145    #[test]
1146    fn all_owner_facets_and_fprm_coexist_round_trip() {
1147        use crate::entry::{
1148            OwnerGid, OwnerGroupName, OwnerGroupSid, OwnerUid, OwnerUserName, OwnerUserSid,
1149            PermissionMode,
1150        };
1151        use crate::{Archive, EntryBuilder, WriteOptions};
1152        let mut buf = Vec::new();
1153        {
1154            let mut archive = Archive::write_header(&mut buf).unwrap();
1155            let mut b = EntryBuilder::new_file("f".into(), WriteOptions::store()).unwrap();
1156            // Legacy fPRM (Permission uses plain String uname/gname on this branch).
1157            b.permission(Permission::new(
1158                7,
1159                "legacy".to_string(),
1160                8,
1161                "grp".to_string(),
1162                0o600,
1163            ));
1164            // All 7 new owner facets.
1165            b.owner_uid(OwnerUid::from(1));
1166            b.owner_gid(OwnerGid::from(2));
1167            b.owner_user_name(OwnerUserName::new("u").unwrap());
1168            b.owner_group_name(OwnerGroupName::new("g").unwrap());
1169            b.owner_user_sid(OwnerUserSid::new("S-1-1").unwrap());
1170            b.owner_group_sid(OwnerGroupSid::new("S-1-2").unwrap());
1171            b.permission_mode(PermissionMode::from(0o644));
1172            let entry = b.build().unwrap();
1173            archive.add_entry(entry).unwrap();
1174            archive.finalize().unwrap();
1175        }
1176        let mut archive = Archive::read_header(&buf[..]).unwrap();
1177        let entry = archive.entries().skip_solid().next().unwrap().unwrap();
1178        let m = entry.metadata();
1179        // All 7 new facets survived.
1180        assert_eq!(m.owner_uid().map(|v| v.get()), Some(1));
1181        assert_eq!(m.owner_gid().map(|v| v.get()), Some(2));
1182        assert_eq!(m.owner_user_name().map(|v| v.as_str()), Some("u"));
1183        assert_eq!(m.owner_group_name().map(|v| v.as_str()), Some("g"));
1184        assert_eq!(m.owner_user_sid().map(|v| v.as_str()), Some("S-1-1"));
1185        assert_eq!(m.owner_group_sid().map(|v| v.as_str()), Some("S-1-2"));
1186        assert_eq!(m.permission_mode().map(|v| v.get()), Some(0o644));
1187        // Legacy fPRM still round-trips intact, independently of the new owner facets.
1188        let p = m
1189            .permission()
1190            .expect("fPRM permission must still be present");
1191        assert_eq!(p.uid(), 7);
1192        assert_eq!(p.uname(), "legacy");
1193        assert_eq!(p.gid(), 8);
1194        assert_eq!(p.gname(), "grp");
1195        assert_eq!(p.permissions(), 0o600);
1196    }
1197
1198    #[test]
1199    fn fonm_trailing_bytes_after_length_are_ignored() {
1200        use crate::{Archive, ChunkType, EntryBuilder, RawChunk, WriteOptions};
1201        let mut buf = Vec::new();
1202        {
1203            let mut a = Archive::write_header(&mut buf).unwrap();
1204            let mut b = EntryBuilder::new_file("g".into(), WriteOptions::store()).unwrap();
1205            b.add_extra_chunk(RawChunk::from_data(
1206                ChunkType::fONm,
1207                vec![3, b'a', b'b', b'c', 0xFF],
1208            ));
1209            a.add_entry(b.build().unwrap()).unwrap();
1210            a.finalize().unwrap();
1211        }
1212        let mut a = Archive::read_header(&buf[..]).unwrap();
1213        let e = a.entries().skip_solid().next().unwrap().unwrap();
1214        assert_eq!(
1215            e.metadata().owner_user_name().map(|v| v.as_str()),
1216            Some("abc")
1217        );
1218    }
1219}