puzzlefs_lib/format/
types.rs

1use capnp::{message, serialize};
2use memmap2::{Mmap, MmapOptions};
3use nix::errno::Errno;
4use nix::sys::stat;
5use std::backtrace::Backtrace;
6use std::collections::BTreeMap;
7use std::ffi::OsStr;
8use std::ffi::OsString;
9use std::fmt;
10use std::fs;
11use std::io;
12use std::os::unix::ffi::OsStrExt;
13use std::os::unix::ffi::OsStringExt;
14use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
15use std::path::Path;
16
17use serde::de::Error as SerdeError;
18use serde::de::Visitor;
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20
21use super::error::{Result, WireFormatError};
22use hex::FromHexError;
23
24pub const DEFAULT_FILE_PERMISSIONS: u16 = 0o644;
25pub const SHA256_BLOCK_SIZE: usize = 32;
26// We use a BTreeMap instead of a HashMap because the BTreeMap is sorted, thus we get a
27// reproducible representation of the serialized metadata
28pub type VerityData = BTreeMap<[u8; SHA256_BLOCK_SIZE], [u8; SHA256_BLOCK_SIZE]>;
29
30#[derive(Debug)]
31pub struct Rootfs {
32    pub metadatas: Vec<Vec<Inode>>,
33    pub fs_verity_data: VerityData,
34    pub manifest_version: u64,
35}
36
37impl TryFrom<RootfsReader> for Rootfs {
38    type Error = WireFormatError;
39    fn try_from(rootfs_reader: RootfsReader) -> Result<Self> {
40        Rootfs::from_capnp(rootfs_reader.reader.get()?)
41    }
42}
43
44impl Rootfs {
45    pub fn from_capnp(reader: crate::metadata_capnp::rootfs::Reader<'_>) -> Result<Self> {
46        let metadata_vec = reader
47            .get_metadatas()?
48            .iter()
49            .map(InodeVector::from_capnp)
50            .collect::<Result<Vec<Vec<_>>>>()?;
51
52        let capnp_verities = reader.get_fs_verity_data()?;
53        let mut fs_verity_data = VerityData::new();
54
55        for capnp_verity in capnp_verities {
56            let digest = capnp_verity.get_digest()?.try_into()?;
57            let verity = capnp_verity.get_verity()?.try_into()?;
58            fs_verity_data.insert(digest, verity);
59        }
60
61        Ok(Rootfs {
62            metadatas: metadata_vec,
63            fs_verity_data,
64            manifest_version: reader.get_manifest_version(),
65        })
66    }
67
68    pub fn fill_capnp(
69        &self,
70        builder: &mut crate::metadata_capnp::rootfs::Builder<'_>,
71    ) -> Result<()> {
72        builder.set_manifest_version(self.manifest_version);
73
74        let metadatas_len = self.metadatas.len().try_into()?;
75        let mut capnp_metadatas = builder.reborrow().init_metadatas(metadatas_len);
76
77        for (i, metadata) in self.metadatas.iter().enumerate() {
78            // we already checked that the length of metadatas fits inside a u32
79            let mut capnp_metadata = capnp_metadatas.reborrow().get(i as u32);
80            InodeVector::fill_capnp(metadata, &mut capnp_metadata)?;
81        }
82
83        let verity_data_len = self.fs_verity_data.len().try_into()?;
84        let mut capnp_verities = builder.reborrow().init_fs_verity_data(verity_data_len);
85
86        for (i, (digest, verity)) in self.fs_verity_data.iter().enumerate() {
87            // we already checked that the length of verity_data fits inside a u32
88            let mut capnp_verity = capnp_verities.reborrow().get(i as u32);
89            capnp_verity.set_digest(digest);
90            capnp_verity.set_verity(verity);
91        }
92
93        Ok(())
94    }
95}
96
97pub struct RootfsReader {
98    reader: message::TypedReader<
99        ::capnp::serialize::BufferSegments<Mmap>,
100        crate::metadata_capnp::rootfs::Owned,
101    >,
102}
103
104impl RootfsReader {
105    pub fn open(f: cap_std::fs::File) -> Result<Self> {
106        // We know the loaded message is safe, so we're allowing unlimited reads.
107        let unlimited_reads = message::ReaderOptions {
108            traversal_limit_in_words: None,
109            nesting_limit: 64,
110        };
111        let mmapped_region = unsafe { MmapOptions::new().map_copy_read_only(&f)? };
112        let segments = serialize::BufferSegments::new(mmapped_region, unlimited_reads)?;
113        let reader = message::Reader::new(segments, unlimited_reads).into_typed();
114
115        Ok(Self { reader })
116    }
117
118    pub fn get_manifest_version(&self) -> Result<u64> {
119        Ok(self.reader.get()?.get_manifest_version())
120    }
121
122    pub fn get_verity_data(&self) -> Result<VerityData> {
123        let mut fs_verity_data = VerityData::new();
124
125        let capnp_verities = self.reader.get()?.get_fs_verity_data()?;
126        for capnp_verity in capnp_verities {
127            let digest = capnp_verity.get_digest()?.try_into()?;
128            let verity = capnp_verity.get_verity()?.try_into()?;
129            fs_verity_data.insert(digest, verity);
130        }
131        Ok(fs_verity_data)
132    }
133
134    pub fn find_inode(&self, ino: u64) -> Result<Inode> {
135        for layer in self.reader.get()?.get_metadatas()?.iter() {
136            let inode_vector = InodeVector { reader: layer };
137
138            if let Some(inode) = inode_vector.find_inode(ino)? {
139                let inode = Inode::from_capnp(inode)?;
140                if let InodeMode::Wht = inode.mode {
141                    // TODO: seems like this should really be an Option.
142                    return Err(WireFormatError::from_errno(Errno::ENOENT));
143                }
144                return Ok(inode);
145            }
146        }
147
148        Err(WireFormatError::from_errno(Errno::ENOENT))
149    }
150
151    pub fn max_inode(&self) -> Result<Ino> {
152        let mut max: Ino = 1;
153        for layer in self.reader.get()?.get_metadatas()?.iter() {
154            let inode_vector = InodeVector { reader: layer };
155            if let Some(ino) = inode_vector.max_ino()? {
156                max = std::cmp::max(ino, max)
157            }
158        }
159        Ok(max)
160    }
161}
162
163// TODO: should this be an ociv1 digest and include size and media type?
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct BlobRef {
166    pub digest: [u8; SHA256_BLOCK_SIZE],
167    pub offset: u64,
168    pub compressed: bool,
169}
170
171impl BlobRef {
172    pub fn from_capnp(reader: crate::metadata_capnp::blob_ref::Reader<'_>) -> Result<Self> {
173        let digest = reader.get_digest()?;
174        Ok(BlobRef {
175            digest: digest.try_into()?,
176            offset: reader.get_offset(),
177            compressed: reader.get_compressed(),
178        })
179    }
180    pub fn fill_capnp(&self, builder: &mut crate::metadata_capnp::blob_ref::Builder<'_>) {
181        builder.set_digest(&self.digest);
182        builder.set_offset(self.offset);
183        builder.set_compressed(self.compressed);
184    }
185}
186
187#[derive(Debug, PartialEq, Eq)]
188pub struct DirEnt {
189    pub ino: Ino,
190    pub name: Vec<u8>,
191}
192
193#[derive(Debug, PartialEq, Eq)]
194pub struct DirList {
195    // TODO: flags instead?
196    pub look_below: bool,
197    pub entries: Vec<DirEnt>,
198}
199
200#[derive(Debug)]
201pub struct FileChunkList {
202    pub chunks: Vec<FileChunk>,
203}
204
205#[derive(Debug, PartialEq, Eq)]
206pub struct FileChunk {
207    pub blob: BlobRef,
208    pub len: u64,
209}
210
211pub type Ino = u64;
212
213impl FileChunk {
214    pub fn from_capnp(reader: crate::metadata_capnp::file_chunk::Reader<'_>) -> Result<Self> {
215        let len = reader.get_len();
216        let blob = BlobRef::from_capnp(reader.get_blob()?)?;
217
218        Ok(FileChunk { blob, len })
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    const DEFAULT_DIRECTORY_PERMISSIONS: u16 = 0o755;
227
228    fn blobref_roundtrip(original: BlobRef) {
229        let mut message = ::capnp::message::Builder::new_default();
230        let mut capnp_blob_ref =
231            message.init_root::<crate::metadata_capnp::blob_ref::Builder<'_>>();
232
233        original.fill_capnp(&mut capnp_blob_ref);
234
235        let mut buf = Vec::new();
236        ::capnp::serialize::write_message(&mut buf, &message)
237            .expect("capnp::serialize::write_message failed");
238
239        let message_reader = serialize::read_message_from_flat_slice(
240            &mut &buf[..],
241            ::capnp::message::ReaderOptions::new(),
242        )
243        .expect("read_message_from_flat_slice failed");
244        let blobref_reader = message_reader
245            .get_root::<crate::metadata_capnp::blob_ref::Reader<'_>>()
246            .expect("message_reader.get_root failed");
247        let deserialized = BlobRef::from_capnp(blobref_reader).expect("BlobRef::from_capnp failed");
248
249        assert_eq!(original, deserialized);
250    }
251
252    #[test]
253    fn test_blobref_serialization() {
254        let local = BlobRef {
255            offset: 42,
256            digest: [
257                0xb7, 0x2e, 0x68, 0x50, 0x82, 0xd1, 0xdd, 0xfe, 0xb6, 0xcc, 0x31, 0xa5, 0x35, 0x29,
258                0x12, 0xFE, 0x3f, 0x51, 0x14, 0x65, 0xf5, 0x27, 0xa5, 0x1a, 0xb3, 0xff, 0xd3, 0xb8,
259                0xAA, 0x3C, 0x25, 0xDD,
260            ],
261            compressed: true,
262        };
263        blobref_roundtrip(local)
264    }
265
266    #[test]
267    fn test_inode_is_constant_serialized_size() {
268        // TODO: this is the sort of think quickcheck is perfect for...
269        let testcases = vec![
270            Inode {
271                ino: 0,
272                mode: InodeMode::Unknown,
273                uid: 0,
274                gid: 0,
275                permissions: 0,
276                additional: None,
277            },
278            Inode {
279                ino: 0,
280                mode: InodeMode::Lnk,
281                uid: 0,
282                gid: 0,
283                permissions: 0,
284                additional: None,
285            },
286            Inode {
287                ino: 0,
288                mode: InodeMode::File {
289                    chunks: vec![FileChunk {
290                        blob: BlobRef {
291                            digest: [
292                                0x12, 0x44, 0xFE, 0xDD, 0x13, 0x39, 0x88, 0x12, 0x48, 0xA8, 0xF8,
293                                0xE4, 0x22, 0x12, 0x15, 0x16, 0x12, 0x44, 0xFE, 0xDD, 0x31, 0x93,
294                                0x88, 0x21, 0x84, 0x8A, 0xF8, 0x4E, 0x22, 0x12, 0x51, 0x16,
295                            ],
296                            offset: 100,
297                            compressed: true,
298                        },
299                        len: 100,
300                    }],
301                },
302                uid: 0,
303                gid: 0,
304                permissions: DEFAULT_FILE_PERMISSIONS,
305                additional: None,
306            },
307            Inode {
308                ino: 65343,
309                mode: InodeMode::Chr {
310                    major: 64,
311                    minor: 65536,
312                },
313                uid: 10,
314                gid: 10000,
315                permissions: DEFAULT_DIRECTORY_PERMISSIONS,
316                additional: None,
317            },
318            Inode {
319                ino: 0,
320                mode: InodeMode::Lnk,
321                uid: 0,
322                gid: 0,
323                permissions: 0xFFFF,
324                additional: Some(InodeAdditional {
325                    xattrs: vec![Xattr {
326                        key: b"some extended attribute".to_vec(),
327                        val: b"with some value".to_vec(),
328                    }],
329                    symlink_target: Some(b"some/other/path".to_vec()),
330                }),
331            },
332        ];
333
334        for test in testcases {
335            let wire = test.to_wire().unwrap();
336            let message_reader = serialize::read_message_from_flat_slice(
337                &mut &wire[..],
338                ::capnp::message::ReaderOptions::new(),
339            )
340            .expect("read_message_from_flat_slice failed");
341            let inode_reader = message_reader
342                .get_root::<crate::metadata_capnp::inode::Reader<'_>>()
343                .expect("message_reader.get_root failed");
344            let after = Inode::from_capnp(inode_reader).expect("BlobRef::from_capnp failed");
345            assert_eq!(test, after);
346        }
347    }
348}
349
350#[derive(Debug, PartialEq, Eq)]
351pub struct Inode {
352    pub ino: Ino,
353    pub mode: InodeMode,
354    pub uid: u32,
355    pub gid: u32,
356    pub permissions: u16,
357    pub additional: Option<InodeAdditional>,
358}
359
360impl Inode {
361    pub fn from_capnp(reader: crate::metadata_capnp::inode::Reader<'_>) -> Result<Self> {
362        Ok(Inode {
363            ino: reader.get_ino(),
364            mode: InodeMode::from_capnp(reader.get_mode())?,
365            uid: reader.get_uid(),
366            gid: reader.get_gid(),
367            permissions: reader.get_permissions(),
368            additional: InodeAdditional::from_capnp(reader.get_additional()?)?,
369        })
370    }
371
372    pub fn fill_capnp(
373        &self,
374        builder: &mut crate::metadata_capnp::inode::Builder<'_>,
375    ) -> Result<()> {
376        builder.set_ino(self.ino);
377
378        let mut mode_builder = builder.reborrow().init_mode();
379        self.mode.fill_capnp(&mut mode_builder)?;
380
381        builder.set_uid(self.uid);
382        builder.set_gid(self.gid);
383        builder.set_permissions(self.permissions);
384
385        if let Some(additional) = &self.additional {
386            let mut additional_builder = builder.reborrow().init_additional();
387            additional.fill_capnp(&mut additional_builder)?;
388        }
389
390        Ok(())
391    }
392
393    pub fn new_dir(
394        ino: Ino,
395        md: &fs::Metadata,
396        dir_list: DirList,
397        additional: Option<InodeAdditional>,
398    ) -> io::Result<Self> {
399        if !md.is_dir() {
400            return Err(io::Error::new(
401                io::ErrorKind::Other,
402                format!("{ino} is a dir"),
403            ));
404        }
405
406        let mode = InodeMode::Dir { dir_list };
407        Ok(Self::new_inode(ino, md, mode, additional))
408    }
409
410    pub fn new_file(
411        ino: Ino,
412        md: &fs::Metadata,
413        file_chunks: Vec<FileChunk>,
414        additional: Option<InodeAdditional>,
415    ) -> io::Result<Self> {
416        if !md.is_file() {
417            return Err(io::Error::new(
418                io::ErrorKind::Other,
419                format!("{ino} is a file"),
420            ));
421        }
422
423        let mode = InodeMode::File {
424            chunks: file_chunks,
425        };
426        Ok(Self::new_inode(ino, md, mode, additional))
427    }
428
429    pub fn new_other(
430        ino: Ino,
431        md: &fs::Metadata,
432        additional: Option<InodeAdditional>,
433    ) -> io::Result<Self> {
434        let file_type = md.file_type();
435        let mode = if file_type.is_fifo() {
436            InodeMode::Fifo
437        } else if file_type.is_char_device() {
438            let major = stat::major(md.rdev());
439            let minor = stat::minor(md.rdev());
440            InodeMode::Chr { major, minor }
441        } else if file_type.is_dir() {
442            return Err(io::Error::new(
443                io::ErrorKind::Other,
444                format!("{ino} is a dir"),
445            ));
446        } else if file_type.is_block_device() {
447            let major = stat::major(md.rdev());
448            let minor = stat::minor(md.rdev());
449            InodeMode::Blk { major, minor }
450        } else if file_type.is_file() {
451            return Err(io::Error::new(
452                io::ErrorKind::Other,
453                format!("{ino} is a file"),
454            ));
455        } else if file_type.is_symlink() {
456            InodeMode::Lnk
457        } else if file_type.is_socket() {
458            InodeMode::Sock
459        } else {
460            InodeMode::Unknown
461        };
462
463        Ok(Self::new_inode(ino, md, mode, additional))
464    }
465
466    pub fn new_whiteout(ino: Ino) -> Self {
467        Inode {
468            ino,
469            mode: InodeMode::Wht,
470            uid: 0,
471            gid: 0,
472            permissions: DEFAULT_FILE_PERMISSIONS,
473            additional: None,
474        }
475    }
476
477    fn new_inode(
478        ino: Ino,
479        md: &fs::Metadata,
480        mode: InodeMode,
481        additional: Option<InodeAdditional>,
482    ) -> Self {
483        Inode {
484            ino,
485            mode,
486            uid: md.uid(),
487            gid: md.gid(),
488            // only preserve rwx permissions for user, group, others (9 bits) and SUID/SGID/sticky bit (3 bits)
489            permissions: (md.permissions().mode() & 0xFFF) as u16,
490            additional,
491        }
492    }
493
494    pub fn dir_entries(&self) -> Result<&Vec<DirEnt>> {
495        match &self.mode {
496            InodeMode::Dir { dir_list } => Ok(&dir_list.entries),
497            _ => Err(WireFormatError::from_errno(Errno::ENOTDIR)),
498        }
499    }
500
501    pub fn dir_lookup(&self, name: &[u8]) -> Result<u64> {
502        let entries = self.dir_entries()?;
503        entries
504            .iter()
505            .find(|dir_ent| dir_ent.name == name)
506            .map(|dir_ent| dir_ent.ino)
507            .ok_or_else(|| WireFormatError::from_errno(Errno::ENOENT))
508    }
509
510    pub fn file_len(&self) -> Result<u64> {
511        let chunks = match &self.mode {
512            InodeMode::File { chunks } => chunks,
513            _ => return Err(WireFormatError::from_errno(Errno::ENOTDIR)),
514        };
515        Ok(chunks.iter().map(|c| c.len).sum())
516    }
517
518    pub fn symlink_target(&self) -> Result<&OsStr> {
519        self.additional
520            .as_ref()
521            .and_then(|a| {
522                a.symlink_target
523                    .as_ref()
524                    .map(|x| OsStr::from_bytes(x.as_slice()))
525            })
526            .ok_or_else(|| WireFormatError::from_errno(Errno::ENOENT))
527    }
528
529    #[cfg(test)]
530    fn to_wire(&self) -> Result<Vec<u8>> {
531        let mut message = ::capnp::message::Builder::new_default();
532        let mut capnp_inode = message.init_root::<crate::metadata_capnp::inode::Builder<'_>>();
533
534        self.fill_capnp(&mut capnp_inode)?;
535
536        let mut buf = Vec::new();
537        ::capnp::serialize::write_message(&mut buf, &message)?;
538        Ok(buf)
539    }
540}
541
542#[derive(Debug, PartialEq, Eq)]
543pub enum InodeMode {
544    Unknown,
545    Fifo,
546    Chr { major: u64, minor: u64 },
547    Dir { dir_list: DirList },
548    Blk { major: u64, minor: u64 },
549    File { chunks: Vec<FileChunk> },
550    Lnk,
551    Sock,
552    Wht,
553}
554
555impl InodeMode {
556    fn from_capnp(reader: crate::metadata_capnp::inode::mode::Reader<'_>) -> Result<Self> {
557        match reader.which() {
558            Ok(crate::metadata_capnp::inode::mode::Unknown(())) => Ok(InodeMode::Unknown),
559            Ok(crate::metadata_capnp::inode::mode::Fifo(())) => Ok(InodeMode::Fifo),
560            Ok(crate::metadata_capnp::inode::mode::Lnk(())) => Ok(InodeMode::Lnk),
561            Ok(crate::metadata_capnp::inode::mode::Sock(())) => Ok(InodeMode::Sock),
562            Ok(crate::metadata_capnp::inode::mode::Wht(())) => Ok(InodeMode::Wht),
563            Ok(crate::metadata_capnp::inode::mode::Chr(reader)) => {
564                let r = reader?;
565                Ok(InodeMode::Chr {
566                    major: r.get_major(),
567                    minor: r.get_minor(),
568                })
569            }
570            Ok(crate::metadata_capnp::inode::mode::Blk(reader)) => {
571                let r = reader?;
572                Ok(InodeMode::Blk {
573                    major: r.get_major(),
574                    minor: r.get_minor(),
575                })
576            }
577            Ok(crate::metadata_capnp::inode::mode::File(reader)) => {
578                let r = reader?;
579                let chunks = r
580                    .iter()
581                    .map(FileChunk::from_capnp)
582                    .collect::<Result<Vec<FileChunk>>>()?;
583                Ok(InodeMode::File { chunks })
584            }
585            Ok(crate::metadata_capnp::inode::mode::Dir(reader)) => {
586                let r = reader?;
587                let entries = r
588                    .get_entries()?
589                    .iter()
590                    .map(|entry| {
591                        let ino = entry.get_ino();
592                        let dir_entry = entry.get_name().map(Vec::from);
593                        match dir_entry {
594                            Ok(d) => Ok(DirEnt { ino, name: d }),
595                            Err(e) => Err(WireFormatError::from(e)),
596                        }
597                    })
598                    .collect::<Result<Vec<DirEnt>>>()?;
599                let look_below = r.get_look_below();
600                Ok(InodeMode::Dir {
601                    dir_list: DirList {
602                        look_below,
603                        entries,
604                    },
605                })
606            }
607            Err(::capnp::NotInSchema(_e)) => {
608                Err(WireFormatError::InvalidSerializedData(Backtrace::capture()))
609            }
610        }
611    }
612
613    fn fill_capnp(
614        &self,
615        builder: &mut crate::metadata_capnp::inode::mode::Builder<'_>,
616    ) -> Result<()> {
617        match &self {
618            Self::Unknown => builder.set_unknown(()),
619            Self::Fifo => builder.set_fifo(()),
620            Self::Chr { major, minor } => {
621                let mut chr_builder = builder.reborrow().init_chr();
622                chr_builder.set_minor(*minor);
623                chr_builder.set_major(*major);
624            }
625            Self::Dir { dir_list } => {
626                let mut dir_builder = builder.reborrow().init_dir();
627                dir_builder.set_look_below(dir_list.look_below);
628                let entries_len = dir_list.entries.len().try_into()?;
629                let mut entries_builder = dir_builder.reborrow().init_entries(entries_len);
630
631                for (i, entry) in dir_list.entries.iter().enumerate() {
632                    // we already checked that the length of entries fits inside a u32
633                    let mut dir_entry_builder = entries_builder.reborrow().get(i as u32);
634                    dir_entry_builder.set_ino(entry.ino);
635                    dir_entry_builder.set_name(&entry.name);
636                }
637            }
638            Self::Blk { major, minor } => {
639                let mut blk_builder = builder.reborrow().init_blk();
640                blk_builder.set_minor(*minor);
641                blk_builder.set_major(*major);
642            }
643            Self::File { chunks } => {
644                let chunks_len = chunks.len().try_into()?;
645                let mut chunks_builder = builder.reborrow().init_file(chunks_len);
646
647                for (i, chunk) in chunks.iter().enumerate() {
648                    // we already checked that the length of chunks fits inside a u32
649                    let mut chunk_builder = chunks_builder.reborrow().get(i as u32);
650                    chunk_builder.set_len(chunk.len);
651                    let mut blob_ref_builder = chunk_builder.init_blob();
652                    chunk.blob.fill_capnp(&mut blob_ref_builder);
653                }
654            }
655            Self::Lnk => builder.set_lnk(()),
656            Self::Sock => builder.set_sock(()),
657            Self::Wht => builder.set_wht(()),
658        }
659        Ok(())
660    }
661}
662
663#[derive(Debug, PartialEq, Eq)]
664pub struct InodeAdditional {
665    pub xattrs: Vec<Xattr>,
666    pub symlink_target: Option<Vec<u8>>,
667}
668
669impl InodeAdditional {
670    pub fn from_capnp(
671        reader: crate::metadata_capnp::inode_additional::Reader<'_>,
672    ) -> Result<Option<Self>> {
673        if !(reader.has_xattrs() || reader.has_symlink_target()) {
674            return Ok(None);
675        }
676
677        let mut xattrs = Vec::new();
678        if reader.has_xattrs() {
679            for capnp_xattr in reader.get_xattrs()? {
680                let xattr = Xattr::from_capnp(capnp_xattr)?;
681                xattrs.push(xattr);
682            }
683        }
684
685        let symlink_target = if reader.has_symlink_target() {
686            Some(reader.get_symlink_target()?.to_vec())
687        } else {
688            None
689        };
690
691        Ok(Some(InodeAdditional {
692            xattrs,
693            symlink_target,
694        }))
695    }
696
697    pub fn fill_capnp(
698        &self,
699        builder: &mut crate::metadata_capnp::inode_additional::Builder<'_>,
700    ) -> Result<()> {
701        let xattrs_len = self.xattrs.len().try_into()?;
702        let mut xattrs_builder = builder.reborrow().init_xattrs(xattrs_len);
703
704        for (i, xattr) in self.xattrs.iter().enumerate() {
705            // we already checked that the length of xattrs fits inside a u32
706            let mut xattr_builder = xattrs_builder.reborrow().get(i as u32);
707            xattr.fill_capnp(&mut xattr_builder);
708        }
709
710        if let Some(symlink_target) = &self.symlink_target {
711            builder.set_symlink_target(symlink_target);
712        }
713
714        Ok(())
715    }
716
717    pub fn new(p: &Path, md: &fs::Metadata) -> io::Result<Option<Self>> {
718        let symlink_target = if md.file_type().is_symlink() {
719            let t = fs::read_link(p)?;
720            Some(OsString::from(t).into_vec())
721        } else {
722            None
723        };
724        let xattrs = Self::get_xattrs(p)?;
725        if symlink_target.is_none() && xattrs.is_empty() {
726            Ok(None)
727        } else {
728            Ok(Some(InodeAdditional {
729                xattrs,
730                symlink_target,
731            }))
732        }
733    }
734
735    fn get_xattrs(p: &Path) -> io::Result<Vec<Xattr>> {
736        xattr::list(p)?
737            .map(|xa| {
738                let value = xattr::get(p, &xa)?;
739                Ok(Xattr {
740                    key: xa.into_vec(),
741                    val: value.unwrap(),
742                })
743            })
744            .collect()
745    }
746}
747
748#[derive(Debug, PartialEq, Eq)]
749pub struct Xattr {
750    pub key: Vec<u8>,
751    pub val: Vec<u8>,
752}
753
754impl Xattr {
755    pub fn from_capnp(reader: crate::metadata_capnp::xattr::Reader<'_>) -> Result<Self> {
756        let key = reader.get_key()?.to_vec();
757        let val = reader.get_val()?.to_vec();
758        Ok(Xattr { key, val })
759    }
760
761    pub fn fill_capnp(&self, builder: &mut crate::metadata_capnp::xattr::Builder<'_>) {
762        builder.set_val(&self.val);
763        builder.set_key(&self.key);
764    }
765}
766
767pub struct InodeVector<'a> {
768    reader: crate::metadata_capnp::inode_vector::Reader<'a>,
769}
770
771impl<'a> InodeVector<'a> {
772    pub fn get_inode_vector(
773        &self,
774    ) -> ::capnp::Result<::capnp::struct_list::Reader<'_, crate::metadata_capnp::inode::Owned>>
775    {
776        self.reader.get_inodes()
777    }
778
779    pub fn find_inode(&self, ino: Ino) -> Result<Option<crate::metadata_capnp::inode::Reader<'_>>> {
780        let mut left = 0;
781        let inodes = self.get_inode_vector()?;
782        let mut right = inodes.len() - 1;
783
784        while left <= right {
785            let mid = left + (right - left) / 2;
786            let i = inodes.get(mid);
787
788            if i.get_ino() == ino {
789                return Ok(Some(i));
790            }
791
792            if i.get_ino() < ino {
793                left = mid + 1;
794            } else {
795                // don't underflow...
796                if mid == 0 {
797                    break;
798                }
799                right = mid - 1;
800            };
801        }
802
803        Ok(None)
804    }
805
806    pub fn max_ino(&self) -> Result<Option<Ino>> {
807        let inodes = self.get_inode_vector()?;
808        let last_index = inodes.len() - 1;
809        Ok(Some(inodes.get(last_index).get_ino()))
810    }
811
812    pub fn from_capnp(
813        reader: crate::metadata_capnp::inode_vector::Reader<'a>,
814    ) -> Result<Vec<Inode>> {
815        reader
816            .get_inodes()?
817            .iter()
818            .map(|inode| Inode::from_capnp(inode))
819            .collect()
820    }
821
822    fn fill_capnp(
823        inodes: &[Inode],
824        builder: &mut crate::metadata_capnp::inode_vector::Builder<'_>,
825    ) -> Result<()> {
826        let inodes_len = inodes.len().try_into()?;
827        let mut capnp_inodes = builder.reborrow().init_inodes(inodes_len);
828
829        for (i, inode) in inodes.iter().enumerate() {
830            // we already checked that the length of pfs_inodes fits inside a u32
831            let mut capnp_inode = capnp_inodes.reborrow().get(i as u32);
832            inode.fill_capnp(&mut capnp_inode)?;
833        }
834
835        Ok(())
836    }
837}
838
839#[derive(Debug, Clone, PartialEq, Eq)]
840pub struct Digest([u8; SHA256_BLOCK_SIZE]);
841
842impl Digest {
843    pub fn new(digest: &[u8; SHA256_BLOCK_SIZE]) -> Self {
844        Self(*digest)
845    }
846    pub fn underlying(&self) -> [u8; SHA256_BLOCK_SIZE] {
847        let mut dest = [0_u8; SHA256_BLOCK_SIZE];
848        dest.copy_from_slice(&self.0);
849        dest
850    }
851}
852
853impl fmt::Display for Digest {
854    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855        write!(f, "{}", hex::encode(self.0))
856    }
857}
858
859impl Serialize for Digest {
860    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
861    where
862        S: Serializer,
863    {
864        let val = format!("sha256:{}", hex::encode(self.0));
865        serializer.serialize_str(&val)
866    }
867}
868
869impl TryFrom<&str> for Digest {
870    type Error = FromHexError;
871    fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
872        let mut digest: [u8; SHA256_BLOCK_SIZE] = [0; SHA256_BLOCK_SIZE];
873        hex::decode_to_slice(s, &mut digest)?;
874        Ok(Digest(digest))
875    }
876}
877
878impl TryFrom<BlobRef> for Digest {
879    type Error = WireFormatError;
880    fn try_from(v: BlobRef) -> std::result::Result<Self, Self::Error> {
881        Ok(Digest(v.digest))
882    }
883}
884
885impl TryFrom<&BlobRef> for Digest {
886    type Error = WireFormatError;
887    fn try_from(v: &BlobRef) -> std::result::Result<Self, Self::Error> {
888        Ok(Digest(v.digest))
889    }
890}
891
892impl<'de> Deserialize<'de> for Digest {
893    fn deserialize<D>(deserializer: D) -> std::result::Result<Digest, D::Error>
894    where
895        D: Deserializer<'de>,
896    {
897        struct DigestVisitor;
898
899        impl<'de> Visitor<'de> for DigestVisitor {
900            type Value = Digest;
901
902            fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
903                formatter.write_fmt(format_args!("expected 'sha256:<hex encoded hash>'"))
904            }
905
906            fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
907            where
908                E: SerdeError,
909            {
910                let parts: Vec<&str> = s.split(':').collect();
911                if parts.len() != 2 {
912                    return Err(SerdeError::custom(format!("bad digest {s}")));
913                }
914
915                match parts[0] {
916                    "sha256" => {
917                        let buf =
918                            hex::decode(parts[1]).map_err(|e| SerdeError::custom(e.to_string()))?;
919
920                        let len = buf.len();
921                        let digest: [u8; SHA256_BLOCK_SIZE] = buf.try_into().map_err(|_| {
922                            SerdeError::custom(format!("invalid sha256 block length {len}"))
923                        })?;
924                        Ok(Digest(digest))
925                    }
926                    _ => Err(SerdeError::custom(format!(
927                        "unknown digest type {}",
928                        parts[0]
929                    ))),
930                }
931            }
932        }
933
934        deserializer.deserialize_str(DigestVisitor)
935    }
936}