Mescla a main (lzfse_rust e ed25519-compact lado a lado)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
27d4cab2e6
18 files changed
+469
-86
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! DMG (Apple UDIF): rodapé `koly`, tabelas `mish` do plist e chunks zerados, brutos, ADC,
|
||||
//! zlib e bzip2. Imagens cifradas (`encrcdsa`) e chunks LZFSE (`ULFO`) são recusados com
|
||||
//! mensagem clara.
|
||||
//! zlib, bzip2 e LZFSE (`ULFO`). Imagens cifradas (`encrcdsa`) são recusadas com mensagem
|
||||
//! clara.
|
||||
|
||||
use super::{bad, be32, be64, read_exact, read_vec, Backend};
|
||||
use crate::source::{FileSource, Source};
|
||||
@@ -187,7 +187,8 @@ impl Dmg {
|
||||
bzip2::read::BzDecoder::new(&raw[..]).read_to_end(&mut out)?;
|
||||
}
|
||||
0x8000_0007 => {
|
||||
return Err(bad("chunk LZFSE (formato ULFO) ainda não é suportado"));
|
||||
lzfse_rust::decode_bytes(&raw, &mut out)
|
||||
.map_err(|e| bad(format!("chunk LZFSE inválido: {e}")))?;
|
||||
}
|
||||
t => return Err(bad(format!("tipo de chunk DMG 0x{t:08x} desconhecido"))),
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
//!
|
||||
//! Cada formato é aberto como o disco que contém, somente para leitura, e entra na cadeia
|
||||
//! normal (partições, sistemas de arquivos, cifras) por meio de [`Source`]. Discos
|
||||
//! diferenciais (VHD e VHDX com pai) seguem a cadeia até o disco base; QCOW2 e VMDK com
|
||||
//! arquivo de apoio são recusados com mensagem clara, porque ler só a camada filha
|
||||
//! devolveria dados errados.
|
||||
//! diferenciais (VHD e VHDX com pai, QCOW2 com arquivo de apoio, VMDK com pai) seguem a
|
||||
//! cadeia até o disco base. A identidade do pai (UUID, CID, data) não é conferida: se o pai foi
|
||||
//! alterado depois do filho, os dados lidos refletem o pai atual.
|
||||
|
||||
mod dmg;
|
||||
mod qcow2;
|
||||
@@ -130,8 +130,8 @@ pub(crate) fn open_depth(path: &Path, depth: u32) -> Result<Option<VDisk>> {
|
||||
};
|
||||
let name = format!("{format} {}", path.display());
|
||||
let inner: Box<dyn Backend> = match format {
|
||||
"QCOW2" => Box::new(qcow2::Qcow2::open(file).map_err(|e| io_err(e, &name))?),
|
||||
"VMDK" => Box::new(vmdk::Vmdk::open(file, path).map_err(|e| io_err(e, &name))?),
|
||||
"QCOW2" => Box::new(qcow2::Qcow2::open(file, path, depth).map_err(|e| io_err(e, &name))?),
|
||||
"VMDK" => Box::new(vmdk::Vmdk::open(file, path, depth).map_err(|e| io_err(e, &name))?),
|
||||
"VHD" => Box::new(vhd::Vhd::open(file, path, depth).map_err(|e| io_err(e, &name))?),
|
||||
"VHDX" => Box::new(vhdx::Vhdx::open(file, path, depth).map_err(|e| io_err(e, &name))?),
|
||||
_ => Box::new(dmg::Dmg::open(file).map_err(|e| io_err(e, &name))?),
|
||||
@@ -139,17 +139,23 @@ pub(crate) fn open_depth(path: &Path, depth: u32) -> Result<Option<VDisk>> {
|
||||
Ok(Some(VDisk { inner, format }))
|
||||
}
|
||||
|
||||
/// Abre o disco pai de um VHD/VHDX diferencial.
|
||||
/// Abre o disco pai (VHD/VHDX diferencial, QCOW2 com apoio, VMDK com pai). Com `raw`, o
|
||||
/// arquivo é tratado como disco bruto sem tentar reconhecer o formato.
|
||||
pub(crate) fn open_parent(
|
||||
candidates: &[std::path::PathBuf],
|
||||
depth: u32,
|
||||
raw: bool,
|
||||
) -> io::Result<Box<dyn Source>> {
|
||||
for p in candidates {
|
||||
if !p.is_file() {
|
||||
continue;
|
||||
}
|
||||
if let Some(v) = open_depth(p, depth + 1).map_err(|e| bad(e.to_string()))? {
|
||||
return Ok(Box::new(v));
|
||||
if !raw {
|
||||
if let Some(v) = open_depth(p, depth + 1).map_err(|e| bad(e.to_string()))? {
|
||||
return Ok(Box::new(v));
|
||||
}
|
||||
} else if depth > MAX_DEPTH {
|
||||
return Err(bad("cadeia de discos pai longa demais ou circular"));
|
||||
}
|
||||
let f = FileSource::open(p)?;
|
||||
return Ok(Box::new(f));
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
//! QCOW2 (QEMU), versões 2 e 3: tabelas L1/L2, clusters comprimidos (deflate e zstd) e
|
||||
//! clusters zerados. Imagens cifradas, com arquivo de apoio, com arquivo de dados externo ou
|
||||
//! com L2 estendido são recusadas.
|
||||
//! QCOW2 (QEMU), versões 2 e 3: tabelas L1/L2, clusters comprimidos (deflate e zstd),
|
||||
//! clusters zerados e arquivo de apoio (backing file): o que o filho não alocou vem do pai.
|
||||
//! Imagens cifradas, com arquivo de dados externo, com L2 estendido ou com apoio que não é um
|
||||
//! arquivo local (`json:`, `nbd:`, URLs) são recusadas.
|
||||
|
||||
use super::{bad, be32, be64, check_table, read_exact, read_vec, Backend};
|
||||
use super::{
|
||||
bad, be32, be64, candidate_paths, check_table, open_parent, read_exact, read_parent, read_vec,
|
||||
Backend,
|
||||
};
|
||||
use crate::source::{FileSource, Source};
|
||||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
const OFFSET_MASK: u64 = 0x00ff_ffff_ffff_fe00;
|
||||
@@ -16,12 +21,13 @@ pub(super) struct Qcow2 {
|
||||
cluster_bits: u32,
|
||||
l1: Vec<u64>,
|
||||
zstd: bool,
|
||||
parent: Option<Box<dyn Source>>,
|
||||
/// Último cluster comprimido decodificado: (deslocamento do cluster virtual, dados).
|
||||
cache: Mutex<Option<(u64, Vec<u8>)>>,
|
||||
}
|
||||
|
||||
impl Qcow2 {
|
||||
pub(super) fn open(file: FileSource) -> io::Result<Self> {
|
||||
pub(super) fn open(file: FileSource, path: &Path, depth: u32) -> io::Result<Self> {
|
||||
let h = read_vec(&file, 0, 112.min(file.size() as usize))?;
|
||||
if h.len() < 72 {
|
||||
return Err(bad("cabeçalho QCOW2 truncado"));
|
||||
@@ -30,11 +36,6 @@ impl Qcow2 {
|
||||
if version != 2 && version != 3 {
|
||||
return Err(bad(format!("versão QCOW {version} não suportada")));
|
||||
}
|
||||
if be64(&h, 8) != 0 {
|
||||
return Err(bad(
|
||||
"a imagem QCOW2 usa arquivo de apoio (backing file); abrir só a camada filha daria dados errados",
|
||||
));
|
||||
}
|
||||
let cluster_bits = be32(&h, 20);
|
||||
if !(9..=21).contains(&cluster_bits) {
|
||||
return Err(bad(format!(
|
||||
@@ -63,6 +64,11 @@ impl Qcow2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
let parent = if be64(&h, 8) != 0 {
|
||||
Some(Self::open_backing(&file, &h, version, path, depth)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let l1_size = be32(&h, 36) as u64;
|
||||
let l1_off = be64(&h, 40);
|
||||
let n = check_table(l1_size, 8, "tabela L1")?;
|
||||
@@ -74,10 +80,65 @@ impl Qcow2 {
|
||||
cluster_bits,
|
||||
l1,
|
||||
zstd,
|
||||
parent,
|
||||
cache: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
/// Abre o arquivo de apoio. O formato vem da extensão de cabeçalho `0xE2792ACA`; `raw`
|
||||
/// (ou ausente em v2) é lido sem reconhecer formato, como o QEMU faz.
|
||||
fn open_backing(
|
||||
file: &FileSource,
|
||||
h: &[u8],
|
||||
version: u32,
|
||||
path: &Path,
|
||||
depth: u32,
|
||||
) -> io::Result<Box<dyn Source>> {
|
||||
let (off, len) = (be64(h, 8), be32(h, 16) as usize);
|
||||
if len == 0 || len > 1023 {
|
||||
return Err(bad("nome do arquivo de apoio QCOW2 inválido"));
|
||||
}
|
||||
let name = String::from_utf8_lossy(&read_vec(file, off, len)?).into_owned();
|
||||
if name.starts_with("json:")
|
||||
|| name.contains("://")
|
||||
|| name.split(':').next().is_some_and(|p| {
|
||||
matches!(p, "nbd" | "ssh" | "rbd" | "sheepdog" | "gluster" | "iscsi")
|
||||
})
|
||||
{
|
||||
return Err(bad(format!(
|
||||
"o arquivo de apoio {name} não é um arquivo local; só caminhos de arquivo são suportados"
|
||||
)));
|
||||
}
|
||||
// extensões de cabeçalho: depois do cabeçalho, alinhadas a 8 bytes
|
||||
let hlen = if version == 3 && h.len() >= 104 {
|
||||
be32(h, 100) as u64
|
||||
} else {
|
||||
72
|
||||
};
|
||||
let mut raw = false;
|
||||
let mut pos = hlen.div_ceil(8) * 8;
|
||||
for _ in 0..64 {
|
||||
let mut eh = [0u8; 8];
|
||||
if read_exact(file, pos, &mut eh).is_err() {
|
||||
break;
|
||||
}
|
||||
let (kind, elen) = (be32(&eh, 0), be32(&eh, 4) as u64);
|
||||
if kind == 0 || elen > 1 << 20 {
|
||||
break;
|
||||
}
|
||||
if kind == 0xE279_2ACA {
|
||||
let fmt = read_vec(file, pos + 8, elen as usize)?;
|
||||
raw = fmt == b"raw";
|
||||
}
|
||||
pos += 8 + elen.div_ceil(8) * 8;
|
||||
}
|
||||
if version == 2 {
|
||||
raw = false;
|
||||
}
|
||||
let dir = path.parent().unwrap_or(Path::new("."));
|
||||
open_parent(&candidate_paths(dir, &[name]), depth, raw)
|
||||
}
|
||||
|
||||
fn cluster_size(&self) -> u64 {
|
||||
1 << self.cluster_bits
|
||||
}
|
||||
@@ -139,8 +200,10 @@ impl Backend for Qcow2 {
|
||||
return Ok(n);
|
||||
}
|
||||
let off = entry & OFFSET_MASK;
|
||||
if off == 0 || entry & 1 != 0 {
|
||||
buf[..n].fill(0);
|
||||
if entry & 1 != 0 {
|
||||
buf[..n].fill(0); // zerado de propósito: não consulta o pai
|
||||
} else if off == 0 {
|
||||
read_parent(&self.parent, voff, &mut buf[..n])?;
|
||||
} else {
|
||||
read_exact(&self.file, off + within, &mut buf[..n])?;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Vhd {
|
||||
}
|
||||
}
|
||||
names.push(utf16(&dh[64..64 + 512], true));
|
||||
Some(open_parent(&candidate_paths(dir, &names), depth)?)
|
||||
Some(open_parent(&candidate_paths(dir, &names), depth, false)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -130,7 +130,7 @@ impl Vhdx {
|
||||
}
|
||||
}
|
||||
let dir = path.parent().unwrap_or(Path::new("."));
|
||||
Some(open_parent(&candidate_paths(dir, &names), depth)?)
|
||||
Some(open_parent(&candidate_paths(dir, &names), depth, false)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! VMDK (VMware, VirtualBox): extensões esparsas (inclusive `streamOptimized` com grãos
|
||||
//! comprimidos) e descritores de texto com extensões `FLAT`, `SPARSE` e `ZERO`, como
|
||||
//! `monolithicFlat` e `twoGbMaxExtentSparse`. Discos com pai (snapshots) e o formato SEsparse
|
||||
//! são recusados.
|
||||
//! `monolithicFlat` e `twoGbMaxExtentSparse`. Discos com pai (snapshots) leem o pai nos grãos
|
||||
//! que o filho não alocou; o formato SEsparse é recusado.
|
||||
|
||||
use super::{bad, check_table, le16, le32, le64, read_exact, read_vec, Backend};
|
||||
use super::{
|
||||
bad, candidate_paths, check_table, le16, le32, le64, open_parent, read_exact, read_parent,
|
||||
read_vec, Backend,
|
||||
};
|
||||
use crate::source::{FileSource, Source};
|
||||
use std::io::{self, Read};
|
||||
use std::path::Path;
|
||||
@@ -36,20 +39,73 @@ struct Extent {
|
||||
pub(super) struct Vmdk {
|
||||
extents: Vec<Extent>,
|
||||
size: u64,
|
||||
parent: Option<Box<dyn Source>>,
|
||||
}
|
||||
|
||||
fn parent_in(descriptor: &str) -> bool {
|
||||
descriptor.lines().any(|l| {
|
||||
let l = l.trim();
|
||||
l.strip_prefix("parentCID")
|
||||
/// Valor de uma chave `chave=valor` do descritor, sem aspas.
|
||||
fn desc_value(descriptor: &str, key: &str) -> Option<String> {
|
||||
descriptor.lines().find_map(|l| {
|
||||
l.trim()
|
||||
.strip_prefix(key)
|
||||
.and_then(|r| r.trim_start().strip_prefix('='))
|
||||
.is_some_and(|v| {
|
||||
let v = v.trim().trim_matches('"');
|
||||
!v.is_empty() && !v.eq_ignore_ascii_case("ffffffff")
|
||||
})
|
||||
.map(|v| v.trim().trim_matches('"').to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Descritor (texto) de um arquivo VMDK: o próprio arquivo, se for descritor, ou o embutido
|
||||
/// logo depois do cabeçalho de uma extensão esparsa.
|
||||
fn descriptor_of(path: &Path) -> Option<String> {
|
||||
let f = FileSource::open(path).ok()?;
|
||||
let head = read_vec(&f, 0, (f.size() as usize).min(24 * 512)).ok()?;
|
||||
if head.starts_with(b"KDMV") {
|
||||
let off = (le64(&head, 28) * SECTOR) as usize;
|
||||
let len = (le64(&head, 36) * SECTOR) as usize;
|
||||
let d = head.get(off..(off + len).min(head.len()))?;
|
||||
let end = d.iter().position(|&b| b == 0).unwrap_or(d.len());
|
||||
return Some(String::from_utf8_lossy(&d[..end]).into_owned());
|
||||
}
|
||||
head.starts_with(b"# Disk Desc")
|
||||
.then(|| String::from_utf8_lossy(&head).into_owned())
|
||||
}
|
||||
|
||||
/// Como o descritor identifica o pai: pelo nome (`parentFileNameHint`) e/ou, no VirtualBox, só
|
||||
/// pelo UUID (`ddb.uuid.parent`); `cid` é o `parentCID` que o pai precisa ter.
|
||||
struct ParentRef {
|
||||
name: Option<String>,
|
||||
uuid: Option<String>,
|
||||
cid: Option<String>,
|
||||
}
|
||||
|
||||
fn parent_ref(descriptor: &str) -> Option<ParentRef> {
|
||||
let cid = desc_value(descriptor, "parentCID")
|
||||
.filter(|c| !c.is_empty() && !c.eq_ignore_ascii_case("ffffffff"));
|
||||
let uuid = desc_value(descriptor, "ddb.uuid.parent")
|
||||
.filter(|u| !u.is_empty() && u.chars().any(|c| c != '0' && c != '-'));
|
||||
let name = desc_value(descriptor, "parentFileNameHint").filter(|n| !n.is_empty());
|
||||
(cid.is_some() || uuid.is_some()).then_some(ParentRef { name, uuid, cid })
|
||||
}
|
||||
|
||||
/// Procura, na pasta do filho e nas duas acima, um VMDK cujo `ddb.uuid.image` seja `uuid`.
|
||||
fn find_by_uuid(dir: &Path, uuid: &str) -> Option<std::path::PathBuf> {
|
||||
let mut d = Some(dir);
|
||||
for _ in 0..3 {
|
||||
let cur = d?;
|
||||
for e in std::fs::read_dir(cur).ok()?.flatten() {
|
||||
let p = e.path();
|
||||
if p.extension()
|
||||
.is_some_and(|x| x.eq_ignore_ascii_case("vmdk"))
|
||||
&& descriptor_of(&p)
|
||||
.and_then(|ds| desc_value(&ds, "ddb.uuid.image"))
|
||||
.is_some_and(|u| u.eq_ignore_ascii_case(uuid))
|
||||
{
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
d = cur.parent();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl Sparse {
|
||||
fn open(file: FileSource) -> io::Result<(Self, u64, Option<String>)> {
|
||||
let mut h = read_vec(&file, 0, SECTOR as usize)?;
|
||||
@@ -95,11 +151,6 @@ impl Sparse {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if descriptor.as_deref().is_some_and(parent_in) {
|
||||
return Err(bad(
|
||||
"VMDK com disco pai (snapshot); abrir só a camada filha daria dados errados",
|
||||
));
|
||||
}
|
||||
let gde = capacity.div_ceil(gtes * grain);
|
||||
let n = check_table(gde, 4, "diretório de grãos")?;
|
||||
let raw = read_vec(&file, gd_off * SECTOR, n * 4)?;
|
||||
@@ -118,7 +169,9 @@ impl Sparse {
|
||||
))
|
||||
}
|
||||
|
||||
fn read(&self, off: u64, buf: &mut [u8]) -> io::Result<usize> {
|
||||
/// Lê dentro de um grão. O `bool` é `false` quando o grão não está alocado neste arquivo
|
||||
/// (o chamador completa com o pai); grão zerado de propósito devolve zeros.
|
||||
fn read(&self, off: u64, buf: &mut [u8]) -> io::Result<(usize, bool)> {
|
||||
let g = off / self.grain;
|
||||
let within = off % self.grain;
|
||||
let n = buf.len().min((self.grain - within) as usize);
|
||||
@@ -129,13 +182,16 @@ impl Sparse {
|
||||
read_exact(&self.file, gt_sector * SECTOR + (g % self.gtes) * 4, &mut b)?;
|
||||
sector = u32::from_le_bytes(b) as u64;
|
||||
}
|
||||
if sector <= 1 {
|
||||
if sector == 0 {
|
||||
return Ok((n, false));
|
||||
}
|
||||
if sector == 1 {
|
||||
buf[..n].fill(0);
|
||||
return Ok(n);
|
||||
return Ok((n, true));
|
||||
}
|
||||
if !self.compressed {
|
||||
read_exact(&self.file, sector * SECTOR + within, &mut buf[..n])?;
|
||||
return Ok(n);
|
||||
return Ok((n, true));
|
||||
}
|
||||
let mut cache = self.cache.lock().unwrap();
|
||||
if cache.as_ref().map(|c| c.0) != Some(g) {
|
||||
@@ -153,17 +209,12 @@ impl Sparse {
|
||||
}
|
||||
let data = &cache.as_ref().unwrap().1;
|
||||
buf[..n].copy_from_slice(&data[within as usize..within as usize + n]);
|
||||
Ok(n)
|
||||
Ok((n, true))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensões de um descritor de texto: `RW 2048 FLAT "disco-f001.vmdk" 0`.
|
||||
fn parse_descriptor(text: &str, dir: &Path) -> io::Result<Vec<Extent>> {
|
||||
if parent_in(text) {
|
||||
return Err(bad(
|
||||
"VMDK com disco pai (snapshot); abrir só a camada filha daria dados errados",
|
||||
));
|
||||
}
|
||||
let mut out = vec![];
|
||||
let mut start = 0u64;
|
||||
for line in text.lines() {
|
||||
@@ -216,27 +267,79 @@ fn parse_descriptor(text: &str, dir: &Path) -> io::Result<Vec<Extent>> {
|
||||
}
|
||||
|
||||
impl Vmdk {
|
||||
pub(super) fn open(file: FileSource, path: &Path) -> io::Result<Self> {
|
||||
fn open_parent_of(r: &ParentRef, dir: &Path, depth: u32) -> io::Result<Box<dyn Source>> {
|
||||
let mut found = r
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|n| candidate_paths(dir, std::slice::from_ref(n)))
|
||||
.unwrap_or_default();
|
||||
let by_name = found.iter().any(|p| p.is_file());
|
||||
if !by_name {
|
||||
if let Some(p) = r.uuid.as_deref().and_then(|u| find_by_uuid(dir, u)) {
|
||||
found = vec![p];
|
||||
}
|
||||
}
|
||||
if r.name.is_none() && r.uuid.is_none() {
|
||||
return Err(bad(
|
||||
"VMDK com pai, mas o descritor não diz qual (sem parentFileNameHint nem ddb.uuid.parent)",
|
||||
));
|
||||
}
|
||||
if r.name.is_none() && found.is_empty() {
|
||||
return Err(bad(
|
||||
"VMDK com pai identificado só por UUID (VirtualBox) e o pai não está na pasta do filho nem nas duas acima",
|
||||
));
|
||||
}
|
||||
if let Some(cid) = &r.cid {
|
||||
// o pai precisa ser o mesmo de quando o filho foi criado
|
||||
if let Some(p) = found.iter().find(|p| p.is_file()) {
|
||||
let have = descriptor_of(p).and_then(|d| desc_value(&d, "CID"));
|
||||
if have
|
||||
.as_deref()
|
||||
.is_some_and(|h| !h.eq_ignore_ascii_case(cid))
|
||||
{
|
||||
return Err(bad(format!(
|
||||
"o pai {} foi alterado depois do filho (CID {} em vez de {cid}); os dados do filho não valem mais",
|
||||
p.display(),
|
||||
have.unwrap_or_default()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
open_parent(&found, depth, false)
|
||||
}
|
||||
|
||||
pub(super) fn open(file: FileSource, path: &Path, depth: u32) -> io::Result<Self> {
|
||||
let mut head = [0u8; 4];
|
||||
let _ = file.read_at(0, &mut head);
|
||||
let extents = if &head == b"KDMV" {
|
||||
let (sp, size, _) = Sparse::open(file)?;
|
||||
vec![Extent {
|
||||
start: 0,
|
||||
len: size,
|
||||
kind: Kind::Sparse(sp),
|
||||
}]
|
||||
let dir = path.parent().unwrap_or(Path::new("."));
|
||||
let (extents, descriptor) = if &head == b"KDMV" {
|
||||
let (sp, size, descriptor) = Sparse::open(file)?;
|
||||
(
|
||||
vec![Extent {
|
||||
start: 0,
|
||||
len: size,
|
||||
kind: Kind::Sparse(sp),
|
||||
}],
|
||||
descriptor,
|
||||
)
|
||||
} else {
|
||||
if file.size() > 1 << 20 {
|
||||
return Err(bad("descritor VMDK grande demais"));
|
||||
}
|
||||
let raw = read_vec(&file, 0, file.size() as usize)?;
|
||||
let text = String::from_utf8_lossy(&raw);
|
||||
let dir = path.parent().unwrap_or(Path::new("."));
|
||||
parse_descriptor(&text, dir)?
|
||||
let text = String::from_utf8_lossy(&raw).into_owned();
|
||||
(parse_descriptor(&text, dir)?, Some(text))
|
||||
};
|
||||
let parent = match descriptor.as_deref().and_then(parent_ref) {
|
||||
Some(r) => Some(Self::open_parent_of(&r, dir, depth)?),
|
||||
None => None,
|
||||
};
|
||||
let size = extents.iter().map(|e| e.len).sum();
|
||||
Ok(Self { extents, size })
|
||||
Ok(Self {
|
||||
extents,
|
||||
size,
|
||||
parent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +364,13 @@ impl Backend for Vmdk {
|
||||
read_exact(file, offset + rel, &mut buf[..n])?;
|
||||
Ok(n)
|
||||
}
|
||||
Kind::Sparse(s) => s.read(rel, &mut buf[..n]),
|
||||
Kind::Sparse(s) => {
|
||||
let (got, allocated) = s.read(rel, &mut buf[..n])?;
|
||||
if !allocated {
|
||||
read_parent(&self.parent, voff, &mut buf[..got])?;
|
||||
}
|
||||
Ok(got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user