hadris_udf/lib.rs
1//! # Hadris UDF
2//!
3//! A pure Rust Universal Disk Format (UDF) filesystem library for optical media
4//! and disk images. It supports hosted applications and `no_std` bootloaders,
5//! kernels, firmware, and embedded systems.
6//!
7//! UDF (ECMA-167) is the filesystem used for:
8//! - DVD-ROM, DVD-Video, DVD-RAM
9//! - Blu-ray discs
10//! - Large USB drives (files >4GB)
11//! - Packet writing to CD/DVD-RW
12//!
13//! ## Features
14//!
15//! This crate supports:
16//! - **UDF 1.02**: DVD-ROM (read-only)
17//! - **UDF 1.50**: DVD-RAM, packet writing (planned)
18//! - **UDF 2.01**: DVD-RW, streaming (planned)
19//!
20//! ## Quick Start
21//!
22//! ```rust,no_run
23//! use std::fs::File;
24//! use std::io::BufReader;
25//! use hadris_udf::UdfVolume;
26//!
27//! // Open a UDF image file
28//! let file = File::open("movie.udf").unwrap();
29//! let reader = BufReader::new(file);
30//! let udf = UdfVolume::open(reader).unwrap();
31//!
32//! // Read volume info
33//! let info = udf.info();
34//! println!("Volume: {}", info.volume_id);
35//!
36//! // List root directory
37//! let root = udf.root_dir().unwrap();
38//! for entry in root.entries() {
39//! println!("{} ({})", entry.name(), entry.size);
40//! }
41//!
42//! // Read a file's contents
43//! # let entry = root.entries().next().unwrap();
44//! let bytes = udf.read_file(&entry).unwrap();
45//! # let _ = bytes;
46//! ```
47//!
48//! ## Feature Flags
49//!
50//! | Feature | Description |
51//! |---------|-------------|
52//! | `read` | Read support (default) |
53//! | `alloc` | Heap allocation without full std |
54//! | `std` | Full standard library support |
55//! | `write` | Write/format support (requires std) |
56//! | `sync` | Synchronous API under [`sync`] (default) |
57//! | `async` | Asynchronous read API under `hadris_udf::r#async` |
58//!
59//! `std` does not select an I/O mode. The `write` implementation is currently
60//! synchronous-only; enabling `write` and `async` together does not expose an
61//! async write API.
62//!
63//! ## Known Limitations
64//!
65//! - Extended allocation descriptors and stream directories are not supported.
66//! - Packet writing / sparing tables / Blu-ray-specific features are not implemented.
67//! - Directory listing reads each file ICB to populate
68//! [`dir::UdfDirEntry::size`] (one extra seek per file).
69//!
70//! ## Specification References
71//!
72//! - ECMA-167: Volume and File Structure for Write-Once and Rewritable Media
73//! - OSTA UDF Specification (udf260.pdf)
74
75#![no_std]
76#![allow(async_fn_in_trait)]
77#![deny(missing_docs)]
78// Sync and async APIs intentionally compile the same source modules twice.
79#![allow(clippy::duplicate_mod)]
80#![cfg_attr(docsrs, feature(doc_cfg))]
81
82#[cfg(feature = "alloc")]
83extern crate alloc;
84
85#[cfg(feature = "std")]
86extern crate std;
87
88// ---------------------------------------------------------------------------
89// Shared types (compiled once, not duplicated by sync/async modules)
90// ---------------------------------------------------------------------------
91
92mod error;
93mod time;
94
95pub use error::{Error, Result};
96pub use time::UdfTimestamp;
97
98// ---------------------------------------------------------------------------
99// Sync module
100// ---------------------------------------------------------------------------
101
102#[cfg(feature = "sync")]
103#[path = ""]
104pub mod sync {
105 //! Synchronous UDF filesystem API.
106 //!
107 //! All I/O operations use synchronous `Read`/`Write`/`Seek` traits.
108
109 pub use hadris_io::Result as IoResult;
110 pub use hadris_io::sync::{Parsable, Read, ReadExt, Seek, Writable, Write};
111 pub use hadris_io::{Error, ErrorKind, SeekFrom};
112
113 macro_rules! io_transform {
114 ($($item:tt)*) => { hadris_macros::strip_async!{ $($item)* } };
115 }
116
117 #[allow(unused_macros)]
118 macro_rules! sync_only {
119 ($($item:tt)*) => { $($item)* };
120 }
121
122 #[allow(unused_macros)]
123 macro_rules! async_only {
124 ($($item:tt)*) => {};
125 }
126
127 #[path = "."]
128 mod __inner {
129 pub mod descriptor;
130 #[cfg(feature = "alloc")]
131 pub mod dir;
132 #[cfg(feature = "alloc")]
133 pub mod file;
134 #[cfg(feature = "alloc")]
135 pub mod fs;
136 sync_only! {
137 #[cfg(feature = "write")]
138 pub mod write;
139 }
140 }
141 #[cfg(feature = "alloc")]
142 pub use __inner::dir::UdfDir;
143 #[cfg(feature = "alloc")]
144 pub use __inner::file::FileType;
145 pub use __inner::*;
146
147 #[cfg(feature = "alloc")]
148 pub use __inner::fs::{UdfVolume, UdfVolumeInfo};
149}
150
151// ---------------------------------------------------------------------------
152// Async module
153// ---------------------------------------------------------------------------
154
155#[cfg(feature = "async")]
156#[path = ""]
157pub mod r#async {
158 //! Asynchronous UDF filesystem API.
159 //!
160 //! All I/O operations use async `Read`/`Write`/`Seek` traits.
161
162 pub use hadris_io::Result as IoResult;
163 pub use hadris_io::r#async::{Parsable, Read, ReadExt, Seek, Writable, Write};
164 pub use hadris_io::{Error, ErrorKind, SeekFrom};
165
166 macro_rules! io_transform {
167 ($($item:tt)*) => { $($item)* };
168 }
169
170 #[allow(unused_macros)]
171 macro_rules! sync_only {
172 ($($item:tt)*) => {};
173 }
174
175 #[allow(unused_macros)]
176 macro_rules! async_only {
177 ($($item:tt)*) => { $($item)* };
178 }
179
180 #[path = "."]
181 mod __inner {
182 pub mod descriptor;
183 #[cfg(feature = "alloc")]
184 pub mod dir;
185 #[cfg(feature = "alloc")]
186 pub mod file;
187 #[cfg(feature = "alloc")]
188 pub mod fs;
189 }
190 #[cfg(feature = "alloc")]
191 pub use __inner::dir::UdfDir;
192 #[cfg(feature = "alloc")]
193 pub use __inner::file::FileType;
194 pub use __inner::*;
195
196 #[cfg(feature = "alloc")]
197 pub use __inner::fs::{UdfVolume, UdfVolumeInfo};
198}
199
200// ---------------------------------------------------------------------------
201// Default re-exports for backwards compatibility (sync)
202// ---------------------------------------------------------------------------
203
204#[cfg(feature = "sync")]
205pub use sync::*;
206
207// When only async is enabled (no sync), re-export async module contents
208// so that shared modules (dir.rs, file.rs) can use `crate::descriptor::*`.
209#[cfg(all(feature = "async", not(feature = "sync")))]
210pub use r#async::*;
211
212/// UDF revision numbers
213#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
214pub struct UdfRevision(u16);
215
216impl UdfRevision {
217 /// UDF 1.02 - DVD-ROM
218 pub const V1_02: Self = Self(0x0102);
219 /// UDF 1.50 - DVD-RAM, packet writing
220 pub const V1_50: Self = Self(0x0150);
221 /// UDF 2.00 - DVD-RW
222 pub const V2_00: Self = Self(0x0200);
223 /// UDF 2.01 - DVD-RW streaming
224 pub const V2_01: Self = Self(0x0201);
225 /// UDF 2.50 - Blu-ray
226 pub const V2_50: Self = Self(0x0250);
227 /// UDF 2.60 - Blu-ray pseudo-overwrite
228 pub const V2_60: Self = Self(0x0260);
229
230 /// Create a revision from raw value
231 pub const fn from_raw(value: u16) -> Self {
232 Self(value)
233 }
234
235 /// Get the raw revision value
236 pub const fn to_raw(self) -> u16 {
237 self.0
238 }
239
240 /// Get the major version number
241 pub const fn major(self) -> u8 {
242 ((self.0 >> 8) & 0xFF) as u8
243 }
244
245 /// Get the minor version number
246 pub const fn minor(self) -> u8 {
247 (self.0 & 0xFF) as u8
248 }
249}
250
251impl core::fmt::Display for UdfRevision {
252 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
253 write!(f, "{}.{:02x}", self.major(), self.minor())
254 }
255}
256
257/// Sector size for UDF (always 2048 bytes for optical media)
258pub const SECTOR_SIZE: usize = 2048;
259
260/// Location of the first Anchor Volume Descriptor Pointer
261pub const AVDP_LOCATION: u32 = 256;
262
263#[cfg(test)]
264mod tests {
265 extern crate std;
266 use super::*;
267 use std::format;
268
269 #[test]
270 fn test_udf_revision() {
271 let rev = UdfRevision::V2_01;
272 assert_eq!(rev.major(), 2);
273 assert_eq!(rev.minor(), 1);
274 assert_eq!(rev.to_raw(), 0x0201);
275 }
276
277 #[test]
278 fn test_udf_revision_display() {
279 assert_eq!(format!("{}", UdfRevision::V1_02), "1.02");
280 assert_eq!(format!("{}", UdfRevision::V2_50), "2.50");
281 }
282}