1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#![warn(rust_2018_idioms)]
//! ipfs-unixfs: UnixFs tree support in Rust.
//!
//! The crate aims to provide a blockstore implementation independent of the UnixFs implementation by
//! working on slices and not doing any IO operations.
//!
//! The main entry point for extracting information and/or data out of UnixFs trees is
//! `ipfs_unixfs::walk::Walker`. To resolve `IpfsPath` segments over dag-pb nodes,
//! `ipfs_unixfs::resolve` should be used.

extern crate alloc;

use alloc::borrow::Cow;
use core::fmt;

/// File support.
pub mod file;

/// Symlink creation support
pub mod symlink;

/// Directory and directory tree support
pub mod dir;
pub use dir::{resolve, LookupError, MaybeResolved, ResolveError};

mod pb;
pub use pb::{FlatUnixFs, PBLink, PBNode, UnixFs, UnixFsType};

/// Support operations for the dag-pb, the outer shell of UnixFS
pub mod dagpb;

/// Support for walking over all UnixFs trees
pub mod walk;

#[cfg(test)]
pub(crate) mod test_support;

/// A link could not be transformed into a Cid.
#[derive(Debug)]
pub struct InvalidCidInLink {
    /// The index of this link, from zero
    pub nth: usize,
    /// Hash which could not be turned into a `Cid`
    pub hash: Cow<'static, [u8]>,
    /// Name of the link, most likely empty when this originates from a file, most likely non-empty
    /// for other kinds.
    pub name: Cow<'static, str>,
    /// Error from the attempted conversion
    pub source: libipld::cid::Error,
    /// This is to deny creating these outside of the crate
    #[allow(dead_code)]
    hidden: (),
}

impl<'a> From<(usize, pb::PBLink<'a>, libipld::cid::Error)> for InvalidCidInLink {
    fn from((nth, link, source): (usize, pb::PBLink<'a>, libipld::cid::Error)) -> Self {
        let hash = match link.Hash {
            Some(Cow::Borrowed(x)) if !x.is_empty() => Cow::Owned(x.to_vec()),
            Some(Cow::Borrowed(_)) | None => Cow::Borrowed(&[][..]),
            Some(Cow::Owned(x)) => Cow::Owned(x),
        };

        let name = match link.Name {
            Some(Cow::Borrowed(x)) if !x.is_empty() => Cow::Owned(x.to_string()),
            Some(Cow::Borrowed(_)) | None => Cow::Borrowed(""),
            Some(Cow::Owned(x)) => Cow::Owned(x),
        };

        InvalidCidInLink {
            nth,
            hash,
            name,
            source,
            hidden: (),
        }
    }
}

impl fmt::Display for InvalidCidInLink {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            fmt,
            "failed to convert link #{} ({:?}) to Cid: {}",
            self.nth, self.name, self.source
        )
    }
}

impl std::error::Error for InvalidCidInLink {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

/// Wrapper around the unexpected UnixFs node type, allowing access to querying what is known about
/// the type.
pub struct UnexpectedNodeType(i32);

impl fmt::Debug for UnexpectedNodeType {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        let converted = UnixFsType::from(self.0);
        // the conversion defaults to Raw
        if converted == UnixFsType::Raw && self.0 != 0 {
            write!(fmt, "{} or <unknown>", self.0)
        } else {
            write!(fmt, "{} or {:?}", self.0, converted)
        }
    }
}

impl From<UnixFsType> for UnexpectedNodeType {
    fn from(t: UnixFsType) -> UnexpectedNodeType {
        UnexpectedNodeType(t.into())
    }
}

impl UnexpectedNodeType {
    /// Returns `true` if the type represents some directory
    pub fn is_directory(&self) -> bool {
        matches!(
            UnixFsType::from(self.0),
            UnixFsType::Directory | UnixFsType::HAMTShard
        )
    }

    /// Returns `true` if the type represents a `File`
    pub fn is_file(&self) -> bool {
        matches!(UnixFsType::from(self.0), UnixFsType::File)
    }
}

/// A container for the UnixFs metadata, which can be present at the root of the file, directory, or symlink trees.
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct Metadata {
    mode: Option<u32>,
    mtime: Option<(i64, u32)>,
}

impl Metadata {
    /// Returns the full file mode, if one has been specified.
    ///
    /// The full file mode is originally read through `st_mode` field of `stat` struct defined in
    /// `sys/stat.h` and its defining OpenGroup standard. The lowest 3 bytes correspond to read,
    /// write, and execute rights per user, group, and other, while the 4th byte determines sticky bits,
    /// set user id or set group id. The following two bytes correspond to the different file types, as
    /// defined by the same OpenGroup standard:
    /// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html
    pub fn mode(&self) -> Option<u32> {
        self.mode
    }

    /// Returns the raw timestamp of last modification time, if specified.
    ///
    /// The timestamp is `(seconds, nanos)` - similar to `core::time::Duration`, with the exception of
    /// allowing seconds to be negative. The seconds are calculated from `1970-01-01 00:00:00` or
    /// the common "unix epoch".
    pub fn mtime(&self) -> Option<(i64, u32)> {
        self.mtime
    }

    /// Returns the mtime metadata as a `FileTime`. Enabled only in the `filetime` feature.
    #[cfg(feature = "filetime")]
    pub fn mtime_as_filetime(&self) -> Option<filetime::FileTime> {
        self.mtime()
            .map(|(seconds, nanos)| filetime::FileTime::from_unix_time(seconds, nanos))
    }
}

impl<'a> From<&'a UnixFs<'_>> for Metadata {
    fn from(data: &'a UnixFs<'_>) -> Self {
        let mode = data.mode;
        let mtime = data
            .mtime
            .clone()
            .map(|ut| (ut.Seconds, ut.FractionalNanoseconds.unwrap_or(0)));

        Metadata { mode, mtime }
    }
}