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