]> git.proxmox.com Git - pxar.git/blame - tests/simple/fs.rs
ditch anyhow crate in examples/tests
[pxar.git] / tests / simple / fs.rs
CommitLineData
1829ef0d
WB
1use std::collections::HashMap;
2use std::io::Read;
3use std::path::{Path, PathBuf};
13993292 4
13993292
WB
5use pxar::decoder::sync as decoder;
6use pxar::decoder::SeqRead;
7use pxar::encoder::sync as encoder;
8use pxar::encoder::{LinkOffset, SeqWrite};
807a4fd6 9use pxar::format::acl::{self, Permissions};
1feb04d7 10use pxar::format::{mode, Device};
1829ef0d 11use pxar::EntryKind as PxarEntryKind;
13993292
WB
12use pxar::Metadata;
13
71194b54
WB
14use crate::Error;
15
bb74cbfa 16/// Hardlink information we use while encoding pxar archives.
1829ef0d
WB
17pub struct HardlinkInfo {
18 link: LinkOffset,
19 path: PathBuf,
20}
21
bb74cbfa 22/// Mapping from `Entry.link_key` to its produced `HardlinkInfo`.
1829ef0d
WB
23pub type HardlinkList = HashMap<String, HardlinkInfo>;
24
bb74cbfa 25/// Our virtual file system's file entry types.
ea8ff636 26#[derive(Debug, Eq, PartialEq)]
13993292
WB
27pub enum EntryKind {
28 Invalid,
29 File(Vec<u8>),
30 Directory(Vec<Entry>),
31 Symlink(PathBuf),
1829ef0d 32 Hardlink(String),
650070b0 33 Device(Device),
13993292
WB
34 Socket,
35 Fifo,
36}
37
bb74cbfa 38/// A virtual file entry.
1829ef0d 39#[derive(Debug, Eq)]
13993292 40pub struct Entry {
bb74cbfa 41 /// For now we just use strings as those are easier to write down for test cases.
13993292 42 pub name: String,
bb74cbfa
WB
43
44 /// The metadata is the same we have in pxar archives for ease of use.
13993292 45 pub metadata: Metadata,
bb74cbfa
WB
46
47 /// The file's kind & contents.
13993292 48 pub entry: EntryKind,
bb74cbfa
WB
49
50 /// If we want to make a hardlink to a file, we write a "key" in here by which we can refer
51 /// back to it in an `EntryKind::Hardlink`. Note that in order for the tests to work we
52 /// currently have to use the canonical file path as key. (This just made it much easier to
53 /// test things by being able to just derive `PartialEq` on `EntryKind`.)
1829ef0d
WB
54 pub link_key: Option<String>,
55}
56
57impl PartialEq for Entry {
58 fn eq(&self, other: &Self) -> bool {
59 // When decoding we don't get hardlink names back, so we skip those in our eq check.
60 self.name == other.name && self.metadata == other.metadata && self.entry == other.entry
61 }
13993292
WB
62}
63
64impl Entry {
bb74cbfa
WB
65 /// Start a new entry. Takes a file *name* (not a path!). We use builder methods for the
66 /// remaining data.
13993292
WB
67 pub fn new(name: &str) -> Self {
68 Self {
69 name: name.to_string(),
70 metadata: Metadata::default(),
71 entry: EntryKind::Invalid,
1829ef0d 72 link_key: None,
13993292
WB
73 }
74 }
75
bb74cbfa
WB
76 /// Add metadata. You can skip this for hardlinks, but note that for other files you'll want to
77 /// have metadat with the correct file mode bits at least.
78 ///
79 /// There could be convenience methods for files and symlinks though.
13993292
WB
80 pub fn metadata(mut self, metadata: impl Into<Metadata>) -> Self {
81 self.metadata = metadata.into();
82 self
83 }
84
bb74cbfa
WB
85 /// Fill in the entry kind. This is never optional, actually, but since metadata is optional
86 /// making this a builder method rather than a paramtere of `new()` makes the test cases read
87 /// nicer, since directory contents are last.
13993292
WB
88 pub fn entry(mut self, entry: EntryKind) -> Self {
89 self.entry = entry;
90 self
91 }
92
1829ef0d
WB
93 /// NOTE: For test cases to be equal via the `==` comparison link keys must be equal to the full
94 /// canonical path, as decoded hardlinks will use the `entry.path()` from the pxar decoder as
95 /// value for `EntryKind::Hardlink`.
96 pub fn link_key(mut self, key: impl Into<String>) -> Self {
97 self.link_key = Some(key.into());
98 self
99 }
100
bb74cbfa 101 /// Internal assertion that this file must not contain a hard link key.
1829ef0d
WB
102 fn no_hardlink(&self) -> Result<(), Error> {
103 if let Some(key) = &self.link_key {
104 bail!(
105 "hardlink key on entry which may not be hard-linked: {}",
106 key
107 );
108 }
109 Ok(())
110 }
111
bb74cbfa
WB
112 /// Encode this entry (recursively) into a pxar encoder.
113 ///
114 /// The path should "point" to the parent.
1829ef0d
WB
115 pub fn encode_into<T>(
116 &self,
117 encoder: &mut encoder::Encoder<T>,
118 hardlinks: &mut HardlinkList,
119 path: &Path,
120 ) -> Result<(), Error>
13993292
WB
121 where
122 T: SeqWrite,
123 {
124 match &self.entry {
125 EntryKind::Invalid => bail!("invalid entry encountered"),
126
127 EntryKind::File(data) => {
1829ef0d 128 let link: LinkOffset = encoder.add_file(
13993292
WB
129 &self.metadata,
130 &self.name,
131 data.len() as u64,
132 &mut &data[..],
133 )?;
1829ef0d
WB
134 if let Some(key) = &self.link_key {
135 let info = HardlinkInfo {
136 link,
137 path: path.join(&self.name),
138 };
139 if hardlinks.insert(key.clone(), info).is_some() {
140 bail!("duplicate hardlink key: {}", key);
141 }
142 }
13993292
WB
143 }
144
145 EntryKind::Directory(entries) => {
1829ef0d 146 self.no_hardlink()?;
13993292 147 let mut dir = encoder.create_directory(&self.name, &self.metadata)?;
1829ef0d 148 let path = path.join(&self.name);
13993292 149 for entry in entries {
1829ef0d 150 entry.encode_into(&mut dir, hardlinks, &path)?;
13993292
WB
151 }
152 dir.finish()?;
153 }
154
155 EntryKind::Symlink(path) => {
1829ef0d 156 self.no_hardlink()?;
13993292
WB
157 let _: () = encoder.add_symlink(&self.metadata, &self.name, path)?;
158 }
159
1829ef0d
WB
160 EntryKind::Hardlink(key) => {
161 self.no_hardlink()?;
162 if let Some(info) = hardlinks.get(key) {
163 let _: () = encoder.add_hardlink(&self.name, &info.path, info.link)?;
164 } else {
165 bail!("missing hardlink target key: {}", key);
166 }
167 }
168
650070b0
WB
169 EntryKind::Device(device) => {
170 self.no_hardlink()?;
171 let _: () = encoder.add_device(&self.metadata, &self.name, device.clone())?;
172 }
173
174 EntryKind::Fifo => {
175 self.no_hardlink()?;
176 let _: () = encoder.add_fifo(&self.metadata, &self.name)?;
177 }
178
179 EntryKind::Socket => {
180 self.no_hardlink()?;
181 let _: () = encoder.add_socket(&self.metadata, &self.name)?;
182 }
13993292
WB
183 }
184 Ok(())
185 }
186
bb74cbfa
WB
187 /// Decode an entry from a pxar decoder.
188 ///
189 /// Note that you CANNOT re-encode it afterwards currently, as for now we do not synthesize
190 /// `link_key` properties!
1829ef0d 191 pub fn decode_from<T: SeqRead>(decoder: &mut decoder::Decoder<T>) -> Result<Entry, Error> {
bb74cbfa
WB
192 // The decoder linearly goes through the entire archive recursiveley, we need to know when
193 // a directory ends:
1829ef0d 194 decoder.enable_goodbye_entries(true);
bb74cbfa 195
1829ef0d
WB
196 Self::decode_root(decoder)
197 }
198
199 fn decode_root<T: SeqRead>(decoder: &mut decoder::Decoder<T>) -> Result<Entry, Error> {
200 let item = decoder
201 .next()
202 .ok_or_else(|| format_err!("empty archive?"))??;
203
204 match item.kind() {
205 PxarEntryKind::Directory => (),
206 other => bail!("invalid entry kind for root node: {:?}", other),
207 }
208
209 let mut root = Entry::new("/").metadata(item.metadata().clone());
210 root.decode_directory(decoder)?;
211 Ok(root)
212 }
213
214 fn decode_directory<T: SeqRead>(
215 &mut self,
216 decoder: &mut decoder::Decoder<T>,
217 ) -> Result<(), Error> {
218 let mut contents = Vec::new();
219
220 while let Some(item) = decoder.next().transpose()? {
221 let make_entry =
222 || -> Result<Entry, Error> {
223 Ok(Entry::new(item.file_name().to_str().ok_or_else(|| {
224 format_err!("non-utf8 name in test: {:?}", item.file_name())
225 })?)
226 .metadata(item.metadata().clone())
227 .link_key(item.path().as_os_str().to_str().ok_or_else(|| {
228 format_err!("non-utf8 path in test: {:?}", item.file_name())
229 })?))
230 };
231 match item.kind() {
232 PxarEntryKind::GoodbyeTable => break,
233 PxarEntryKind::File { size, .. } => {
234 let mut data = Vec::new();
235 decoder
236 .contents()
237 .ok_or_else(|| {
238 format_err!("failed to get contents for file entry: {:?}", item.path())
239 })?
240 .read_to_end(&mut data)?;
1feb04d7
WB
241 if data.len() as u64 != *size {
242 bail!(
243 "file {:?} was advertised to be of size {} but we read {} bytes",
244 item.path(),
245 size,
246 data.len(),
247 );
248 }
1829ef0d
WB
249 contents.push(make_entry()?.entry(EntryKind::File(data)));
250 }
251 PxarEntryKind::Directory => {
252 let mut dir = make_entry()?;
253 dir.decode_directory(decoder)?;
254 contents.push(dir);
255 }
256 PxarEntryKind::Symlink(link) => {
257 contents.push(make_entry()?.entry(EntryKind::Symlink(link.into())));
258 }
259 PxarEntryKind::Hardlink(link) => {
260 contents.push(
261 make_entry()?.entry(EntryKind::Hardlink(
262 link.as_os_str()
263 .to_str()
264 .ok_or_else(|| {
265 format_err!("non-utf8 hardlink entry: {:?}", link.as_os_str())
266 })?
267 .to_string(),
268 )),
269 );
270 }
650070b0
WB
271 PxarEntryKind::Device(device) => {
272 contents.push(make_entry()?.entry(EntryKind::Device(device.clone())));
273 }
274 PxarEntryKind::Fifo => {
275 contents.push(make_entry()?.entry(EntryKind::Fifo));
276 }
277 PxarEntryKind::Socket => {
278 contents.push(make_entry()?.entry(EntryKind::Socket));
279 }
1829ef0d
WB
280 }
281 }
282
283 self.entry = EntryKind::Directory(contents);
284 Ok(())
13993292
WB
285 }
286}
287
8fe60382 288#[rustfmt::skip]
13993292
WB
289pub fn test_fs() -> Entry {
290 Entry::new("/")
291 .metadata(Metadata::dir_builder(0o755))
292 .entry(EntryKind::Directory(vec![
293 Entry::new("home")
294 .metadata(Metadata::dir_builder(0o755))
295 .entry(EntryKind::Directory(vec![
296 Entry::new("user")
297 .metadata(Metadata::dir_builder(0o700).owner(1000, 1000))
298 .entry(EntryKind::Directory(vec![
299 Entry::new(".profile")
300 .metadata(Metadata::file_builder(0o644).owner(1000, 1000))
301 .entry(EntryKind::File(b"#umask 022".to_vec())),
302 Entry::new("data")
303 .metadata(Metadata::file_builder(0o644).owner(1000, 1000))
304 .entry(EntryKind::File(b"a file from a user".to_vec())),
305 ])),
306 ])),
307 Entry::new("bin")
308 .metadata(Metadata::builder(mode::IFLNK | 0o777))
309 .entry(EntryKind::Symlink(PathBuf::from("usr/bin"))),
1829ef0d
WB
310 Entry::new("usr")
311 .metadata(Metadata::dir_builder(0o755))
312 .entry(EntryKind::Directory(vec![
313 Entry::new("bin")
314 .metadata(Metadata::dir_builder(0o755))
315 .entry(EntryKind::Directory(vec![
316 Entry::new("bzip2")
317 .metadata(Metadata::file_builder(0o755))
c81b2e4e 318 .entry(EntryKind::File(b"This is the bzip2 executable".to_vec()))
1829ef0d
WB
319 .link_key("/usr/bin/bzip2"),
320 Entry::new("cat")
321 .metadata(Metadata::file_builder(0o755))
322 .entry(EntryKind::File(b"This is another executable".to_vec())),
323 Entry::new("bunzip2")
324 .entry(EntryKind::Hardlink("/usr/bin/bzip2".to_string())),
325 ])),
326 ])),
650070b0
WB
327 Entry::new("dev")
328 .metadata(Metadata::dir_builder(0o755))
329 .entry(EntryKind::Directory(vec![
330 Entry::new("null")
331 .metadata(Metadata::builder(mode::IFCHR | 0o666))
332 .entry(EntryKind::Device(Device { major: 1, minor: 3 })),
333 Entry::new("zero")
334 .metadata(Metadata::builder(mode::IFCHR | 0o666))
335 .entry(EntryKind::Device(Device { major: 1, minor: 5 })),
336 Entry::new("loop0")
337 .metadata(Metadata::builder(mode::IFBLK | 0o666))
338 .entry(EntryKind::Device(Device { major: 7, minor: 0 })),
339 Entry::new("loop1")
807a4fd6
WB
340 .metadata(
341 Metadata::builder(mode::IFBLK | 0o666)
342 .acl_user(acl::User::new(1000, 6))
343 .acl_group(acl::Group::new(1000, 6))
344 )
650070b0
WB
345 .entry(EntryKind::Device(Device { major: 7, minor: 1 })),
346 ])),
347 Entry::new("run")
807a4fd6
WB
348 .metadata(
349 Metadata::dir_builder(0o755)
350 .default_acl(Some(acl::Default {
351 user_obj_permissions: Permissions(acl::READ | acl::WRITE),
352 group_obj_permissions: Permissions(acl::READ),
353 other_permissions: Permissions(acl::READ),
354 mask_permissions: Permissions::NO_MASK,
355 }))
356 .default_acl_user(acl::User::new(1001, acl::READ))
357 )
650070b0
WB
358 .entry(EntryKind::Directory(vec![
359 Entry::new("fifo0")
360 .metadata(Metadata::builder(mode::IFIFO | 0o666))
361 .entry(EntryKind::Fifo),
362 Entry::new("sock0")
363 .metadata(Metadata::builder(mode::IFSOCK | 0o600))
364 .entry(EntryKind::Socket),
365 ])),
13993292
WB
366 ]))
367}