// UDF(ECMA-167 / ISO 13346)读取。
//
// 路径:Anchor Volume Descriptor Pointer(第 256 块)-> 描述符序列
// -> Partition Descriptor(分区起点)+ Logical Volume Descriptor(块大小、File Set 位置)
// -> File Set Descriptor(根目录 ICB)-> File Entry(分配描述符)-> 目录 FID / 文件内容
//
// 覆盖现代安装镜像与 DVD/蓝光常见的 UDF 2.01+;不做 VAT(多区段写入)与元数据分区。
///|
/// UDF 描述符公共头(tag),16 字节。
priv struct UdfTag {
id : Int32
checksum_ok : Bool
}
///|
fn read_le16(data : Bytes, offset : Int32) -> Int32 {
data[offset].to_uint().reinterpret_as_int() |
(data[offset + 1].to_uint().reinterpret_as_int() << 8)
}
///|
fn read_le32(data : Bytes, offset : Int32) -> Int32 {
data[offset].to_uint().reinterpret_as_int() |
(data[offset + 1].to_uint().reinterpret_as_int() << 8) |
(data[offset + 2].to_uint().reinterpret_as_int() << 16) |
(data[offset + 3].to_uint().reinterpret_as_int() << 24)
}
///|
///|
/// UDF 的信息长度是 64 位;本实现只支持 4 GiB 以内的文件,超出返回 -1。
fn read_length64(data : Bytes, offset : Int32) -> Int32 {
let hi = read_le32(data, offset + 4)
let lo = read_le32(data, offset)
if hi != 0 {
-1
} else {
lo
}
}
///|
/// 解析并校验 tag:校验和 = 除第 4 字节外全部 16 字节之和(模 256)。
fn parse_tag(data : Bytes, offset : Int32) -> UdfTag {
let mut sum = 0
for i = 0; i < 16; i = i + 1 {
if i != 4 {
sum = sum + data[offset + i].to_uint().reinterpret_as_int()
}
}
UdfTag::{
id: read_le16(data, offset),
checksum_ok: sum % 256 == data[offset + 4].to_uint().reinterpret_as_int(),
}
}
///|
/// 长分配描述符(long_ad):长度、逻辑块号、分区引用号。
pub struct UdfExtent {
length : Int32
location : Int32
partition : Int32
}
///|
fn read_long_ad(data : Bytes, offset : Int32) -> UdfExtent {
UdfExtent::{
length: read_le32(data, offset) & 0x3FFFFFFF,
location: read_le32(data, offset + 4),
partition: read_le16(data, offset + 8),
}
}
///|
/// UDF 目录项。
pub struct UdfEntry {
name : String
is_dir : Bool
size : Int32
}
///|
/// 一个已解析的 UDF 文件/目录(内部表示)。
priv struct UdfNode {
is_dir : Bool
size : Int32
embedded : Bytes
extents : Array[UdfExtent]
}
///|
/// UDF 映像。
pub struct UdfImage {
data : Bytes
block_size : Int32
partition_start : Int32
root : UdfExtent
}
///|
/// 解析 UDF 标识符(d-string):首字节是压缩标识,8 = Latin-1,16 = UTF-16BE。
fn decode_dstring(raw : Bytes) -> String {
if raw.length() == 0 {
return ""
}
let kind = raw[0].to_uint().reinterpret_as_int()
let body = sub_bytes(raw, 1, raw.length())
if kind == 16 {
decode_ucs2_be(body)
} else {
let mut out = ""
for i = 0; i < body.length(); i = i + 1 {
out = out +
Int32::unsafe_to_char(body[i].to_uint().reinterpret_as_int()).to_string()
}
out
}
}
///|
/// 打开 UDF 映像;不是 UDF(没有有效 Anchor)时以 BadDescriptor 失败。
pub fn UdfImage::open(data : Bytes) -> UdfImage raise IsoError {
let anchor = 256 * 2048
if anchor + 2048 > data.length() {
raise Truncated
}
let tag = parse_tag(data, anchor)
if tag.id != 2 || !tag.checksum_ok {
raise BadDescriptor(tag.id)
}
// 主卷描述符序列
let sequence = read_long_ad(data, anchor + 16)
if sequence.length <= 0 {
raise BadDescriptor(0)
}
let mut partition_start = -1
let mut block_size = 2048
let mut file_set : UdfExtent? = None
let blocks = sequence.length / 2048
for i = 0; i < blocks; i = i + 1 {
let offset = (sequence.location + i) * 2048
if offset + 2048 > data.length() {
break
}
let t = parse_tag(data, offset)
if t.id == 5 {
partition_start = read_le32(data, offset + 188)
} else if t.id == 6 {
block_size = read_le32(data, offset + 212)
file_set = Some(read_long_ad(data, offset + 248))
} else if t.id == 8 {
break
}
}
if partition_start < 0 {
raise BadDescriptor(partition_start)
}
let fsd = match file_set {
Some(extent) => extent
None => raise BadDescriptor(0)
}
let fsd_offset = (partition_start + fsd.location) * block_size
if fsd_offset + 512 > data.length() {
raise Truncated
}
if parse_tag(data, fsd_offset).id != 256 {
raise BadDescriptor(256)
}
let root = read_long_ad(data, fsd_offset + 400)
UdfImage::{ data, block_size, partition_start, root, }
}
///|
pub fn UdfImage::block_size(self : UdfImage) -> Int32 {
self.block_size
}
///|
/// 读取 File Entry(ICB),得到文件类型、长度与分配位置。
fn UdfImage::read_entry(
self : UdfImage,
icb : UdfExtent,
) -> UdfNode raise IsoError {
let offset = (self.partition_start + icb.location) * self.block_size
if offset + 176 > self.data.length() {
raise Truncated
}
let tag = parse_tag(self.data, offset)
if tag.id != 261 {
raise BadDescriptor(tag.id)
}
let file_type = self.data[offset + 27].to_uint().reinterpret_as_int()
let info_length = read_length64(self.data, offset + 56)
let l_ea = read_le32(self.data, offset + 168)
let l_ad = read_le32(self.data, offset + 172)
let flags = read_le16(self.data, offset + 34)
let long_ad = (flags & 1) != 0
let mut embedded = Bytes::new(0)
let extents : Array[UdfExtent] = []
let mut at = offset + 176 + l_ea
let end = at + l_ad
let step = if long_ad { 16 } else { 8 }
while at + step <= end {
let word = read_le32(self.data, at)
let kind = (word >> 30) & 3
let length = word & 0x3FFFFFFF
if kind == 3 {
// 内嵌数据:目录通常直接放在 File Entry 里
embedded = sub_bytes(self.data, at + step, at + step + length)
} else if kind == 0 && length > 0 {
let location = read_le32(self.data, at + 4)
let partition = if long_ad {
read_le16(self.data, at + 8)
} else {
icb.partition
}
extents.push(UdfExtent::{ length, location, partition, })
}
at = at + step
}
UdfNode::{ is_dir: file_type == 4, size: info_length, embedded, extents, }
}
///|
/// 取节点内容(内嵌优先,否则按分配描述符拼接)。
fn UdfImage::node_data(self : UdfImage, node : UdfNode) -> Bytes raise IsoError {
if node.embedded.length() > 0 {
return node.embedded
}
let mut total = 0
for extent in node.extents {
total = total + extent.length
}
let out = FixedArray::make(total, b'\x00')
let mut written = 0
for extent in node.extents {
let start = (self.partition_start + extent.location) * self.block_size
if start < 0 || start + extent.length > self.data.length() {
raise Truncated
}
for i = 0; i < extent.length; i = i + 1 {
out[written + i] = self.data[start + i]
}
written = written + extent.length
}
Bytes::from_array(out[:])
}
///|
/// 解析目录数据里的 FID 列表,返回 (名字, ICB)。
fn parse_fids(data : Bytes) -> Array[(String, UdfExtent, Bool, Int32)] {
let out : Array[(String, UdfExtent, Bool, Int32)] = []
let mut at = 0
while at + 38 <= data.length() {
let tag = parse_tag(data, at)
if tag.id != 257 {
// 不是 FID:多半已经走到填充区
break
}
let characteristics = data[at + 18].to_uint().reinterpret_as_int()
let l_fi = data[at + 19].to_uint().reinterpret_as_int()
let icb = read_long_ad(data, at + 20)
let l_iu = read_le16(data, at + 36)
let name_start = at + 38 + l_iu
let name = if l_fi > 0 {
decode_dstring(sub_bytes(data, name_start, name_start + l_fi))
} else {
""
}
let is_dir = (characteristics & 2) != 0
let is_parent = (characteristics & 8) != 0
// FID 总长度按 4 字节对齐
let step = (38 + l_iu + l_fi + 3) / 4 * 4
if !is_parent {
out.push((name, icb, is_dir, step))
}
at = at + step
}
out
}
///|
/// 列出一级目录。
pub fn UdfImage::list_dir(
self : UdfImage,
path : String,
) -> Array[UdfEntry] raise IsoError {
let segments : Array[String] = []
for segment in path.split("/") {
let seg = segment.to_owned()
if seg != "" {
segments.push(seg)
}
}
let mut node = self.read_entry(self.root)
for seg in segments {
if !node.is_dir {
raise NotDirectory(path)
}
let entries = self.read_dir_entries(node)
let mut found = false
for entry in entries {
if entry.0 == seg {
node = self.read_entry(entry.1)
found = true
break
}
}
if !found {
raise NotFound(path)
}
}
if !node.is_dir {
raise NotDirectory(path)
}
let out : Array[UdfEntry] = []
for entry in self.read_dir_entries(node) {
let (name, icb, is_dir, _) = entry
let child = self.read_entry(icb)
out.push(UdfEntry::{ name, is_dir, size: child.size, })
}
out
}
///|
fn UdfImage::read_dir_entries(
self : UdfImage,
node : UdfNode,
) -> Array[(String, UdfExtent, Bool, Int32)] raise IsoError {
let data = self.node_data(node)
parse_fids(data)
}
///|
/// 读取文件内容。
pub fn UdfImage::read_file(
self : UdfImage,
path : String,
) -> Bytes raise IsoError {
let segments : Array[String] = []
for segment in path.split("/") {
let seg = segment.to_owned()
if seg != "" {
segments.push(seg)
}
}
let mut node = self.read_entry(self.root)
for seg in segments {
let entries = self.read_dir_entries(node)
let mut found = false
for entry in entries {
if entry.0 == seg {
node = self.read_entry(entry.1)
found = true
break
}
}
if !found {
raise NotFound(path)
}
}
if node.is_dir {
raise IsDirectory(path)
}
self.node_data(node)
}