Skip to main content

libcdio_rs/mmc/
read_disc_info.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 based on MMC `READ DISC INFORMATION`.
19
20use displaydoc::Display;
21use thiserror::Error;
22
23use crate::{
24    Mmc,
25    mmc::{Cdb, MmcCommand, MmcDirection, MmcError},
26};
27
28/// Routines based on MMC `READ DISC INFORMATION`.
29impl Mmc {
30    /// Indicates that the media is of a writable kind (such as CD-RW, BD-RE,
31    /// DVD+RW, etc) and the drive is capable of writing to the media.
32    pub fn is_disc_erasable(&self) -> Result<bool, MmcReadDiscInfoError> {
33        let data = self.read_disc_information(DiscInfoKind::Standard)?;
34        Ok(data[2] & 1 << 4 != 0)
35    }
36
37    fn read_disc_information(&self, kind: DiscInfoKind) -> Result<Vec<u8>, MmcReadDiscInfoError> {
38        let mut buf = vec![0; INITIAL_BUFFER_SIZE];
39        let mut cdb = Cdb::default();
40        cdb[0] = MmcCommand::ReadDiscInfo as u8;
41        cdb[1] = kind as u8 & 0b111;
42        cdb[7..9].copy_from_slice(&(buf.len() as u16).to_be_bytes());
43
44        self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?;
45
46        let data_length = buf[0..2]
47            .try_into()
48            .map(u16::from_be_bytes)
49            .map(|len| usize::from(len) + DATA_LEN_FIELDSIZE)
50            .expect("initial buffer length is greater than two bytes");
51        if buf.len() < data_length {
52            buf.resize(data_length, 0);
53            cdb[7..9].copy_from_slice(&(buf.len() as u16).to_be_bytes());
54            self.run_command(Some(MmcDirection::Read), buf.as_mut_slice(), cdb)?;
55        }
56        buf.truncate(data_length);
57        tracing::debug!(?buf, len = buf.len());
58
59        return Ok(buf);
60
61        const INITIAL_BUFFER_SIZE: usize = 64;
62        const DATA_LEN_FIELDSIZE: usize = 2;
63    }
64}
65
66#[allow(unused)]
67#[repr(u8)]
68#[derive(Clone, Copy, Debug)]
69enum DiscInfoKind {
70    /// Disc type, codes, sessions, opc entries..
71    Standard = 0b000,
72
73    /// Assigned, appendable and other track counts..
74    TrackResources = 0b001,
75
76    /// Pseudo Overwrite entries, updates and replacements
77    PowResources = 0b010,
78}
79
80/// error from a `READ DISC INFORMATION` command.
81#[derive(Debug, Display, Error)]
82pub enum MmcReadDiscInfoError {
83    /// operating system returned an error
84    Os(#[from] MmcError),
85
86    /// invalid response from mmc command: {0}
87    InvalidResponse(String),
88}
89
90#[cfg(test)]
91mod tests {
92    use tracing::info;
93
94    use super::*;
95
96    #[test_log::test(test)]
97    #[ignore = "requires a drive with mmc"]
98    fn is_disc_erasable() {
99        let is_disc_erasable = Mmc::new().unwrap().is_disc_erasable().unwrap();
100        info!(is_disc_erasable);
101    }
102}