]>
Commit | Line | Data |
---|---|---|
145b50e6 | 1 | use std::collections::HashMap; |
70acf637 | 2 | use std::fs::File; |
c76d3f98 | 3 | use std::io::{self, Read, Write}; |
145b50e6 | 4 | use std::os::linux::fs::MetadataExt; |
1250e3ea WB |
5 | use std::os::unix::ffi::OsStrExt; |
6 | use std::path::{Path, PathBuf}; | |
dc4a2854 | 7 | |
62aaaa86 | 8 | use anyhow::{bail, format_err, Error}; |
dc4a2854 WB |
9 | |
10 | use pxar::accessor::Accessor; | |
1250e3ea | 11 | use pxar::encoder::{Encoder, LinkOffset, SeqWrite}; |
62aaaa86 | 12 | use pxar::Metadata; |
dc4a2854 WB |
13 | |
14 | fn 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)] |
70 | struct HardLinkInfo { | |
71 | st_dev: u64, | |
72 | st_ino: u64, | |
73 | } | |
74 | ||
70acf637 WB |
75 | fn 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 |
101 | fn 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 | } |