1use crate::prelude::*;
2
3use std::{
4 ffi::CString,
5 fs::File,
6 io::{Read, Write},
7 ptr::null_mut,
8};
9
10use libc::c_void;
11
12use crate::{
13 carchive::{
14 self, archive, archive_entry_free, archive_entry_new, archive_entry_set_atime,
15 archive_entry_set_ctime, archive_entry_set_gid, archive_entry_set_mode,
16 archive_entry_set_mtime, archive_entry_set_pathname, archive_entry_set_perm,
17 archive_entry_set_size, archive_entry_set_uid, archive_write_data, archive_write_free,
18 archive_write_header,
19 },
20 ARCHIVE_FILTER_BZIP2, ARCHIVE_FILTER_GZIP, ARCHIVE_FILTER_LRZIP, ARCHIVE_FILTER_LZ4,
21 ARCHIVE_FILTER_LZIP, ARCHIVE_FILTER_LZMA, ARCHIVE_FILTER_LZOP, ARCHIVE_FILTER_NONE,
22 ARCHIVE_FILTER_XZ, ARCHIVE_FILTER_ZSTD, ARCHIVE_FORMAT_7ZIP, ARCHIVE_FORMAT_TAR,
23 ARCHIVE_FORMAT_XAR, ARCHIVE_FORMAT_ZIP,
24};
25use std::os::raw::c_int;
26
27const BUFFER_SIZE: usize = 16384;
28
29pub struct ArchiveWriter<W: Write> {
34 archive_writer: *mut archive,
35 fileref: Box<FileWriter<W>>,
36 file_format: c_int,
37 file_filter: c_int,
38}
39
40struct FileWriter<W: Write> {
41 obj: W,
42}
43
44unsafe extern "C" fn archivewriter_write<W: Write>(
45 archive: *mut carchive::archive,
46 client_data: *mut c_void,
47 buffer: *const c_void,
48 size: usize,
49) -> carchive::la_ssize_t {
50 let writer = (client_data as *mut FileWriter<W>).as_mut().unwrap();
51 let writable = std::slice::from_raw_parts(buffer as *const u8, size);
52
53 match writer.obj.write(writable) {
54 Ok(size) => size as carchive::la_ssize_t,
55 Err(e) => {
56 let description = CString::new(e.to_string()).unwrap();
57 carchive::archive_set_error(
58 archive,
59 e.raw_os_error().unwrap_or(0),
60 description.as_ptr(),
61 );
62 -1
63 }
64 }
65}
66
67impl<W: Write> ArchiveWriter<W> {
68 pub fn new(dest: W) -> Result<ArchiveWriter<W>>
70 where
71 W: Write,
72 {
73 let fref = Box::new(FileWriter { obj: dest });
74 unsafe {
75 let archive_writer = carchive::archive_write_new();
76
77 if archive_writer.is_null() {
78 return Err(Error::NullArchive);
79 }
80
81 match carchive::archive_write_set_bytes_in_last_block(archive_writer, 1) {
82 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
83 _ => return Err(Error::from(archive_writer)),
84 };
85
86 Ok(ArchiveWriter {
87 archive_writer,
88 fileref: fref,
89 file_format: -1,
90 file_filter: -1,
91 })
92 }
93 }
94
95 pub fn set_output_format(&mut self, format: c_int) -> Result<()> {
97 match unsafe { carchive::archive_write_set_format(self.archive_writer, format) } {
98 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
99 _ => return Err(Error::from(self.archive_writer)),
100 }
101
102 self.file_format = format;
103 Ok(())
104 }
105
106 pub fn set_output_filter(&mut self, filter: c_int) -> Result<()> {
108 match unsafe { carchive::archive_write_add_filter(self.archive_writer, filter) } {
109 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
110 _ => return Err(Error::from(self.archive_writer)),
111 }
112 self.file_filter = filter;
113 Ok(())
114 }
115
116 pub fn add_filter_option(&mut self, name: &str, value: &str) -> Result<()> {
117 let n = CString::new(name.to_string()).unwrap();
118 let v = CString::new(value.to_string()).unwrap();
119 match unsafe {
120 carchive::archive_write_set_filter_option(
121 self.archive_writer,
122 null_mut(),
123 n.as_ptr(),
124 v.as_ptr(),
125 )
126 } {
127 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
128 _ => return Err(Error::from(self.archive_writer)),
129 }
130 Ok(())
131 }
132
133 pub fn add_format_option(&mut self, name: &str, value: &str) -> Result<()> {
134 let n = CString::new(name.to_string()).unwrap();
135 let v = CString::new(value.to_string()).unwrap();
136 match unsafe {
137 carchive::archive_write_set_format_option(
138 self.archive_writer,
139 null_mut(),
140 n.as_ptr(),
141 v.as_ptr(),
142 )
143 } {
144 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
145 _ => return Err(Error::from(self.archive_writer)),
146 }
147 Ok(())
148 }
149
150 pub fn open(&mut self) -> Result<()> {
153 if self.file_format < 0 || self.file_filter < 0 {
154 return Err(Error::IncompleteInitialization);
155 }
156 match unsafe {
157 carchive::archive_write_open(
158 self.archive_writer,
159 std::ptr::addr_of_mut!(*self.fileref) as *mut c_void,
160 None,
161 Some(archivewriter_write::<W>),
162 None,
163 )
164 } {
165 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
166 _ => return Err(Error::from(self.archive_writer)),
167 }
168 Ok(())
169 }
170
171 fn free(&mut self) -> Result<()> {
173 match unsafe { archive_write_free(self.archive_writer) } {
174 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => Ok(()),
175 _ => Err(Error::from(self.archive_writer)),
176 }
177 }
178
179 pub fn set_compression_high(&mut self) -> Result<()> {
181 let mut max_compression_level = match self.file_filter {
182 ARCHIVE_FILTER_BZIP2 => 9,
183 ARCHIVE_FILTER_GZIP => 9,
184 ARCHIVE_FILTER_LRZIP => 9,
185 ARCHIVE_FILTER_XZ => 9,
186 ARCHIVE_FILTER_LZ4 => 9,
187 ARCHIVE_FILTER_LZIP => 9,
188 ARCHIVE_FILTER_ZSTD => 22,
189 ARCHIVE_FILTER_LZMA => 9,
190 ARCHIVE_FILTER_LZOP => 9,
191 _ => -1,
192 };
193
194 if max_compression_level > 0 {
195 self.add_filter_option("compression-level", &max_compression_level.to_string())?;
196 }
197
198 max_compression_level = match self.file_format {
199 ARCHIVE_FORMAT_7ZIP => 9,
200 ARCHIVE_FORMAT_XAR => 9,
201 ARCHIVE_FORMAT_ZIP => 9,
202 _ => -1,
203 };
204
205 if max_compression_level > 0 {
206 self.add_format_option("compression-level", &max_compression_level.to_string())?;
207 }
208
209 Ok(())
210 }
211
212 pub fn set_compression_mid(&mut self) -> Result<()> {
213 let mut max_compression_level = match self.file_filter {
214 ARCHIVE_FILTER_BZIP2 => 9,
215 ARCHIVE_FILTER_GZIP => 9,
216 ARCHIVE_FILTER_LRZIP => 9,
217 ARCHIVE_FILTER_XZ => 9,
218 ARCHIVE_FILTER_LZ4 => 9,
219 ARCHIVE_FILTER_LZIP => 9,
220 ARCHIVE_FILTER_ZSTD => 22,
221 ARCHIVE_FILTER_LZMA => 9,
222 ARCHIVE_FILTER_LZOP => 9,
223 _ => -1,
224 };
225
226 if max_compression_level > 0 {
227 self.add_filter_option(
228 "compression-level",
229 &(max_compression_level / 2).to_string(),
230 )?;
231 }
232
233 max_compression_level = match self.file_format {
234 ARCHIVE_FORMAT_7ZIP => 9,
235 ARCHIVE_FORMAT_XAR => 9,
236 ARCHIVE_FORMAT_ZIP => 9,
237 _ => -1,
238 };
239
240 if max_compression_level > 0 {
241 self.add_format_option(
242 "compression-level",
243 &(max_compression_level / 2).to_string(),
244 )?;
245 }
246
247 Ok(())
248 }
249
250 pub fn set_compression_low(&mut self) -> Result<()> {
251 self.add_format_option("compression-level", "0")?;
252 self.add_filter_option("compression-level", "0")
253 }
254
255 pub fn set_output_targz(&mut self) -> Result<()> {
258 self.set_output_format(ARCHIVE_FORMAT_TAR)?;
259 self.set_output_filter(ARCHIVE_FILTER_GZIP)
260 }
261
262 pub fn set_output_tarxz(&mut self) -> Result<()> {
263 self.set_output_format(ARCHIVE_FORMAT_TAR)?;
264 self.set_output_filter(ARCHIVE_FILTER_XZ)
265 }
266
267 pub fn set_output_tarzst(&mut self) -> Result<()> {
268 self.set_output_format(ARCHIVE_FORMAT_TAR)?;
269 self.set_output_filter(ARCHIVE_FILTER_ZSTD)
270 }
271
272 pub fn set_output_7zlzma2(&mut self) -> Result<()> {
273 self.set_output_format(ARCHIVE_FORMAT_7ZIP)?;
274 self.set_output_filter(ARCHIVE_FILTER_NONE)?;
275 self.add_format_option("compression", "lzma2")
276 }
277
278 pub fn set_output_zip(&mut self) -> Result<()> {
279 self.set_output_format(ARCHIVE_FORMAT_ZIP)?;
280 self.set_output_filter(ARCHIVE_FILTER_NONE)?;
281 self.add_format_option("compression", "deflate")
282 }
283
284 pub fn add_obj_from_reader<S: Read>(
285 &mut self,
286 mut source: S,
287 archivepath: &str,
288 objmeta: &Metadata,
289 ) -> Result<()> {
290 let mut buffer: [u8; BUFFER_SIZE] = [0u8; BUFFER_SIZE];
291 let p = CString::new(archivepath.to_string()).unwrap();
292
293 unsafe {
294 let entry = archive_entry_new();
295
296 archive_entry_set_size(entry, objmeta.size()); archive_entry_set_mode(entry, objmeta.nodetype() | objmeta.perm());
300 archive_entry_set_perm(entry, objmeta.perm());
301 archive_entry_set_ctime(entry, objmeta.ctime(), objmeta.ctime_nano());
302 archive_entry_set_mtime(entry, objmeta.mtime(), objmeta.mtime_nano());
303 archive_entry_set_atime(entry, objmeta.atime(), objmeta.atime_nano());
304 archive_entry_set_pathname(entry, p.as_ptr());
305 archive_entry_set_uid(entry, objmeta.owner());
306 archive_entry_set_gid(entry, objmeta.group());
307
308 match archive_write_header(self.archive_writer, entry) {
309 carchive::ARCHIVE_OK | carchive::ARCHIVE_WARN => (),
310 _ => return Err(Error::from(self.archive_writer)),
311 }
312
313 loop {
314 let readed = source.read(&mut buffer)?;
315 if readed == 0 {
316 break;
317 }
318
319 if archive_write_data(
320 self.archive_writer,
321 buffer.as_ptr() as *const c_void,
322 readed,
323 ) != readed as isize
324 {
325 return Err(Error::from(self.archive_writer));
326 }
327 }
328
329 archive_entry_free(entry);
331 }
332 Ok(())
333 }
334
335 pub fn add_file(&mut self, localpath: &str, archivepath: &str) -> Result<()> {
337 let source = File::open(localpath)?;
338 let meta = source.metadata()?;
339 self.add_obj_from_reader(source, archivepath, &meta.into())
340 }
341
342 pub fn add_data(
344 &mut self,
345 archivepath: &str,
346 data: &[u8],
347 mtime: i64,
348 mtime_nano: i64,
349 is_dir: bool,
350 ) -> Result<()> {
351 let nodetype = if is_dir { crate::AE_IFDIR } else { crate::AE_IFREG };
352 let meta = crate::Metadata::from_fields(
353 data.len() as i64,
354 nodetype,
355 0o644,
356 mtime,
357 mtime_nano,
358 );
359 let cursor = std::io::Cursor::new(data);
360 self.add_obj_from_reader(cursor, archivepath, &meta)
361 }
362}
363
364impl<W: Write> Drop for ArchiveWriter<W> {
365 fn drop(&mut self) {
366 drop(self.free());
367 }
368}