]>
Commit | Line | Data |
---|---|---|
3dfed10e | 1 | #[cfg(unix)] |
a1dfa0c6 XL |
2 | use std::os::unix::prelude::*; |
3 | #[cfg(windows)] | |
4 | use std::os::windows::prelude::*; | |
7cac9316 XL |
5 | |
6 | use std::borrow::Cow; | |
7 | use std::fmt; | |
8 | use std::fs; | |
9 | use std::io; | |
ff7c6d11 | 10 | use std::iter; |
a1dfa0c6 | 11 | use std::iter::repeat; |
7cac9316 | 12 | use std::mem; |
a1dfa0c6 | 13 | use std::path::{Component, Path, PathBuf}; |
7cac9316 XL |
14 | use std::str; |
15 | ||
74b04a01 XL |
16 | use crate::other; |
17 | use crate::EntryType; | |
7cac9316 XL |
18 | |
19 | /// Representation of the header of an entry in an archive | |
20 | #[repr(C)] | |
21 | #[allow(missing_docs)] | |
22 | pub struct Header { | |
23 | bytes: [u8; 512], | |
24 | } | |
25 | ||
26 | /// Declares the information that should be included when filling a Header | |
27 | /// from filesystem metadata. | |
28 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] | |
94222f64 | 29 | #[non_exhaustive] |
7cac9316 XL |
30 | pub enum HeaderMode { |
31 | /// All supported metadata, including mod/access times and ownership will | |
32 | /// be included. | |
33 | Complete, | |
34 | ||
35 | /// Only metadata that is directly relevant to the identity of a file will | |
36 | /// be included. In particular, ownership and mod/access times are excluded. | |
37 | Deterministic, | |
7cac9316 XL |
38 | } |
39 | ||
40 | /// Representation of the header of an entry in an archive | |
41 | #[repr(C)] | |
42 | #[allow(missing_docs)] | |
43 | pub struct OldHeader { | |
44 | pub name: [u8; 100], | |
45 | pub mode: [u8; 8], | |
46 | pub uid: [u8; 8], | |
47 | pub gid: [u8; 8], | |
48 | pub size: [u8; 12], | |
49 | pub mtime: [u8; 12], | |
50 | pub cksum: [u8; 8], | |
51 | pub linkflag: [u8; 1], | |
52 | pub linkname: [u8; 100], | |
53 | pub pad: [u8; 255], | |
54 | } | |
55 | ||
56 | /// Representation of the header of an entry in an archive | |
57 | #[repr(C)] | |
58 | #[allow(missing_docs)] | |
59 | pub struct UstarHeader { | |
60 | pub name: [u8; 100], | |
61 | pub mode: [u8; 8], | |
62 | pub uid: [u8; 8], | |
63 | pub gid: [u8; 8], | |
64 | pub size: [u8; 12], | |
65 | pub mtime: [u8; 12], | |
66 | pub cksum: [u8; 8], | |
67 | pub typeflag: [u8; 1], | |
68 | pub linkname: [u8; 100], | |
69 | ||
70 | // UStar format | |
71 | pub magic: [u8; 6], | |
72 | pub version: [u8; 2], | |
73 | pub uname: [u8; 32], | |
74 | pub gname: [u8; 32], | |
75 | pub dev_major: [u8; 8], | |
76 | pub dev_minor: [u8; 8], | |
77 | pub prefix: [u8; 155], | |
78 | pub pad: [u8; 12], | |
79 | } | |
80 | ||
81 | /// Representation of the header of an entry in an archive | |
82 | #[repr(C)] | |
83 | #[allow(missing_docs)] | |
84 | pub struct GnuHeader { | |
85 | pub name: [u8; 100], | |
86 | pub mode: [u8; 8], | |
87 | pub uid: [u8; 8], | |
88 | pub gid: [u8; 8], | |
89 | pub size: [u8; 12], | |
90 | pub mtime: [u8; 12], | |
91 | pub cksum: [u8; 8], | |
92 | pub typeflag: [u8; 1], | |
93 | pub linkname: [u8; 100], | |
94 | ||
95 | // GNU format | |
96 | pub magic: [u8; 6], | |
97 | pub version: [u8; 2], | |
98 | pub uname: [u8; 32], | |
99 | pub gname: [u8; 32], | |
100 | pub dev_major: [u8; 8], | |
101 | pub dev_minor: [u8; 8], | |
102 | pub atime: [u8; 12], | |
103 | pub ctime: [u8; 12], | |
104 | pub offset: [u8; 12], | |
105 | pub longnames: [u8; 4], | |
106 | pub unused: [u8; 1], | |
107 | pub sparse: [GnuSparseHeader; 4], | |
108 | pub isextended: [u8; 1], | |
109 | pub realsize: [u8; 12], | |
110 | pub pad: [u8; 17], | |
111 | } | |
112 | ||
113 | /// Description of the header of a spare entry. | |
114 | /// | |
115 | /// Specifies the offset/number of bytes of a chunk of data in octal. | |
116 | #[repr(C)] | |
117 | #[allow(missing_docs)] | |
118 | pub struct GnuSparseHeader { | |
119 | pub offset: [u8; 12], | |
120 | pub numbytes: [u8; 12], | |
121 | } | |
122 | ||
123 | /// Representation of the entry found to represent extended GNU sparse files. | |
124 | /// | |
125 | /// When a `GnuHeader` has the `isextended` flag set to `1` then the contents of | |
126 | /// the next entry will be one of these headers. | |
127 | #[repr(C)] | |
128 | #[allow(missing_docs)] | |
129 | pub struct GnuExtSparseHeader { | |
130 | pub sparse: [GnuSparseHeader; 21], | |
131 | pub isextended: [u8; 1], | |
132 | pub padding: [u8; 7], | |
133 | } | |
134 | ||
135 | impl Header { | |
136 | /// Creates a new blank GNU header. | |
137 | /// | |
138 | /// The GNU style header is the default for this library and allows various | |
139 | /// extensions such as long path names, long link names, and setting the | |
140 | /// atime/ctime metadata attributes of files. | |
141 | pub fn new_gnu() -> Header { | |
142 | let mut header = Header { bytes: [0; 512] }; | |
ff7c6d11 XL |
143 | unsafe { |
144 | let gnu = cast_mut::<_, GnuHeader>(&mut header); | |
7cac9316 XL |
145 | gnu.magic = *b"ustar "; |
146 | gnu.version = *b" \0"; | |
147 | } | |
ff7c6d11 | 148 | header.set_mtime(0); |
7cac9316 XL |
149 | header |
150 | } | |
151 | ||
152 | /// Creates a new blank UStar header. | |
153 | /// | |
154 | /// The UStar style header is an extension of the original archive header | |
155 | /// which enables some extra metadata along with storing a longer (but not | |
156 | /// too long) path name. | |
157 | /// | |
158 | /// UStar is also the basis used for pax archives. | |
159 | pub fn new_ustar() -> Header { | |
160 | let mut header = Header { bytes: [0; 512] }; | |
ff7c6d11 XL |
161 | unsafe { |
162 | let gnu = cast_mut::<_, UstarHeader>(&mut header); | |
7cac9316 XL |
163 | gnu.magic = *b"ustar\0"; |
164 | gnu.version = *b"00"; | |
165 | } | |
ff7c6d11 | 166 | header.set_mtime(0); |
7cac9316 XL |
167 | header |
168 | } | |
169 | ||
170 | /// Creates a new blank old header. | |
171 | /// | |
172 | /// This header format is the original archive header format which all other | |
173 | /// versions are compatible with (e.g. they are a superset). This header | |
174 | /// format limits the path name limit and isn't able to contain extra | |
175 | /// metadata like atime/ctime. | |
176 | pub fn new_old() -> Header { | |
ff7c6d11 XL |
177 | let mut header = Header { bytes: [0; 512] }; |
178 | header.set_mtime(0); | |
179 | header | |
7cac9316 XL |
180 | } |
181 | ||
182 | fn is_ustar(&self) -> bool { | |
ff7c6d11 | 183 | let ustar = unsafe { cast::<_, UstarHeader>(self) }; |
7cac9316 XL |
184 | ustar.magic[..] == b"ustar\0"[..] && ustar.version[..] == b"00"[..] |
185 | } | |
186 | ||
187 | fn is_gnu(&self) -> bool { | |
ff7c6d11 | 188 | let ustar = unsafe { cast::<_, UstarHeader>(self) }; |
7cac9316 XL |
189 | ustar.magic[..] == b"ustar "[..] && ustar.version[..] == b" \0"[..] |
190 | } | |
191 | ||
192 | /// View this archive header as a raw "old" archive header. | |
193 | /// | |
194 | /// This view will always succeed as all archive header formats will fill | |
195 | /// out at least the fields specified in the old header format. | |
196 | pub fn as_old(&self) -> &OldHeader { | |
ff7c6d11 | 197 | unsafe { cast(self) } |
7cac9316 XL |
198 | } |
199 | ||
200 | /// Same as `as_old`, but the mutable version. | |
201 | pub fn as_old_mut(&mut self) -> &mut OldHeader { | |
ff7c6d11 | 202 | unsafe { cast_mut(self) } |
7cac9316 XL |
203 | } |
204 | ||
205 | /// View this archive header as a raw UStar archive header. | |
206 | /// | |
207 | /// The UStar format is an extension to the tar archive format which enables | |
208 | /// longer pathnames and a few extra attributes such as the group and user | |
209 | /// name. | |
210 | /// | |
211 | /// This cast may not succeed as this function will test whether the | |
212 | /// magic/version fields of the UStar format have the appropriate values, | |
213 | /// returning `None` if they aren't correct. | |
214 | pub fn as_ustar(&self) -> Option<&UstarHeader> { | |
a1dfa0c6 XL |
215 | if self.is_ustar() { |
216 | Some(unsafe { cast(self) }) | |
217 | } else { | |
218 | None | |
219 | } | |
7cac9316 XL |
220 | } |
221 | ||
222 | /// Same as `as_ustar_mut`, but the mutable version. | |
223 | pub fn as_ustar_mut(&mut self) -> Option<&mut UstarHeader> { | |
a1dfa0c6 XL |
224 | if self.is_ustar() { |
225 | Some(unsafe { cast_mut(self) }) | |
226 | } else { | |
227 | None | |
228 | } | |
7cac9316 XL |
229 | } |
230 | ||
231 | /// View this archive header as a raw GNU archive header. | |
232 | /// | |
233 | /// The GNU format is an extension to the tar archive format which enables | |
234 | /// longer pathnames and a few extra attributes such as the group and user | |
235 | /// name. | |
236 | /// | |
237 | /// This cast may not succeed as this function will test whether the | |
238 | /// magic/version fields of the GNU format have the appropriate values, | |
239 | /// returning `None` if they aren't correct. | |
240 | pub fn as_gnu(&self) -> Option<&GnuHeader> { | |
a1dfa0c6 XL |
241 | if self.is_gnu() { |
242 | Some(unsafe { cast(self) }) | |
243 | } else { | |
244 | None | |
245 | } | |
7cac9316 XL |
246 | } |
247 | ||
248 | /// Same as `as_gnu`, but the mutable version. | |
249 | pub fn as_gnu_mut(&mut self) -> Option<&mut GnuHeader> { | |
a1dfa0c6 XL |
250 | if self.is_gnu() { |
251 | Some(unsafe { cast_mut(self) }) | |
252 | } else { | |
253 | None | |
254 | } | |
7cac9316 XL |
255 | } |
256 | ||
83c7162d XL |
257 | /// Treats the given byte slice as a header. |
258 | /// | |
259 | /// Panics if the length of the passed slice is not equal to 512. | |
260 | pub fn from_byte_slice(bytes: &[u8]) -> &Header { | |
261 | assert_eq!(bytes.len(), mem::size_of::<Header>()); | |
262 | assert_eq!(mem::align_of_val(bytes), mem::align_of::<Header>()); | |
a1dfa0c6 | 263 | unsafe { &*(bytes.as_ptr() as *const Header) } |
83c7162d XL |
264 | } |
265 | ||
7cac9316 XL |
266 | /// Returns a view into this header as a byte array. |
267 | pub fn as_bytes(&self) -> &[u8; 512] { | |
268 | &self.bytes | |
269 | } | |
270 | ||
271 | /// Returns a view into this header as a byte array. | |
272 | pub fn as_mut_bytes(&mut self) -> &mut [u8; 512] { | |
273 | &mut self.bytes | |
274 | } | |
275 | ||
276 | /// Blanket sets the metadata in this header from the metadata argument | |
277 | /// provided. | |
278 | /// | |
279 | /// This is useful for initializing a `Header` from the OS's metadata from a | |
280 | /// file. By default, this will use `HeaderMode::Complete` to include all | |
281 | /// metadata. | |
282 | pub fn set_metadata(&mut self, meta: &fs::Metadata) { | |
283 | self.fill_from(meta, HeaderMode::Complete); | |
284 | } | |
285 | ||
286 | /// Sets only the metadata relevant to the given HeaderMode in this header | |
287 | /// from the metadata argument provided. | |
288 | pub fn set_metadata_in_mode(&mut self, meta: &fs::Metadata, mode: HeaderMode) { | |
289 | self.fill_from(meta, mode); | |
290 | } | |
291 | ||
292 | /// Returns the size of entry's data this header represents. | |
293 | /// | |
294 | /// This is different from `Header::size` for sparse files, which have | |
295 | /// some longer `size()` but shorter `entry_size()`. The `entry_size()` | |
296 | /// listed here should be the number of bytes in the archive this header | |
297 | /// describes. | |
298 | /// | |
299 | /// May return an error if the field is corrupted. | |
300 | pub fn entry_size(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
301 | num_field_wrapper_from(&self.as_old().size).map_err(|err| { |
302 | io::Error::new( | |
303 | err.kind(), | |
304 | format!("{} when getting size for {}", err, self.path_lossy()), | |
305 | ) | |
306 | }) | |
7cac9316 XL |
307 | } |
308 | ||
309 | /// Returns the file size this header represents. | |
310 | /// | |
311 | /// May return an error if the field is corrupted. | |
312 | pub fn size(&self) -> io::Result<u64> { | |
313 | if self.entry_type().is_gnu_sparse() { | |
a1dfa0c6 XL |
314 | self.as_gnu() |
315 | .ok_or_else(|| other("sparse header was not a gnu header")) | |
316 | .and_then(|h| h.real_size()) | |
7cac9316 XL |
317 | } else { |
318 | self.entry_size() | |
319 | } | |
320 | } | |
321 | ||
322 | /// Encodes the `size` argument into the size field of this header. | |
323 | pub fn set_size(&mut self, size: u64) { | |
a1dfa0c6 | 324 | num_field_wrapper_into(&mut self.as_old_mut().size, size); |
7cac9316 XL |
325 | } |
326 | ||
327 | /// Returns the raw path name stored in this header. | |
328 | /// | |
3dfed10e | 329 | /// This method may fail if the pathname is not valid Unicode and this is |
7cac9316 XL |
330 | /// called on a Windows platform. |
331 | /// | |
332 | /// Note that this function will convert any `\` characters to directory | |
333 | /// separators. | |
334 | pub fn path(&self) -> io::Result<Cow<Path>> { | |
335 | bytes2path(self.path_bytes()) | |
336 | } | |
337 | ||
338 | /// Returns the pathname stored in this header as a byte array. | |
339 | /// | |
340 | /// This function is guaranteed to succeed, but you may wish to call the | |
341 | /// `path` method to convert to a `Path`. | |
342 | /// | |
343 | /// Note that this function will convert any `\` characters to directory | |
344 | /// separators. | |
345 | pub fn path_bytes(&self) -> Cow<[u8]> { | |
346 | if let Some(ustar) = self.as_ustar() { | |
347 | ustar.path_bytes() | |
348 | } else { | |
349 | let name = truncate(&self.as_old().name); | |
350 | Cow::Borrowed(name) | |
351 | } | |
352 | } | |
353 | ||
83c7162d XL |
354 | /// Gets the path in a "lossy" way, used for error reporting ONLY. |
355 | fn path_lossy(&self) -> String { | |
356 | String::from_utf8_lossy(&self.path_bytes()).to_string() | |
357 | } | |
358 | ||
7cac9316 XL |
359 | /// Sets the path name for this header. |
360 | /// | |
361 | /// This function will set the pathname listed in this header, encoding it | |
362 | /// in the appropriate format. May fail if the path is too long or if the | |
3dfed10e XL |
363 | /// path specified is not Unicode and this is a Windows platform. Will |
364 | /// strip out any "." path component, which signifies the current directory. | |
17df50a5 XL |
365 | /// |
366 | /// Note: This function does not support names over 100 bytes, or paths | |
367 | /// over 255 bytes, even for formats that support longer names. Instead, | |
368 | /// use `Builder` methods to insert a long-name extension at the same time | |
369 | /// as the file content. | |
7cac9316 XL |
370 | pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { |
371 | self._set_path(p.as_ref()) | |
372 | } | |
373 | ||
374 | fn _set_path(&mut self, path: &Path) -> io::Result<()> { | |
375 | if let Some(ustar) = self.as_ustar_mut() { | |
a1dfa0c6 | 376 | return ustar.set_path(path); |
7cac9316 | 377 | } |
a1dfa0c6 XL |
378 | copy_path_into(&mut self.as_old_mut().name, path, false).map_err(|err| { |
379 | io::Error::new( | |
83c7162d | 380 | err.kind(), |
a1dfa0c6 XL |
381 | format!("{} when setting path for {}", err, self.path_lossy()), |
382 | ) | |
383 | }) | |
7cac9316 XL |
384 | } |
385 | ||
386 | /// Returns the link name stored in this header, if any is found. | |
387 | /// | |
3dfed10e | 388 | /// This method may fail if the pathname is not valid Unicode and this is |
7cac9316 XL |
389 | /// called on a Windows platform. `Ok(None)` being returned, however, |
390 | /// indicates that the link name was not present. | |
391 | /// | |
392 | /// Note that this function will convert any `\` characters to directory | |
393 | /// separators. | |
394 | pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> { | |
395 | match self.link_name_bytes() { | |
396 | Some(bytes) => bytes2path(bytes).map(Some), | |
397 | None => Ok(None), | |
398 | } | |
399 | } | |
400 | ||
401 | /// Returns the link name stored in this header as a byte array, if any. | |
402 | /// | |
403 | /// This function is guaranteed to succeed, but you may wish to call the | |
404 | /// `link_name` method to convert to a `Path`. | |
405 | /// | |
406 | /// Note that this function will convert any `\` characters to directory | |
407 | /// separators. | |
408 | pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> { | |
409 | let old = self.as_old(); | |
410 | if old.linkname[0] != 0 { | |
411 | Some(Cow::Borrowed(truncate(&old.linkname))) | |
412 | } else { | |
413 | None | |
414 | } | |
415 | } | |
416 | ||
74b04a01 | 417 | /// Sets the link name for this header. |
7cac9316 | 418 | /// |
74b04a01 XL |
419 | /// This function will set the linkname listed in this header, encoding it |
420 | /// in the appropriate format. May fail if the link name is too long or if | |
3dfed10e XL |
421 | /// the path specified is not Unicode and this is a Windows platform. Will |
422 | /// strip out any "." path component, which signifies the current directory. | |
7cac9316 XL |
423 | pub fn set_link_name<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { |
424 | self._set_link_name(p.as_ref()) | |
425 | } | |
426 | ||
427 | fn _set_link_name(&mut self, path: &Path) -> io::Result<()> { | |
a1dfa0c6 XL |
428 | copy_path_into(&mut self.as_old_mut().linkname, path, true).map_err(|err| { |
429 | io::Error::new( | |
430 | err.kind(), | |
431 | format!("{} when setting link name for {}", err, self.path_lossy()), | |
432 | ) | |
433 | }) | |
7cac9316 XL |
434 | } |
435 | ||
436 | /// Returns the mode bits for this file | |
437 | /// | |
438 | /// May return an error if the field is corrupted. | |
439 | pub fn mode(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
440 | octal_from(&self.as_old().mode) |
441 | .map(|u| u as u32) | |
442 | .map_err(|err| { | |
443 | io::Error::new( | |
444 | err.kind(), | |
445 | format!("{} when getting mode for {}", err, self.path_lossy()), | |
446 | ) | |
447 | }) | |
7cac9316 XL |
448 | } |
449 | ||
450 | /// Encodes the `mode` provided into this header. | |
451 | pub fn set_mode(&mut self, mode: u32) { | |
452 | octal_into(&mut self.as_old_mut().mode, mode); | |
453 | } | |
454 | ||
455 | /// Returns the value of the owner's user ID field | |
456 | /// | |
457 | /// May return an error if the field is corrupted. | |
a1dfa0c6 XL |
458 | pub fn uid(&self) -> io::Result<u64> { |
459 | num_field_wrapper_from(&self.as_old().uid) | |
460 | .map(|u| u as u64) | |
461 | .map_err(|err| { | |
462 | io::Error::new( | |
463 | err.kind(), | |
464 | format!("{} when getting uid for {}", err, self.path_lossy()), | |
465 | ) | |
466 | }) | |
7cac9316 XL |
467 | } |
468 | ||
469 | /// Encodes the `uid` provided into this header. | |
a1dfa0c6 XL |
470 | pub fn set_uid(&mut self, uid: u64) { |
471 | num_field_wrapper_into(&mut self.as_old_mut().uid, uid); | |
7cac9316 XL |
472 | } |
473 | ||
474 | /// Returns the value of the group's user ID field | |
a1dfa0c6 XL |
475 | pub fn gid(&self) -> io::Result<u64> { |
476 | num_field_wrapper_from(&self.as_old().gid) | |
477 | .map(|u| u as u64) | |
478 | .map_err(|err| { | |
479 | io::Error::new( | |
480 | err.kind(), | |
481 | format!("{} when getting gid for {}", err, self.path_lossy()), | |
482 | ) | |
483 | }) | |
7cac9316 XL |
484 | } |
485 | ||
486 | /// Encodes the `gid` provided into this header. | |
a1dfa0c6 XL |
487 | pub fn set_gid(&mut self, gid: u64) { |
488 | num_field_wrapper_into(&mut self.as_old_mut().gid, gid); | |
7cac9316 XL |
489 | } |
490 | ||
491 | /// Returns the last modification time in Unix time format | |
492 | pub fn mtime(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
493 | num_field_wrapper_from(&self.as_old().mtime).map_err(|err| { |
494 | io::Error::new( | |
495 | err.kind(), | |
496 | format!("{} when getting mtime for {}", err, self.path_lossy()), | |
497 | ) | |
498 | }) | |
7cac9316 XL |
499 | } |
500 | ||
501 | /// Encodes the `mtime` provided into this header. | |
502 | /// | |
503 | /// Note that this time is typically a number of seconds passed since | |
504 | /// January 1, 1970. | |
505 | pub fn set_mtime(&mut self, mtime: u64) { | |
a1dfa0c6 | 506 | num_field_wrapper_into(&mut self.as_old_mut().mtime, mtime); |
7cac9316 XL |
507 | } |
508 | ||
509 | /// Return the user name of the owner of this file. | |
510 | /// | |
511 | /// A return value of `Ok(Some(..))` indicates that the user name was | |
512 | /// present and was valid utf-8, `Ok(None)` indicates that the user name is | |
513 | /// not present in this archive format, and `Err` indicates that the user | |
514 | /// name was present but was not valid utf-8. | |
515 | pub fn username(&self) -> Result<Option<&str>, str::Utf8Error> { | |
516 | match self.username_bytes() { | |
517 | Some(bytes) => str::from_utf8(bytes).map(Some), | |
518 | None => Ok(None), | |
519 | } | |
520 | } | |
521 | ||
522 | /// Returns the user name of the owner of this file, if present. | |
523 | /// | |
524 | /// A return value of `None` indicates that the user name is not present in | |
525 | /// this header format. | |
526 | pub fn username_bytes(&self) -> Option<&[u8]> { | |
527 | if let Some(ustar) = self.as_ustar() { | |
528 | Some(ustar.username_bytes()) | |
529 | } else if let Some(gnu) = self.as_gnu() { | |
530 | Some(gnu.username_bytes()) | |
531 | } else { | |
532 | None | |
533 | } | |
534 | } | |
535 | ||
536 | /// Sets the username inside this header. | |
537 | /// | |
538 | /// This function will return an error if this header format cannot encode a | |
539 | /// user name or the name is too long. | |
540 | pub fn set_username(&mut self, name: &str) -> io::Result<()> { | |
541 | if let Some(ustar) = self.as_ustar_mut() { | |
a1dfa0c6 | 542 | return ustar.set_username(name); |
7cac9316 XL |
543 | } |
544 | if let Some(gnu) = self.as_gnu_mut() { | |
545 | gnu.set_username(name) | |
546 | } else { | |
547 | Err(other("not a ustar or gnu archive, cannot set username")) | |
548 | } | |
549 | } | |
550 | ||
551 | /// Return the group name of the owner of this file. | |
552 | /// | |
553 | /// A return value of `Ok(Some(..))` indicates that the group name was | |
554 | /// present and was valid utf-8, `Ok(None)` indicates that the group name is | |
555 | /// not present in this archive format, and `Err` indicates that the group | |
556 | /// name was present but was not valid utf-8. | |
557 | pub fn groupname(&self) -> Result<Option<&str>, str::Utf8Error> { | |
558 | match self.groupname_bytes() { | |
559 | Some(bytes) => str::from_utf8(bytes).map(Some), | |
560 | None => Ok(None), | |
561 | } | |
562 | } | |
563 | ||
564 | /// Returns the group name of the owner of this file, if present. | |
565 | /// | |
566 | /// A return value of `None` indicates that the group name is not present in | |
567 | /// this header format. | |
568 | pub fn groupname_bytes(&self) -> Option<&[u8]> { | |
569 | if let Some(ustar) = self.as_ustar() { | |
570 | Some(ustar.groupname_bytes()) | |
571 | } else if let Some(gnu) = self.as_gnu() { | |
572 | Some(gnu.groupname_bytes()) | |
573 | } else { | |
574 | None | |
575 | } | |
576 | } | |
577 | ||
578 | /// Sets the group name inside this header. | |
579 | /// | |
580 | /// This function will return an error if this header format cannot encode a | |
581 | /// group name or the name is too long. | |
582 | pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { | |
583 | if let Some(ustar) = self.as_ustar_mut() { | |
a1dfa0c6 | 584 | return ustar.set_groupname(name); |
7cac9316 XL |
585 | } |
586 | if let Some(gnu) = self.as_gnu_mut() { | |
587 | gnu.set_groupname(name) | |
588 | } else { | |
589 | Err(other("not a ustar or gnu archive, cannot set groupname")) | |
590 | } | |
591 | } | |
592 | ||
593 | /// Returns the device major number, if present. | |
594 | /// | |
595 | /// This field may not be present in all archives, and it may not be | |
596 | /// correctly formed in all archives. `Ok(Some(..))` means it was present | |
597 | /// and correctly decoded, `Ok(None)` indicates that this header format does | |
598 | /// not include the device major number, and `Err` indicates that it was | |
599 | /// present and failed to decode. | |
600 | pub fn device_major(&self) -> io::Result<Option<u32>> { | |
601 | if let Some(ustar) = self.as_ustar() { | |
602 | ustar.device_major().map(Some) | |
603 | } else if let Some(gnu) = self.as_gnu() { | |
604 | gnu.device_major().map(Some) | |
605 | } else { | |
606 | Ok(None) | |
607 | } | |
608 | } | |
609 | ||
610 | /// Encodes the value `major` into the dev_major field of this header. | |
611 | /// | |
612 | /// This function will return an error if this header format cannot encode a | |
613 | /// major device number. | |
614 | pub fn set_device_major(&mut self, major: u32) -> io::Result<()> { | |
615 | if let Some(ustar) = self.as_ustar_mut() { | |
94222f64 XL |
616 | ustar.set_device_major(major); |
617 | Ok(()) | |
618 | } else if let Some(gnu) = self.as_gnu_mut() { | |
619 | gnu.set_device_major(major); | |
620 | Ok(()) | |
7cac9316 XL |
621 | } else { |
622 | Err(other("not a ustar or gnu archive, cannot set dev_major")) | |
623 | } | |
624 | } | |
625 | ||
626 | /// Returns the device minor number, if present. | |
627 | /// | |
628 | /// This field may not be present in all archives, and it may not be | |
629 | /// correctly formed in all archives. `Ok(Some(..))` means it was present | |
630 | /// and correctly decoded, `Ok(None)` indicates that this header format does | |
631 | /// not include the device minor number, and `Err` indicates that it was | |
632 | /// present and failed to decode. | |
633 | pub fn device_minor(&self) -> io::Result<Option<u32>> { | |
634 | if let Some(ustar) = self.as_ustar() { | |
635 | ustar.device_minor().map(Some) | |
636 | } else if let Some(gnu) = self.as_gnu() { | |
637 | gnu.device_minor().map(Some) | |
638 | } else { | |
639 | Ok(None) | |
640 | } | |
641 | } | |
642 | ||
643 | /// Encodes the value `minor` into the dev_minor field of this header. | |
644 | /// | |
645 | /// This function will return an error if this header format cannot encode a | |
646 | /// minor device number. | |
647 | pub fn set_device_minor(&mut self, minor: u32) -> io::Result<()> { | |
648 | if let Some(ustar) = self.as_ustar_mut() { | |
94222f64 XL |
649 | ustar.set_device_minor(minor); |
650 | Ok(()) | |
651 | } else if let Some(gnu) = self.as_gnu_mut() { | |
652 | gnu.set_device_minor(minor); | |
653 | Ok(()) | |
7cac9316 XL |
654 | } else { |
655 | Err(other("not a ustar or gnu archive, cannot set dev_minor")) | |
656 | } | |
657 | } | |
658 | ||
659 | /// Returns the type of file described by this header. | |
660 | pub fn entry_type(&self) -> EntryType { | |
661 | EntryType::new(self.as_old().linkflag[0]) | |
662 | } | |
663 | ||
664 | /// Sets the type of file that will be described by this header. | |
665 | pub fn set_entry_type(&mut self, ty: EntryType) { | |
666 | self.as_old_mut().linkflag = [ty.as_byte()]; | |
667 | } | |
668 | ||
669 | /// Returns the checksum field of this header. | |
670 | /// | |
671 | /// May return an error if the field is corrupted. | |
672 | pub fn cksum(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
673 | octal_from(&self.as_old().cksum) |
674 | .map(|u| u as u32) | |
675 | .map_err(|err| { | |
676 | io::Error::new( | |
677 | err.kind(), | |
678 | format!("{} when getting cksum for {}", err, self.path_lossy()), | |
679 | ) | |
680 | }) | |
7cac9316 XL |
681 | } |
682 | ||
683 | /// Sets the checksum field of this header based on the current fields in | |
684 | /// this header. | |
685 | pub fn set_cksum(&mut self) { | |
ff7c6d11 | 686 | let cksum = self.calculate_cksum(); |
7cac9316 XL |
687 | octal_into(&mut self.as_old_mut().cksum, cksum); |
688 | } | |
689 | ||
ff7c6d11 XL |
690 | fn calculate_cksum(&self) -> u32 { |
691 | let old = self.as_old(); | |
692 | let start = old as *const _ as usize; | |
693 | let cksum_start = old.cksum.as_ptr() as *const _ as usize; | |
694 | let offset = cksum_start - start; | |
695 | let len = old.cksum.len(); | |
a1dfa0c6 XL |
696 | self.bytes[0..offset] |
697 | .iter() | |
ff7c6d11 XL |
698 | .chain(iter::repeat(&b' ').take(len)) |
699 | .chain(&self.bytes[offset + len..]) | |
700 | .fold(0, |a, b| a + (*b as u32)) | |
701 | } | |
702 | ||
7cac9316 XL |
703 | fn fill_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
704 | self.fill_platform_from(meta, mode); | |
705 | // Set size of directories to zero | |
a1dfa0c6 XL |
706 | self.set_size(if meta.is_dir() || meta.file_type().is_symlink() { |
707 | 0 | |
708 | } else { | |
709 | meta.len() | |
710 | }); | |
7cac9316 XL |
711 | if let Some(ustar) = self.as_ustar_mut() { |
712 | ustar.set_device_major(0); | |
713 | ustar.set_device_minor(0); | |
714 | } | |
715 | if let Some(gnu) = self.as_gnu_mut() { | |
716 | gnu.set_device_major(0); | |
717 | gnu.set_device_minor(0); | |
718 | } | |
719 | } | |
720 | ||
74b04a01 XL |
721 | #[cfg(target_arch = "wasm32")] |
722 | #[allow(unused_variables)] | |
723 | fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { | |
724 | unimplemented!(); | |
725 | } | |
726 | ||
3dfed10e | 727 | #[cfg(unix)] |
7cac9316 | 728 | fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
7cac9316 XL |
729 | match mode { |
730 | HeaderMode::Complete => { | |
731 | self.set_mtime(meta.mtime() as u64); | |
a1dfa0c6 XL |
732 | self.set_uid(meta.uid() as u64); |
733 | self.set_gid(meta.gid() as u64); | |
7cac9316 | 734 | self.set_mode(meta.mode() as u32); |
a1dfa0c6 | 735 | } |
7cac9316 | 736 | HeaderMode::Deterministic => { |
17df50a5 XL |
737 | // We could in theory set the mtime to zero here, but not all |
738 | // tools seem to behave well when ingesting files with a 0 | |
739 | // timestamp. For example rust-lang/cargo#9512 shows that lldb | |
740 | // doesn't ingest files with a zero timestamp correctly. | |
741 | // | |
742 | // We just need things to be deterministic here so just pick | |
743 | // something that isn't zero. This time, chosen after careful | |
744 | // deliberation, corresponds to Nov 29, 1973. | |
745 | self.set_mtime(123456789); | |
746 | ||
7cac9316 XL |
747 | self.set_uid(0); |
748 | self.set_gid(0); | |
749 | ||
750 | // Use a default umask value, but propagate the (user) execute bit. | |
a1dfa0c6 | 751 | let fs_mode = if meta.is_dir() || (0o100 & meta.mode() == 0o100) { |
7cac9316 | 752 | 0o755 |
a1dfa0c6 | 753 | } else { |
7cac9316 | 754 | 0o644 |
a1dfa0c6 | 755 | }; |
7cac9316 | 756 | self.set_mode(fs_mode); |
a1dfa0c6 | 757 | } |
7cac9316 XL |
758 | } |
759 | ||
760 | // Note that if we are a GNU header we *could* set atime/ctime, except | |
761 | // the `tar` utility doesn't do that by default and it causes problems | |
762 | // with 7-zip [1]. | |
763 | // | |
764 | // It's always possible to fill them out manually, so we just don't fill | |
765 | // it out automatically here. | |
766 | // | |
767 | // [1]: https://github.com/alexcrichton/tar-rs/issues/70 | |
768 | ||
769 | // TODO: need to bind more file types | |
ff7c6d11 XL |
770 | self.set_entry_type(entry_type(meta.mode())); |
771 | ||
ff7c6d11 | 772 | fn entry_type(mode: u32) -> EntryType { |
ff7c6d11 XL |
773 | match mode as libc::mode_t & libc::S_IFMT { |
774 | libc::S_IFREG => EntryType::file(), | |
775 | libc::S_IFLNK => EntryType::symlink(), | |
776 | libc::S_IFCHR => EntryType::character_special(), | |
777 | libc::S_IFBLK => EntryType::block_special(), | |
778 | libc::S_IFDIR => EntryType::dir(), | |
779 | libc::S_IFIFO => EntryType::fifo(), | |
780 | _ => EntryType::new(b' '), | |
781 | } | |
782 | } | |
7cac9316 XL |
783 | } |
784 | ||
785 | #[cfg(windows)] | |
786 | fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { | |
3dfed10e | 787 | // There's no concept of a file mode on Windows, so do a best approximation here. |
7cac9316 XL |
788 | match mode { |
789 | HeaderMode::Complete => { | |
790 | self.set_uid(0); | |
791 | self.set_gid(0); | |
792 | // The dates listed in tarballs are always seconds relative to | |
793 | // January 1, 1970. On Windows, however, the timestamps are returned as | |
794 | // dates relative to January 1, 1601 (in 100ns intervals), so we need to | |
795 | // add in some offset for those dates. | |
796 | let mtime = (meta.last_write_time() / (1_000_000_000 / 100)) - 11644473600; | |
797 | self.set_mtime(mtime); | |
798 | let fs_mode = { | |
799 | const FILE_ATTRIBUTE_READONLY: u32 = 0x00000001; | |
800 | let readonly = meta.file_attributes() & FILE_ATTRIBUTE_READONLY; | |
801 | match (meta.is_dir(), readonly != 0) { | |
802 | (true, false) => 0o755, | |
803 | (true, true) => 0o555, | |
804 | (false, false) => 0o644, | |
805 | (false, true) => 0o444, | |
806 | } | |
807 | }; | |
808 | self.set_mode(fs_mode); | |
a1dfa0c6 | 809 | } |
7cac9316 XL |
810 | HeaderMode::Deterministic => { |
811 | self.set_uid(0); | |
812 | self.set_gid(0); | |
17df50a5 | 813 | self.set_mtime(123456789); // see above in unix |
a1dfa0c6 | 814 | let fs_mode = if meta.is_dir() { 0o755 } else { 0o644 }; |
7cac9316 | 815 | self.set_mode(fs_mode); |
a1dfa0c6 | 816 | } |
7cac9316 XL |
817 | } |
818 | ||
819 | let ft = meta.file_type(); | |
820 | self.set_entry_type(if ft.is_dir() { | |
821 | EntryType::dir() | |
822 | } else if ft.is_file() { | |
823 | EntryType::file() | |
824 | } else if ft.is_symlink() { | |
825 | EntryType::symlink() | |
826 | } else { | |
827 | EntryType::new(b' ') | |
828 | }); | |
829 | } | |
ff7c6d11 XL |
830 | |
831 | fn debug_fields(&self, b: &mut fmt::DebugStruct) { | |
832 | if let Ok(entry_size) = self.entry_size() { | |
833 | b.field("entry_size", &entry_size); | |
834 | } | |
835 | if let Ok(size) = self.size() { | |
836 | b.field("size", &size); | |
837 | } | |
838 | if let Ok(path) = self.path() { | |
839 | b.field("path", &path); | |
840 | } | |
841 | if let Ok(link_name) = self.link_name() { | |
842 | b.field("link_name", &link_name); | |
843 | } | |
844 | if let Ok(mode) = self.mode() { | |
845 | b.field("mode", &DebugAsOctal(mode)); | |
846 | } | |
847 | if let Ok(uid) = self.uid() { | |
848 | b.field("uid", &uid); | |
849 | } | |
850 | if let Ok(gid) = self.gid() { | |
851 | b.field("gid", &gid); | |
852 | } | |
853 | if let Ok(mtime) = self.mtime() { | |
854 | b.field("mtime", &mtime); | |
855 | } | |
856 | if let Ok(username) = self.username() { | |
857 | b.field("username", &username); | |
858 | } | |
859 | if let Ok(groupname) = self.groupname() { | |
860 | b.field("groupname", &groupname); | |
861 | } | |
862 | if let Ok(device_major) = self.device_major() { | |
863 | b.field("device_major", &device_major); | |
864 | } | |
865 | if let Ok(device_minor) = self.device_minor() { | |
866 | b.field("device_minor", &device_minor); | |
867 | } | |
868 | if let Ok(cksum) = self.cksum() { | |
869 | b.field("cksum", &cksum); | |
870 | b.field("cksum_valid", &(cksum == self.calculate_cksum())); | |
871 | } | |
872 | } | |
873 | } | |
874 | ||
875 | struct DebugAsOctal<T>(T); | |
876 | ||
877 | impl<T: fmt::Octal> fmt::Debug for DebugAsOctal<T> { | |
878 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
879 | fmt::Octal::fmt(&self.0, f) | |
880 | } | |
881 | } | |
882 | ||
883 | unsafe fn cast<T, U>(a: &T) -> &U { | |
884 | assert_eq!(mem::size_of_val(a), mem::size_of::<U>()); | |
885 | assert_eq!(mem::align_of_val(a), mem::align_of::<U>()); | |
886 | &*(a as *const T as *const U) | |
887 | } | |
888 | ||
889 | unsafe fn cast_mut<T, U>(a: &mut T) -> &mut U { | |
890 | assert_eq!(mem::size_of_val(a), mem::size_of::<U>()); | |
891 | assert_eq!(mem::align_of_val(a), mem::align_of::<U>()); | |
892 | &mut *(a as *mut T as *mut U) | |
7cac9316 XL |
893 | } |
894 | ||
895 | impl Clone for Header { | |
896 | fn clone(&self) -> Header { | |
897 | Header { bytes: self.bytes } | |
898 | } | |
899 | } | |
900 | ||
ff7c6d11 XL |
901 | impl fmt::Debug for Header { |
902 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
903 | if let Some(me) = self.as_ustar() { | |
904 | me.fmt(f) | |
905 | } else if let Some(me) = self.as_gnu() { | |
906 | me.fmt(f) | |
907 | } else { | |
908 | self.as_old().fmt(f) | |
909 | } | |
910 | } | |
911 | } | |
912 | ||
913 | impl OldHeader { | |
914 | /// Views this as a normal `Header` | |
915 | pub fn as_header(&self) -> &Header { | |
916 | unsafe { cast(self) } | |
917 | } | |
918 | ||
919 | /// Views this as a normal `Header` | |
920 | pub fn as_header_mut(&mut self) -> &mut Header { | |
921 | unsafe { cast_mut(self) } | |
922 | } | |
923 | } | |
924 | ||
925 | impl fmt::Debug for OldHeader { | |
926 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
927 | let mut f = f.debug_struct("OldHeader"); | |
928 | self.as_header().debug_fields(&mut f); | |
929 | f.finish() | |
930 | } | |
931 | } | |
932 | ||
7cac9316 XL |
933 | impl UstarHeader { |
934 | /// See `Header::path_bytes` | |
935 | pub fn path_bytes(&self) -> Cow<[u8]> { | |
936 | if self.prefix[0] == 0 && !self.name.contains(&b'\\') { | |
937 | Cow::Borrowed(truncate(&self.name)) | |
938 | } else { | |
939 | let mut bytes = Vec::new(); | |
940 | let prefix = truncate(&self.prefix); | |
94222f64 | 941 | if !prefix.is_empty() { |
7cac9316 XL |
942 | bytes.extend_from_slice(prefix); |
943 | bytes.push(b'/'); | |
944 | } | |
945 | bytes.extend_from_slice(truncate(&self.name)); | |
946 | Cow::Owned(bytes) | |
947 | } | |
948 | } | |
949 | ||
83c7162d XL |
950 | /// Gets the path in a "lossy" way, used for error reporting ONLY. |
951 | fn path_lossy(&self) -> String { | |
952 | String::from_utf8_lossy(&self.path_bytes()).to_string() | |
953 | } | |
954 | ||
7cac9316 XL |
955 | /// See `Header::set_path` |
956 | pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { | |
957 | self._set_path(p.as_ref()) | |
958 | } | |
959 | ||
960 | fn _set_path(&mut self, path: &Path) -> io::Result<()> { | |
961 | // This can probably be optimized quite a bit more, but for now just do | |
962 | // something that's relatively easy and readable. | |
963 | // | |
964 | // First up, if the path fits within `self.name` then we just shove it | |
965 | // in there. If not then we try to split it between some existing path | |
966 | // components where it can fit in name/prefix. To do that we peel off | |
967 | // enough until the path fits in `prefix`, then we try to put both | |
968 | // halves into their destination. | |
8faf50e0 | 969 | let bytes = path2bytes(path)?; |
7cac9316 XL |
970 | let (maxnamelen, maxprefixlen) = (self.name.len(), self.prefix.len()); |
971 | if bytes.len() <= maxnamelen { | |
a1dfa0c6 XL |
972 | copy_path_into(&mut self.name, path, false).map_err(|err| { |
973 | io::Error::new( | |
974 | err.kind(), | |
975 | format!("{} when setting path for {}", err, self.path_lossy()), | |
976 | ) | |
977 | })?; | |
7cac9316 XL |
978 | } else { |
979 | let mut prefix = path; | |
980 | let mut prefixlen; | |
981 | loop { | |
982 | match prefix.parent() { | |
983 | Some(parent) => prefix = parent, | |
a1dfa0c6 XL |
984 | None => { |
985 | return Err(other(&format!( | |
986 | "path cannot be split to be inserted into archive: {}", | |
987 | path.display() | |
74b04a01 | 988 | ))); |
a1dfa0c6 | 989 | } |
7cac9316 | 990 | } |
8faf50e0 | 991 | prefixlen = path2bytes(prefix)?.len(); |
7cac9316 | 992 | if prefixlen <= maxprefixlen { |
a1dfa0c6 | 993 | break; |
7cac9316 XL |
994 | } |
995 | } | |
a1dfa0c6 XL |
996 | copy_path_into(&mut self.prefix, prefix, false).map_err(|err| { |
997 | io::Error::new( | |
998 | err.kind(), | |
999 | format!("{} when setting path for {}", err, self.path_lossy()), | |
1000 | ) | |
1001 | })?; | |
8faf50e0 | 1002 | let path = bytes2path(Cow::Borrowed(&bytes[prefixlen + 1..]))?; |
a1dfa0c6 XL |
1003 | copy_path_into(&mut self.name, &path, false).map_err(|err| { |
1004 | io::Error::new( | |
1005 | err.kind(), | |
1006 | format!("{} when setting path for {}", err, self.path_lossy()), | |
1007 | ) | |
1008 | })?; | |
7cac9316 XL |
1009 | } |
1010 | Ok(()) | |
1011 | } | |
1012 | ||
1013 | /// See `Header::username_bytes` | |
1014 | pub fn username_bytes(&self) -> &[u8] { | |
1015 | truncate(&self.uname) | |
1016 | } | |
1017 | ||
1018 | /// See `Header::set_username` | |
1019 | pub fn set_username(&mut self, name: &str) -> io::Result<()> { | |
a1dfa0c6 XL |
1020 | copy_into(&mut self.uname, name.as_bytes()).map_err(|err| { |
1021 | io::Error::new( | |
1022 | err.kind(), | |
1023 | format!("{} when setting username for {}", err, self.path_lossy()), | |
1024 | ) | |
1025 | }) | |
7cac9316 XL |
1026 | } |
1027 | ||
1028 | /// See `Header::groupname_bytes` | |
1029 | pub fn groupname_bytes(&self) -> &[u8] { | |
1030 | truncate(&self.gname) | |
1031 | } | |
1032 | ||
1033 | /// See `Header::set_groupname` | |
1034 | pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { | |
a1dfa0c6 XL |
1035 | copy_into(&mut self.gname, name.as_bytes()).map_err(|err| { |
1036 | io::Error::new( | |
1037 | err.kind(), | |
1038 | format!("{} when setting groupname for {}", err, self.path_lossy()), | |
1039 | ) | |
1040 | }) | |
7cac9316 XL |
1041 | } |
1042 | ||
1043 | /// See `Header::device_major` | |
1044 | pub fn device_major(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
1045 | octal_from(&self.dev_major) |
1046 | .map(|u| u as u32) | |
1047 | .map_err(|err| { | |
1048 | io::Error::new( | |
1049 | err.kind(), | |
1050 | format!( | |
1051 | "{} when getting device_major for {}", | |
1052 | err, | |
1053 | self.path_lossy() | |
1054 | ), | |
1055 | ) | |
1056 | }) | |
7cac9316 XL |
1057 | } |
1058 | ||
1059 | /// See `Header::set_device_major` | |
1060 | pub fn set_device_major(&mut self, major: u32) { | |
1061 | octal_into(&mut self.dev_major, major); | |
1062 | } | |
1063 | ||
1064 | /// See `Header::device_minor` | |
1065 | pub fn device_minor(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
1066 | octal_from(&self.dev_minor) |
1067 | .map(|u| u as u32) | |
1068 | .map_err(|err| { | |
1069 | io::Error::new( | |
1070 | err.kind(), | |
1071 | format!( | |
1072 | "{} when getting device_minor for {}", | |
1073 | err, | |
1074 | self.path_lossy() | |
1075 | ), | |
1076 | ) | |
1077 | }) | |
7cac9316 XL |
1078 | } |
1079 | ||
1080 | /// See `Header::set_device_minor` | |
1081 | pub fn set_device_minor(&mut self, minor: u32) { | |
1082 | octal_into(&mut self.dev_minor, minor); | |
1083 | } | |
ff7c6d11 XL |
1084 | |
1085 | /// Views this as a normal `Header` | |
1086 | pub fn as_header(&self) -> &Header { | |
1087 | unsafe { cast(self) } | |
1088 | } | |
1089 | ||
1090 | /// Views this as a normal `Header` | |
1091 | pub fn as_header_mut(&mut self) -> &mut Header { | |
1092 | unsafe { cast_mut(self) } | |
1093 | } | |
1094 | } | |
1095 | ||
1096 | impl fmt::Debug for UstarHeader { | |
1097 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
1098 | let mut f = f.debug_struct("UstarHeader"); | |
1099 | self.as_header().debug_fields(&mut f); | |
1100 | f.finish() | |
1101 | } | |
7cac9316 XL |
1102 | } |
1103 | ||
1104 | impl GnuHeader { | |
1105 | /// See `Header::username_bytes` | |
1106 | pub fn username_bytes(&self) -> &[u8] { | |
1107 | truncate(&self.uname) | |
1108 | } | |
1109 | ||
83c7162d XL |
1110 | /// Gets the fullname (group:user) in a "lossy" way, used for error reporting ONLY. |
1111 | fn fullname_lossy(&self) -> String { | |
1112 | format!( | |
1113 | "{}:{}", | |
94222f64 XL |
1114 | String::from_utf8_lossy(self.groupname_bytes()), |
1115 | String::from_utf8_lossy(self.username_bytes()), | |
83c7162d XL |
1116 | ) |
1117 | } | |
1118 | ||
7cac9316 XL |
1119 | /// See `Header::set_username` |
1120 | pub fn set_username(&mut self, name: &str) -> io::Result<()> { | |
a1dfa0c6 XL |
1121 | copy_into(&mut self.uname, name.as_bytes()).map_err(|err| { |
1122 | io::Error::new( | |
1123 | err.kind(), | |
1124 | format!( | |
1125 | "{} when setting username for {}", | |
1126 | err, | |
1127 | self.fullname_lossy() | |
1128 | ), | |
1129 | ) | |
1130 | }) | |
7cac9316 XL |
1131 | } |
1132 | ||
1133 | /// See `Header::groupname_bytes` | |
1134 | pub fn groupname_bytes(&self) -> &[u8] { | |
1135 | truncate(&self.gname) | |
1136 | } | |
1137 | ||
1138 | /// See `Header::set_groupname` | |
1139 | pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { | |
a1dfa0c6 XL |
1140 | copy_into(&mut self.gname, name.as_bytes()).map_err(|err| { |
1141 | io::Error::new( | |
1142 | err.kind(), | |
1143 | format!( | |
1144 | "{} when setting groupname for {}", | |
1145 | err, | |
1146 | self.fullname_lossy() | |
1147 | ), | |
1148 | ) | |
1149 | }) | |
7cac9316 XL |
1150 | } |
1151 | ||
1152 | /// See `Header::device_major` | |
1153 | pub fn device_major(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
1154 | octal_from(&self.dev_major) |
1155 | .map(|u| u as u32) | |
1156 | .map_err(|err| { | |
1157 | io::Error::new( | |
1158 | err.kind(), | |
1159 | format!( | |
1160 | "{} when getting device_major for {}", | |
1161 | err, | |
1162 | self.fullname_lossy() | |
1163 | ), | |
1164 | ) | |
1165 | }) | |
7cac9316 XL |
1166 | } |
1167 | ||
1168 | /// See `Header::set_device_major` | |
1169 | pub fn set_device_major(&mut self, major: u32) { | |
1170 | octal_into(&mut self.dev_major, major); | |
1171 | } | |
1172 | ||
1173 | /// See `Header::device_minor` | |
1174 | pub fn device_minor(&self) -> io::Result<u32> { | |
a1dfa0c6 XL |
1175 | octal_from(&self.dev_minor) |
1176 | .map(|u| u as u32) | |
1177 | .map_err(|err| { | |
1178 | io::Error::new( | |
1179 | err.kind(), | |
1180 | format!( | |
1181 | "{} when getting device_minor for {}", | |
1182 | err, | |
1183 | self.fullname_lossy() | |
1184 | ), | |
1185 | ) | |
1186 | }) | |
7cac9316 XL |
1187 | } |
1188 | ||
1189 | /// See `Header::set_device_minor` | |
1190 | pub fn set_device_minor(&mut self, minor: u32) { | |
1191 | octal_into(&mut self.dev_minor, minor); | |
1192 | } | |
1193 | ||
1194 | /// Returns the last modification time in Unix time format | |
1195 | pub fn atime(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
1196 | num_field_wrapper_from(&self.atime).map_err(|err| { |
1197 | io::Error::new( | |
1198 | err.kind(), | |
1199 | format!("{} when getting atime for {}", err, self.fullname_lossy()), | |
1200 | ) | |
1201 | }) | |
7cac9316 XL |
1202 | } |
1203 | ||
1204 | /// Encodes the `atime` provided into this header. | |
1205 | /// | |
1206 | /// Note that this time is typically a number of seconds passed since | |
1207 | /// January 1, 1970. | |
1208 | pub fn set_atime(&mut self, atime: u64) { | |
a1dfa0c6 | 1209 | num_field_wrapper_into(&mut self.atime, atime); |
7cac9316 XL |
1210 | } |
1211 | ||
1212 | /// Returns the last modification time in Unix time format | |
1213 | pub fn ctime(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
1214 | num_field_wrapper_from(&self.ctime).map_err(|err| { |
1215 | io::Error::new( | |
1216 | err.kind(), | |
1217 | format!("{} when getting ctime for {}", err, self.fullname_lossy()), | |
1218 | ) | |
1219 | }) | |
7cac9316 XL |
1220 | } |
1221 | ||
1222 | /// Encodes the `ctime` provided into this header. | |
1223 | /// | |
1224 | /// Note that this time is typically a number of seconds passed since | |
1225 | /// January 1, 1970. | |
1226 | pub fn set_ctime(&mut self, ctime: u64) { | |
a1dfa0c6 | 1227 | num_field_wrapper_into(&mut self.ctime, ctime); |
7cac9316 XL |
1228 | } |
1229 | ||
1230 | /// Returns the "real size" of the file this header represents. | |
1231 | /// | |
1232 | /// This is applicable for sparse files where the returned size here is the | |
1233 | /// size of the entire file after the sparse regions have been filled in. | |
1234 | pub fn real_size(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
1235 | octal_from(&self.realsize).map_err(|err| { |
1236 | io::Error::new( | |
1237 | err.kind(), | |
1238 | format!( | |
1239 | "{} when getting real_size for {}", | |
1240 | err, | |
1241 | self.fullname_lossy() | |
1242 | ), | |
1243 | ) | |
1244 | }) | |
7cac9316 XL |
1245 | } |
1246 | ||
1247 | /// Indicates whether this header will be followed by additional | |
1248 | /// sparse-header records. | |
1249 | /// | |
1250 | /// Note that this is handled internally by this library, and is likely only | |
1251 | /// interesting if a `raw` iterator is being used. | |
1252 | pub fn is_extended(&self) -> bool { | |
1253 | self.isextended[0] == 1 | |
1254 | } | |
ff7c6d11 XL |
1255 | |
1256 | /// Views this as a normal `Header` | |
1257 | pub fn as_header(&self) -> &Header { | |
1258 | unsafe { cast(self) } | |
1259 | } | |
1260 | ||
1261 | /// Views this as a normal `Header` | |
1262 | pub fn as_header_mut(&mut self) -> &mut Header { | |
1263 | unsafe { cast_mut(self) } | |
1264 | } | |
1265 | } | |
1266 | ||
1267 | impl fmt::Debug for GnuHeader { | |
1268 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
1269 | let mut f = f.debug_struct("GnuHeader"); | |
1270 | self.as_header().debug_fields(&mut f); | |
1271 | if let Ok(atime) = self.atime() { | |
1272 | f.field("atime", &atime); | |
1273 | } | |
1274 | if let Ok(ctime) = self.ctime() { | |
1275 | f.field("ctime", &ctime); | |
1276 | } | |
1277 | f.field("is_extended", &self.is_extended()) | |
a1dfa0c6 XL |
1278 | .field("sparse", &DebugSparseHeaders(&self.sparse)) |
1279 | .finish() | |
ff7c6d11 XL |
1280 | } |
1281 | } | |
1282 | ||
1283 | struct DebugSparseHeaders<'a>(&'a [GnuSparseHeader]); | |
1284 | ||
1285 | impl<'a> fmt::Debug for DebugSparseHeaders<'a> { | |
1286 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
1287 | let mut f = f.debug_list(); | |
1288 | for header in self.0 { | |
1289 | if !header.is_empty() { | |
1290 | f.entry(header); | |
1291 | } | |
1292 | } | |
1293 | f.finish() | |
1294 | } | |
7cac9316 XL |
1295 | } |
1296 | ||
1297 | impl GnuSparseHeader { | |
1298 | /// Returns true if block is empty | |
1299 | pub fn is_empty(&self) -> bool { | |
1300 | self.offset[0] == 0 || self.numbytes[0] == 0 | |
1301 | } | |
1302 | ||
1303 | /// Offset of the block from the start of the file | |
1304 | /// | |
1305 | /// Returns `Err` for a malformed `offset` field. | |
1306 | pub fn offset(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
1307 | octal_from(&self.offset).map_err(|err| { |
1308 | io::Error::new( | |
1309 | err.kind(), | |
3dfed10e | 1310 | format!("{} when getting offset from sparse header", err), |
a1dfa0c6 XL |
1311 | ) |
1312 | }) | |
7cac9316 XL |
1313 | } |
1314 | ||
1315 | /// Length of the block | |
1316 | /// | |
1317 | /// Returns `Err` for a malformed `numbytes` field. | |
1318 | pub fn length(&self) -> io::Result<u64> { | |
a1dfa0c6 XL |
1319 | octal_from(&self.numbytes).map_err(|err| { |
1320 | io::Error::new( | |
1321 | err.kind(), | |
1322 | format!("{} when getting length from sparse header", err), | |
1323 | ) | |
1324 | }) | |
7cac9316 XL |
1325 | } |
1326 | } | |
1327 | ||
ff7c6d11 XL |
1328 | impl fmt::Debug for GnuSparseHeader { |
1329 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
1330 | let mut f = f.debug_struct("GnuSparseHeader"); | |
1331 | if let Ok(offset) = self.offset() { | |
1332 | f.field("offset", &offset); | |
1333 | } | |
1334 | if let Ok(length) = self.length() { | |
1335 | f.field("length", &length); | |
1336 | } | |
1337 | f.finish() | |
1338 | } | |
1339 | } | |
1340 | ||
7cac9316 XL |
1341 | impl GnuExtSparseHeader { |
1342 | /// Crates a new zero'd out sparse header entry. | |
1343 | pub fn new() -> GnuExtSparseHeader { | |
1344 | unsafe { mem::zeroed() } | |
1345 | } | |
1346 | ||
1347 | /// Returns a view into this header as a byte array. | |
1348 | pub fn as_bytes(&self) -> &[u8; 512] { | |
1349 | debug_assert_eq!(mem::size_of_val(self), 512); | |
1350 | unsafe { mem::transmute(self) } | |
1351 | } | |
1352 | ||
1353 | /// Returns a view into this header as a byte array. | |
1354 | pub fn as_mut_bytes(&mut self) -> &mut [u8; 512] { | |
1355 | debug_assert_eq!(mem::size_of_val(self), 512); | |
1356 | unsafe { mem::transmute(self) } | |
1357 | } | |
1358 | ||
1359 | /// Returns a slice of the underlying sparse headers. | |
1360 | /// | |
1361 | /// Some headers may represent empty chunks of both the offset and numbytes | |
1362 | /// fields are 0. | |
1363 | pub fn sparse(&self) -> &[GnuSparseHeader; 21] { | |
1364 | &self.sparse | |
1365 | } | |
1366 | ||
1367 | /// Indicates if another sparse header should be following this one. | |
1368 | pub fn is_extended(&self) -> bool { | |
1369 | self.isextended[0] == 1 | |
1370 | } | |
1371 | } | |
1372 | ||
1373 | impl Default for GnuExtSparseHeader { | |
1374 | fn default() -> Self { | |
1375 | Self::new() | |
1376 | } | |
1377 | } | |
1378 | ||
1379 | fn octal_from(slice: &[u8]) -> io::Result<u64> { | |
a1dfa0c6 XL |
1380 | let trun = truncate(slice); |
1381 | let num = match str::from_utf8(trun) { | |
1382 | Ok(n) => n, | |
1383 | Err(_) => { | |
1384 | return Err(other(&format!( | |
1385 | "numeric field did not have utf-8 text: {}", | |
1386 | String::from_utf8_lossy(trun) | |
74b04a01 | 1387 | ))); |
ff7c6d11 | 1388 | } |
a1dfa0c6 XL |
1389 | }; |
1390 | match u64::from_str_radix(num.trim(), 8) { | |
1391 | Ok(n) => Ok(n), | |
1392 | Err(_) => Err(other(&format!("numeric field was not a number: {}", num))), | |
7cac9316 XL |
1393 | } |
1394 | } | |
1395 | ||
1396 | fn octal_into<T: fmt::Octal>(dst: &mut [u8], val: T) { | |
1397 | let o = format!("{:o}", val); | |
1398 | let value = o.bytes().rev().chain(repeat(b'0')); | |
1399 | for (slot, value) in dst.iter_mut().rev().skip(1).zip(value) { | |
1400 | *slot = value; | |
1401 | } | |
1402 | } | |
1403 | ||
a1dfa0c6 XL |
1404 | // Wrapper to figure out if we should fill the header field using tar's numeric |
1405 | // extension (binary) or not (octal). | |
1406 | fn num_field_wrapper_into(dst: &mut [u8], src: u64) { | |
1407 | if src >= 8589934592 || (src >= 2097152 && dst.len() == 8) { | |
1408 | numeric_extended_into(dst, src); | |
1409 | } else { | |
1410 | octal_into(dst, src); | |
1411 | } | |
1412 | } | |
1413 | ||
1414 | // Wrapper to figure out if we should read the header field in binary (numeric | |
1415 | // extension) or octal (standard encoding). | |
1416 | fn num_field_wrapper_from(src: &[u8]) -> io::Result<u64> { | |
1417 | if src[0] & 0x80 != 0 { | |
1418 | Ok(numeric_extended_from(src)) | |
1419 | } else { | |
1420 | octal_from(src) | |
1421 | } | |
1422 | } | |
1423 | ||
1424 | // When writing numeric fields with is the extended form, the high bit of the | |
1425 | // first byte is set to 1 and the remainder of the field is treated as binary | |
1426 | // instead of octal ascii. | |
1427 | // This handles writing u64 to 8 (uid, gid) or 12 (size, *time) bytes array. | |
1428 | fn numeric_extended_into(dst: &mut [u8], src: u64) { | |
1429 | let len: usize = dst.len(); | |
1430 | for (slot, val) in dst.iter_mut().zip( | |
74b04a01 XL |
1431 | repeat(0) |
1432 | .take(len - 8) // to zero init extra bytes | |
a1dfa0c6 XL |
1433 | .chain((0..8).rev().map(|x| ((src >> (8 * x)) & 0xff) as u8)), |
1434 | ) { | |
1435 | *slot = val; | |
1436 | } | |
1437 | dst[0] |= 0x80; | |
1438 | } | |
1439 | ||
1440 | fn numeric_extended_from(src: &[u8]) -> u64 { | |
1441 | let mut dst: u64 = 0; | |
1442 | let mut b_to_skip = 1; | |
1443 | if src.len() == 8 { | |
1444 | // read first byte without extension flag bit | |
1445 | dst = (src[0] ^ 0x80) as u64; | |
1446 | } else { | |
1447 | // only read last 8 bytes | |
1448 | b_to_skip = src.len() - 8; | |
1449 | } | |
1450 | for byte in src.iter().skip(b_to_skip) { | |
1451 | dst <<= 8; | |
1452 | dst |= *byte as u64; | |
1453 | } | |
1454 | dst | |
1455 | } | |
1456 | ||
7cac9316 XL |
1457 | fn truncate(slice: &[u8]) -> &[u8] { |
1458 | match slice.iter().position(|i| *i == 0) { | |
1459 | Some(i) => &slice[..i], | |
1460 | None => slice, | |
1461 | } | |
1462 | } | |
1463 | ||
1464 | /// Copies `bytes` into the `slot` provided, returning an error if the `bytes` | |
1465 | /// array is too long or if it contains any nul bytes. | |
1466 | fn copy_into(slot: &mut [u8], bytes: &[u8]) -> io::Result<()> { | |
1467 | if bytes.len() > slot.len() { | |
1468 | Err(other("provided value is too long")) | |
1469 | } else if bytes.iter().any(|b| *b == 0) { | |
1470 | Err(other("provided value contains a nul byte")) | |
1471 | } else { | |
1472 | for (slot, val) in slot.iter_mut().zip(bytes.iter().chain(Some(&0))) { | |
1473 | *slot = *val; | |
1474 | } | |
1475 | Ok(()) | |
1476 | } | |
1477 | } | |
1478 | ||
1479 | /// Copies `path` into the `slot` provided | |
1480 | /// | |
1481 | /// Returns an error if: | |
1482 | /// | |
1483 | /// * the path is too long to fit | |
1484 | /// * a nul byte was found | |
1485 | /// * an invalid path component is encountered (e.g. a root path or parent dir) | |
1486 | /// * the path itself is empty | |
a1dfa0c6 | 1487 | fn copy_path_into(mut slot: &mut [u8], path: &Path, is_link_name: bool) -> io::Result<()> { |
7cac9316 | 1488 | let mut emitted = false; |
ff7c6d11 | 1489 | let mut needs_slash = false; |
7cac9316 | 1490 | for component in path.components() { |
8faf50e0 | 1491 | let bytes = path2bytes(Path::new(component.as_os_str()))?; |
7cac9316 | 1492 | match (component, is_link_name) { |
a1dfa0c6 | 1493 | (Component::Prefix(..), false) | (Component::RootDir, false) => { |
74b04a01 | 1494 | return Err(other("paths in archives must be relative")); |
7cac9316 XL |
1495 | } |
1496 | (Component::ParentDir, false) => { | |
74b04a01 | 1497 | return Err(other("paths in archives must not have `..`")); |
7cac9316 | 1498 | } |
ff7c6d11 | 1499 | // Allow "./" as the path |
a1dfa0c6 | 1500 | (Component::CurDir, false) if path.components().count() == 1 => {} |
7cac9316 | 1501 | (Component::CurDir, false) => continue, |
a1dfa0c6 | 1502 | (Component::Normal(_), _) | (_, true) => {} |
7cac9316 | 1503 | }; |
ff7c6d11 | 1504 | if needs_slash { |
8faf50e0 | 1505 | copy(&mut slot, b"/")?; |
7cac9316 XL |
1506 | } |
1507 | if bytes.contains(&b'/') { | |
1508 | if let Component::Normal(..) = component { | |
a1dfa0c6 | 1509 | return Err(other("path component in archive cannot contain `/`")); |
7cac9316 XL |
1510 | } |
1511 | } | |
8faf50e0 | 1512 | copy(&mut slot, &*bytes)?; |
ff7c6d11 XL |
1513 | if &*bytes != b"/" { |
1514 | needs_slash = true; | |
1515 | } | |
7cac9316 XL |
1516 | emitted = true; |
1517 | } | |
1518 | if !emitted { | |
a1dfa0c6 | 1519 | return Err(other("paths in archives must have at least one component")); |
7cac9316 XL |
1520 | } |
1521 | if ends_with_slash(path) { | |
8faf50e0 | 1522 | copy(&mut slot, &[b'/'])?; |
7cac9316 XL |
1523 | } |
1524 | return Ok(()); | |
1525 | ||
1526 | fn copy(slot: &mut &mut [u8], bytes: &[u8]) -> io::Result<()> { | |
8faf50e0 | 1527 | copy_into(*slot, bytes)?; |
7cac9316 XL |
1528 | let tmp = mem::replace(slot, &mut []); |
1529 | *slot = &mut tmp[bytes.len()..]; | |
1530 | Ok(()) | |
1531 | } | |
1532 | } | |
1533 | ||
74b04a01 XL |
1534 | #[cfg(target_arch = "wasm32")] |
1535 | fn ends_with_slash(p: &Path) -> bool { | |
1536 | p.to_string_lossy().ends_with('/') | |
1537 | } | |
1538 | ||
7cac9316 XL |
1539 | #[cfg(windows)] |
1540 | fn ends_with_slash(p: &Path) -> bool { | |
1541 | let last = p.as_os_str().encode_wide().last(); | |
1542 | last == Some(b'/' as u16) || last == Some(b'\\' as u16) | |
1543 | } | |
1544 | ||
3dfed10e | 1545 | #[cfg(unix)] |
7cac9316 XL |
1546 | fn ends_with_slash(p: &Path) -> bool { |
1547 | p.as_os_str().as_bytes().ends_with(&[b'/']) | |
1548 | } | |
1549 | ||
74b04a01 | 1550 | #[cfg(any(windows, target_arch = "wasm32"))] |
7cac9316 | 1551 | pub fn path2bytes(p: &Path) -> io::Result<Cow<[u8]>> { |
a1dfa0c6 XL |
1552 | p.as_os_str() |
1553 | .to_str() | |
1554 | .map(|s| s.as_bytes()) | |
3dfed10e | 1555 | .ok_or_else(|| other(&format!("path {} was not valid Unicode", p.display()))) |
a1dfa0c6 XL |
1556 | .map(|bytes| { |
1557 | if bytes.contains(&b'\\') { | |
1558 | // Normalize to Unix-style path separators | |
1559 | let mut bytes = bytes.to_owned(); | |
1560 | for b in &mut bytes { | |
1561 | if *b == b'\\' { | |
1562 | *b = b'/'; | |
1563 | } | |
7cac9316 | 1564 | } |
a1dfa0c6 XL |
1565 | Cow::Owned(bytes) |
1566 | } else { | |
1567 | Cow::Borrowed(bytes) | |
7cac9316 | 1568 | } |
a1dfa0c6 | 1569 | }) |
7cac9316 XL |
1570 | } |
1571 | ||
3dfed10e | 1572 | #[cfg(unix)] |
83c7162d | 1573 | /// On unix this will never fail |
7cac9316 XL |
1574 | pub fn path2bytes(p: &Path) -> io::Result<Cow<[u8]>> { |
1575 | Ok(p.as_os_str().as_bytes()).map(Cow::Borrowed) | |
1576 | } | |
1577 | ||
1578 | #[cfg(windows)] | |
3dfed10e | 1579 | /// On windows we cannot accept non-Unicode bytes because it |
83c7162d | 1580 | /// is impossible to convert it to UTF-16. |
7cac9316 XL |
1581 | pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { |
1582 | return match bytes { | |
1583 | Cow::Borrowed(bytes) => { | |
3dfed10e | 1584 | let s = str::from_utf8(bytes).map_err(|_| not_unicode(bytes))?; |
7cac9316 XL |
1585 | Ok(Cow::Borrowed(Path::new(s))) |
1586 | } | |
1587 | Cow::Owned(bytes) => { | |
3dfed10e | 1588 | let s = String::from_utf8(bytes).map_err(|uerr| not_unicode(&uerr.into_bytes()))?; |
7cac9316 XL |
1589 | Ok(Cow::Owned(PathBuf::from(s))) |
1590 | } | |
1591 | }; | |
1592 | ||
83c7162d XL |
1593 | fn not_unicode(v: &[u8]) -> io::Error { |
1594 | other(&format!( | |
3dfed10e | 1595 | "only Unicode paths are supported on Windows: {}", |
83c7162d XL |
1596 | String::from_utf8_lossy(v) |
1597 | )) | |
7cac9316 XL |
1598 | } |
1599 | } | |
1600 | ||
3dfed10e | 1601 | #[cfg(unix)] |
83c7162d | 1602 | /// On unix this operation can never fail. |
7cac9316 XL |
1603 | pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { |
1604 | use std::ffi::{OsStr, OsString}; | |
1605 | ||
1606 | Ok(match bytes { | |
3dfed10e XL |
1607 | Cow::Borrowed(bytes) => Cow::Borrowed(Path::new(OsStr::from_bytes(bytes))), |
1608 | Cow::Owned(bytes) => Cow::Owned(PathBuf::from(OsString::from_vec(bytes))), | |
7cac9316 XL |
1609 | }) |
1610 | } | |
74b04a01 XL |
1611 | |
1612 | #[cfg(target_arch = "wasm32")] | |
1613 | pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { | |
1614 | Ok(match bytes { | |
1615 | Cow::Borrowed(bytes) => { | |
1616 | Cow::Borrowed({ Path::new(str::from_utf8(bytes).map_err(invalid_utf8)?) }) | |
1617 | } | |
1618 | Cow::Owned(bytes) => { | |
1619 | Cow::Owned({ PathBuf::from(String::from_utf8(bytes).map_err(invalid_utf8)?) }) | |
1620 | } | |
1621 | }) | |
1622 | } | |
1623 | ||
1624 | #[cfg(target_arch = "wasm32")] | |
1625 | fn invalid_utf8<T>(_: T) -> io::Error { | |
3dfed10e | 1626 | io::Error::new(io::ErrorKind::InvalidData, "Invalid utf-8") |
74b04a01 | 1627 | } |