nix_index/
files.rs

1//! Data types for working with trees of files.
2//!
3//! The main type here is `FileTree` which represents
4//! such as the file listing for a store path.
5use std::collections::HashMap;
6use std::io::{self, Write};
7use std::str::{self, FromStr};
8
9use clap::builder::PossibleValue;
10use clap::ValueEnum;
11use memchr::memchr;
12use serde::{Deserialize, Serialize};
13use serde_bytes::ByteBuf;
14
15use crate::frcode;
16
17/// This enum represents a single node in a file tree.
18///
19/// The type is generic over the contents of a directory node,
20/// because we want to use this enum to represent both a flat
21/// structure where a directory only stores some meta-information about itself
22/// (such as the number of children) and full file trees, where a
23/// directory contains all the child nodes.
24///
25/// Note that file nodes by themselves do not have names. Names are given
26/// to file nodes by the parent directory, which has a map of entry names to
27/// file nodes.
28#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
29pub enum FileNode<T> {
30    /// A regular file. This is the normal kind of file which is
31    /// neither a directory not a symlink.
32    Regular {
33        /// The size of this file, in bytes.
34        size: u64,
35        /// Whether or not this file has the `executable` bit set.
36        executable: bool,
37    },
38    /// A symbolic link that points to another file path.
39    Symlink {
40        /// The path that this symlink points to.
41        target: ByteBuf,
42    },
43    /// A directory. It usually has a mapping of names to child nodes (in
44    /// the case of a fill tree), but we also support a reduced form where
45    /// we only store the number of entries in the directory.
46    Directory {
47        /// The size of a directory is the number of children it contains.
48        size: u64,
49
50        /// The contents of this directory. These are generic, as explained
51        /// in the documentation for this type.
52        contents: T,
53    },
54}
55
56/// The type of a file.
57///
58/// This mirrors the variants of `FileNode`, but without storing
59/// data in each variant.
60///
61/// An exception to this is the `executable` field for the regular type.
62/// This is needed since we present `regular` and `executable` files as different
63/// to the user, so we need a way to represent both types.
64#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
65pub enum FileType {
66    Regular { executable: bool },
67    Directory,
68    Symlink,
69}
70
71impl ValueEnum for FileType {
72    fn value_variants<'a>() -> &'a [Self] {
73        &[
74            FileType::Regular { executable: false },
75            FileType::Regular { executable: true },
76            FileType::Directory,
77            FileType::Symlink,
78        ]
79    }
80
81    fn to_possible_value(&self) -> Option<PossibleValue> {
82        match self {
83            FileType::Regular { executable: false } => Some(PossibleValue::new("r")),
84            FileType::Regular { executable: true } => Some(PossibleValue::new("x")),
85            FileType::Directory => Some(PossibleValue::new("d")),
86            FileType::Symlink => Some(PossibleValue::new("s")),
87        }
88    }
89}
90
91impl FromStr for FileType {
92    type Err = &'static str;
93
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        match s {
96            "r" => Ok(FileType::Regular { executable: false }),
97            "x" => Ok(FileType::Regular { executable: true }),
98            "d" => Ok(FileType::Directory),
99            "s" => Ok(FileType::Symlink),
100            _ => Err("invalid file type"),
101        }
102    }
103}
104
105/// This lists all file types that can currently be represented.
106pub const ALL_FILE_TYPES: &[FileType] = &[
107    FileType::Regular { executable: true },
108    FileType::Regular { executable: false },
109    FileType::Directory,
110    FileType::Symlink,
111];
112
113impl<T> FileNode<T> {
114    /// Split this node into a node without contents and optionally the contents themselves,
115    /// if the node was a directory.
116    pub fn split_contents(&self) -> (FileNode<()>, Option<&T>) {
117        use self::FileNode::*;
118        match *self {
119            Regular { size, executable } => (Regular { size, executable }, None),
120            Symlink { ref target } => (
121                Symlink {
122                    target: target.clone(),
123                },
124                None,
125            ),
126            Directory { size, ref contents } => (Directory { size, contents: () }, Some(contents)),
127        }
128    }
129
130    /// Return the type of this file.
131    pub fn get_type(&self) -> FileType {
132        match *self {
133            FileNode::Regular { executable, .. } => FileType::Regular { executable },
134            FileNode::Directory { .. } => FileType::Directory,
135            FileNode::Symlink { .. } => FileType::Symlink,
136        }
137    }
138}
139
140impl FileNode<()> {
141    fn encode<W: Write>(&self, encoder: &mut frcode::Encoder<W>) -> io::Result<()> {
142        use self::FileNode::*;
143        match *self {
144            Regular { executable, size } => {
145                let e = if executable { "x" } else { "r" };
146                encoder.write_meta(format!("{}{}", size, e).as_bytes())?;
147            }
148            Symlink { ref target } => {
149                encoder.write_meta(target)?;
150                encoder.write_meta(b"s")?;
151            }
152            Directory { size, contents: () } => {
153                encoder.write_meta(format!("{}d", size).as_bytes())?;
154            }
155        }
156        Ok(())
157    }
158
159    pub fn decode(buf: &[u8]) -> Option<Self> {
160        use self::FileNode::*;
161        buf.split_last().and_then(|(kind, buf)| match *kind {
162            b'x' | b'r' => {
163                let executable = *kind == b'x';
164                str::from_utf8(buf)
165                    .ok()
166                    .and_then(|s| s.parse().ok())
167                    .map(|size| Regular { executable, size })
168            }
169            b's' => Some(Symlink {
170                target: ByteBuf::from(buf),
171            }),
172            b'd' => str::from_utf8(buf)
173                .ok()
174                .and_then(|s| s.parse().ok())
175                .map(|size| Directory { size, contents: () }),
176            _ => None,
177        })
178    }
179}
180
181/// This type represents a full tree of files.
182///
183/// A *file tree* is a *file node* where each directory contains
184/// the tree for its children.
185#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
186pub struct FileTree(FileNode<HashMap<ByteBuf, FileTree>>);
187
188/// An entry in a file tree is a path to a node paired with that node.
189///
190/// If the entry refers to a directory, it only stores information about that
191/// directory itself. It does not contain the children of the directory.
192pub struct FileTreeEntry {
193    pub path: Vec<u8>,
194    pub node: FileNode<()>,
195}
196
197impl FileTreeEntry {
198    pub fn encode<W: Write>(self, encoder: &mut frcode::Encoder<W>) -> io::Result<()> {
199        self.node.encode(encoder)?;
200        encoder.write_path(self.path)?;
201        Ok(())
202    }
203
204    pub fn decode(buf: &[u8]) -> Option<FileTreeEntry> {
205        memchr(b'\0', buf).and_then(|sep| {
206            let path = &buf[(sep + 1)..];
207            let node = &buf[0..sep];
208            FileNode::decode(node).map(|node| FileTreeEntry {
209                path: path.to_vec(),
210                node,
211            })
212        })
213    }
214}
215
216impl FileTree {
217    pub fn regular(size: u64, executable: bool) -> Self {
218        FileTree(FileNode::Regular { size, executable })
219    }
220
221    pub fn symlink(target: ByteBuf) -> Self {
222        FileTree(FileNode::Symlink { target })
223    }
224
225    pub fn directory(entries: HashMap<ByteBuf, FileTree>) -> Self {
226        FileTree(FileNode::Directory {
227            size: entries.len() as u64,
228            contents: entries,
229        })
230    }
231
232    pub fn to_list(&self, filter_prefix: &[u8]) -> Vec<FileTreeEntry> {
233        let mut result = Vec::new();
234
235        let mut stack = Vec::with_capacity(16);
236        stack.push((Vec::new(), self));
237
238        while let Some(entry) = stack.pop() {
239            let path = entry.0;
240            let FileTree(current) = entry.1;
241            let (node, contents) = current.split_contents();
242            if let Some(entries) = contents {
243                let mut entries = entries.iter().collect::<Vec<_>>();
244                entries.sort_by(|a, b| Ord::cmp(a.0, b.0));
245                for (name, entry) in entries {
246                    let mut path = path.clone();
247                    path.push(b'/');
248                    path.extend_from_slice(name);
249                    stack.push((path, entry));
250                }
251            }
252            if path.starts_with(filter_prefix) {
253                result.push(FileTreeEntry { path, node });
254            }
255        }
256        result
257    }
258}