1use anyhow::Result;
24use chrono::{DateTime, Utc};
25use log::{debug, error, trace, warn};
26use nuts_archive::{Archive, Group};
27use std::fs::{self, File, Metadata};
28use std::io::Read;
29use std::path::Path;
30
31#[cfg(unix)]
32use std::os::unix::fs::{MetadataExt, PermissionsExt};
33
34use crate::backend::PluginBackend;
35use crate::{say, say_err};
36
37#[cfg(unix)]
38mod unix {
39 pub const S_IRUSR: u32 = 0o0000400; pub const S_IWUSR: u32 = 0o0000200; pub const S_IXUSR: u32 = 0o0000100; pub const S_IRGRP: u32 = 0o0000040; pub const S_IWGRP: u32 = 0o0000020; pub const S_IXGRP: u32 = 0o0000010; pub const S_IROTH: u32 = 0o0000004; pub const S_IWOTH: u32 = 0o0000002; pub const S_IXOTH: u32 = 0o0000001; }
49
50macro_rules! into_utc {
51 ($metadata:ident . $type:ident ()) => {
52 if let Ok(time) = $metadata.$type() {
53 time.into()
54 } else {
55 warn!(
56 "the {} time is not available on your platform",
57 stringify!($type)
58 );
59 Utc::now()
60 }
61 };
62}
63
64fn changed(metadata: &Metadata) -> DateTime<Utc> {
65 if cfg!(unix) {
66 DateTime::from_timestamp(metadata.ctime(), 0).unwrap_or_else(|| {
67 warn!(
68 "could not convert epoch ctime {} into naive datetime",
69 metadata.ctime()
70 );
71
72 Utc::now()
73 })
74 } else {
75 panic!("platform currently not supported");
76 }
77}
78
79fn can_read(metadata: &Metadata, group: Group) -> bool {
80 if cfg!(unix) {
81 let mask = match group {
82 Group::User => unix::S_IRUSR,
83 Group::Group => unix::S_IRGRP,
84 Group::Other => unix::S_IROTH,
85 };
86
87 metadata.permissions().mode() & mask > 0
88 } else {
89 match group {
90 Group::User => true,
91 Group::Group => false,
92 Group::Other => false,
93 }
94 }
95}
96
97fn can_write(metadata: &Metadata, group: Group) -> bool {
98 if cfg!(unix) {
99 let mask = match group {
100 Group::User => unix::S_IWUSR,
101 Group::Group => unix::S_IWGRP,
102 Group::Other => unix::S_IWOTH,
103 };
104
105 metadata.permissions().mode() & mask > 0
106 } else {
107 match group {
108 Group::User => !metadata.permissions().readonly(),
109 Group::Group => false,
110 Group::Other => false,
111 }
112 }
113}
114
115fn can_execute(metadata: &Metadata, group: Group) -> bool {
116 if cfg!(unix) {
117 let mask = match group {
118 Group::User => unix::S_IXUSR,
119 Group::Group => unix::S_IXGRP,
120 Group::Other => unix::S_IXOTH,
121 };
122
123 metadata.permissions().mode() & mask > 0
124 } else {
125 false
126 }
127}
128
129pub fn append_recursive(archive: &mut Archive<PluginBackend>, path: &Path) -> Result<()> {
130 debug!("append {}", path.display());
131
132 let metadata = match fs::symlink_metadata(path) {
133 Ok(md) => md,
134 Err(err) => {
135 error!("{}", err);
136 say_err!("! {}", path.display());
137 return Ok(());
138 }
139 };
140
141 if metadata.is_file() {
142 let block_size = archive.as_ref().block_size() as usize;
143
144 let mut fh = File::open(path)?;
145 let mut buf = vec![0; block_size];
146
147 let mut builder = archive.append_file(path.to_string_lossy());
148
149 builder.set_created(into_utc!(metadata.created()));
150 builder.set_changed(changed(&metadata));
151 builder.set_modified(into_utc!(metadata.modified()));
152
153 for group in [Group::User, Group::Group, Group::Other] {
154 builder.set_readable(group, can_read(&metadata, group));
155 builder.set_writable(group, can_write(&metadata, group));
156 builder.set_executable(group, can_execute(&metadata, group));
157 }
158
159 let mut entry = builder.build()?;
160
161 loop {
162 let n = fh.read(&mut buf)?;
163 trace!("{} bytes read from {}", n, path.display());
164
165 if n > 0 {
166 entry.write_all(&buf[..n])?;
167 } else {
168 break;
169 }
170 }
171 } else if metadata.is_dir() {
172 let mut builder = archive.append_directory(path.to_string_lossy());
173
174 builder.set_created(into_utc!(metadata.created()));
175 builder.set_changed(changed(&metadata));
176 builder.set_modified(into_utc!(metadata.modified()));
177
178 for group in [Group::User, Group::Group, Group::Other] {
179 builder.set_readable(group, can_read(&metadata, group));
180 builder.set_writable(group, can_write(&metadata, group));
181 builder.set_executable(group, can_execute(&metadata, group));
182 }
183
184 builder.build()?;
185 } else if metadata.is_symlink() {
186 let target = path.read_link()?;
187
188 let mut builder = archive.append_symlink(path.to_string_lossy(), target.to_string_lossy());
189
190 builder.set_created(into_utc!(metadata.created()));
191 builder.set_changed(changed(&metadata));
192 builder.set_modified(into_utc!(metadata.modified()));
193
194 for group in [Group::User, Group::Group, Group::Other] {
195 builder.set_readable(group, can_read(&metadata, group));
196 builder.set_writable(group, can_write(&metadata, group));
197 builder.set_executable(group, can_execute(&metadata, group));
198 }
199
200 builder.build()?;
201 }
202
203 say!("a {}", path.display());
204
205 if path.is_dir() {
206 for entry in path.read_dir()? {
207 let child = entry?.path();
208
209 append_recursive(archive, &child)?;
210 }
211 }
212
213 Ok(())
214}