]> git.proxmox.com Git - pxar.git/blob - examples/pxarcmd.rs
drop custom poll_fn, require rust 1.64
[pxar.git] / examples / pxarcmd.rs
1 use std::collections::HashMap;
2 use std::fs::File;
3 use std::io::{self, Read, Write};
4 use std::os::linux::fs::MetadataExt;
5 use std::os::unix::ffi::OsStrExt;
6 use std::path::{Path, PathBuf};
7
8 use pxar::accessor::Accessor;
9 use pxar::encoder::{Encoder, LinkOffset, SeqWrite};
10 use pxar::Metadata;
11
12 macro_rules! format_err {
13 ($($msg:tt)+) => {
14 std::io::Error::new(std::io::ErrorKind::Other, format!($($msg)+))
15 };
16 }
17
18 macro_rules! bail {
19 ($($msg:tt)+) => {{
20 return Err(format_err!($($msg)+).into());
21 }};
22 }
23
24 type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
25
26 fn main() -> Result<(), Error> {
27 let mut args = std::env::args_os();
28 let _ = args.next();
29
30 let cmd = args
31 .next()
32 .ok_or_else(|| format_err!("expected a command (ls or cat)"))?;
33 let cmd = cmd
34 .to_str()
35 .ok_or_else(|| format_err!("expected a valid command string (utf-8)"))?;
36 match cmd {
37 "create" => return cmd_create(args),
38 "ls" | "cat" => (),
39 _ => bail!("valid commands are: cat, ls"),
40 }
41
42 let file = args
43 .next()
44 .ok_or_else(|| format_err!("expected a file name"))?;
45 let file = std::fs::File::open(file)?;
46 let accessor = Accessor::from_file_ref(&file)?;
47 let dir = accessor.open_root()?;
48
49 let mut buf = Vec::new();
50
51 for file in args {
52 let entry = dir
53 .lookup(&file)?
54 .ok_or_else(|| format_err!("no such file in archive: {:?}", file))?;
55
56 if cmd == "ls" {
57 if file.as_bytes().ends_with(b"/") {
58 for file in entry.enter_directory()?.read_dir() {
59 println!("{:?}", file?.file_name());
60 }
61 } else {
62 println!("{:?}", entry.metadata());
63 }
64 } else if cmd == "cat" {
65 buf.clear();
66 let entry = if entry.is_hardlink() {
67 accessor.follow_hardlink(&entry)?
68 } else {
69 entry
70 };
71 entry.contents()?.read_to_end(&mut buf)?;
72 io::stdout().write_all(&buf)?;
73 } else {
74 bail!("unknown command: {}", cmd);
75 }
76 }
77
78 Ok(())
79 }
80
81 #[derive(Eq, PartialEq, Hash)]
82 struct HardLinkInfo {
83 st_dev: u64,
84 st_ino: u64,
85 }
86
87 fn cmd_create(mut args: std::env::ArgsOs) -> Result<(), Error> {
88 let file = args
89 .next()
90 .ok_or_else(|| format_err!("expected a file name"))?;
91
92 let dir_path = args
93 .next()
94 .ok_or_else(|| format_err!("expected a directory"))?;
95
96 if args.next().is_some() {
97 bail!("too many parameters, there can only be a single root directory in a pxar archive");
98 }
99
100 let dir_path = Path::new(&dir_path);
101
102 // we use "simple" directory traversal without `openat()`
103 let meta = Metadata::from(std::fs::metadata(&dir_path)?);
104 let dir = std::fs::read_dir(&dir_path)?;
105
106 let mut encoder = Encoder::create(file, &meta)?;
107 add_directory(&mut encoder, dir, &dir_path, &mut HashMap::new())?;
108 encoder.finish()?;
109
110 Ok(())
111 }
112
113 fn add_directory<'a, T: SeqWrite + 'a>(
114 encoder: &mut Encoder<T>,
115 dir: std::fs::ReadDir,
116 root_path: &Path,
117 hardlinks: &mut HashMap<HardLinkInfo, (PathBuf, LinkOffset)>,
118 ) -> Result<(), Error> {
119 let mut file_list = Vec::new();
120 for file in dir {
121 let file = file?;
122 let file_name = file.file_name();
123 if file_name == "." || file_name == ".." {
124 continue;
125 }
126 file_list.push((
127 file_name.to_os_string(),
128 file.path().to_path_buf(),
129 file.file_type()?,
130 file.metadata()?,
131 ));
132 }
133
134 file_list.sort_unstable_by(|a, b| (a.0).cmp(&b.0));
135
136 for (file_name, file_path, file_type, file_meta) in file_list {
137 println!("{:?}", file_path);
138
139 let meta = Metadata::from(&file_meta);
140 if file_type.is_dir() {
141 let mut dir = encoder.create_directory(file_name, &meta)?;
142 add_directory(
143 &mut dir,
144 std::fs::read_dir(file_path)?,
145 root_path,
146 &mut *hardlinks,
147 )?;
148 dir.finish()?;
149 } else if file_type.is_symlink() {
150 todo!("symlink handling");
151 } else if file_type.is_file() {
152 let link_info = HardLinkInfo {
153 st_dev: file_meta.st_dev(),
154 st_ino: file_meta.st_ino(),
155 };
156
157 if file_meta.st_nlink() > 1 {
158 if let Some((path, offset)) = hardlinks.get(&link_info) {
159 eprintln!("Adding hardlink {:?} => {:?}", file_name, path);
160 encoder.add_hardlink(file_name, path, *offset)?;
161 continue;
162 }
163 }
164
165 let offset: LinkOffset = encoder.add_file(
166 &meta,
167 file_name,
168 file_meta.len(),
169 &mut File::open(&file_path)?,
170 )?;
171
172 if file_meta.st_nlink() > 1 {
173 let file_path = file_path.strip_prefix(root_path)?.to_path_buf();
174 hardlinks.insert(link_info, (file_path, offset));
175 }
176 } else {
177 todo!("special file handling");
178 }
179 }
180 Ok(())
181 }