Skip to main content

libcdio_rs/iso9660/
xa.rs

1// Copyright (C) 2026 Shiva Kiran Koninty <shiva@skran.xyz>
2//
3// This file is part of libcdio-rs.
4//
5// libcdio-rs is free software: you can redistribute it and/or
6// modify it under the terms of the GNU General Public License as
7// published by the Free Software Foundation, either version 3 of the
8// License, or (at your option) any later version.
9//
10// libcdio-rs is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13// General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with libcdio-rs. If not, see <https://www.gnu.org/licenses/>.
17
18//! Routines related to CD-ROM XA (eXtended Architecture).
19
20use bitflags::bitflags;
21
22use crate::iso9660::entry::IsoEntry;
23
24impl IsoEntry<'_> {
25    /// Returns CD-ROM XA (eXtended Architecture) attributes of the entry.
26    pub fn xa(&self) -> Option<XaAttributes> {
27        let have_xa = unsafe { (*self.stat.as_ptr()).b_xa };
28        if !have_xa {
29            return None;
30        }
31
32        // SAFETY: The above check confirms that xa are present.
33        let xa = unsafe { (*self.stat.as_ptr()).xa };
34
35        Some(XaAttributes {
36            file_attr: XaFileAttributes::from_bits_retain(u16::from_be(xa.attributes)),
37            file_num: u8::from_be(xa.filenum),
38            group_id: u16::from_be(xa.group_id),
39            user_id: u16::from_be(xa.user_id),
40            total_size: self.total_size(),
41        })
42    }
43}
44
45/// CD-ROM XA (eXtended Architecture) attributes.
46#[derive(Clone, Debug)]
47#[non_exhaustive]
48pub struct XaAttributes {
49    pub file_attr: XaFileAttributes,
50    pub file_num: u8,
51    pub group_id: u16,
52    pub user_id: u16,
53    total_size: u64,
54}
55
56impl XaAttributes {
57    /// Returns multi extent size.
58    ///
59    /// Returns `None` if not using Mode2/Form2 encoding.
60    // TODO: Add unit test
61    pub const fn mode2form2_size(&self) -> Option<u64> {
62        if !self.file_attr.contains(XaFileAttributes::Mode2Form2) {
63            return None;
64        }
65
66        const ISO_BLOCK_BYTES: u64 = 2048;
67        const MODE2FORM2_SECTOR_BYTES: u64 = 2324;
68
69        let total_sectors = self.total_size.div_ceil(ISO_BLOCK_BYTES);
70
71        Some(total_sectors * MODE2FORM2_SECTOR_BYTES)
72    }
73}
74
75bitflags! {
76    /// XA File Attributes.
77    ///
78    /// See: https://psx-spx.consoledev.net/cdromformat/#cdrom-iso-file-and-directory-descriptors
79    #[derive(Clone, Copy, Debug)]
80    pub struct XaFileAttributes: u16 {
81        const OwnerRead = 1 << 0;
82        const OwnerExecute = 1 << 2;
83        const GroupRead = 1 << 4;
84        const GroupExecute = 1 << 6;
85        const WorldRead = 1 << 8;
86        const WorldExecute = 1 << 10;
87        const Mode2 = 1 << 11;
88        const Mode2Form2 = 1 << 12;
89        const Interleaved = 1 << 13;
90        const Cdda = 1 << 14;
91        const Directory = 1 << 15;
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use std::path::PathBuf;
98
99    use crate::iso9660::Iso;
100
101    use super::*;
102
103    #[test]
104    fn xa() {
105        let iso = Iso::new(PathBuf::from("tests/data/xa.iso")).unwrap();
106        let entry = iso.entry("/copying".to_string()).unwrap();
107        let xa = entry.xa().unwrap();
108        assert_eq!(xa.file_num, 0);
109        assert_eq!(xa.group_id, 3000);
110        assert_eq!(xa.user_id, 1000);
111
112        let expected_attr =
113            XaFileAttributes::GroupRead & XaFileAttributes::GroupExecute & XaFileAttributes::Mode2;
114        assert!(xa.file_attr.contains(expected_attr));
115    }
116}