| #[cfg(unix)] |
| use std::os::unix::prelude::*; |
| #[cfg(windows)] |
| use std::os::windows::prelude::*; |
| |
| use std::borrow::Cow; |
| use std::fmt; |
| use std::fs; |
| use std::io; |
| use std::iter; |
| use std::iter::{once, repeat}; |
| use std::mem; |
| use std::path::{Component, Path, PathBuf}; |
| use std::str; |
| |
| use crate::other; |
| use crate::EntryType; |
| |
| /// A deterministic, arbitrary, non-zero timestamp that use used as `mtime` |
| /// of headers when [`HeaderMode::Deterministic`] is used. |
| /// |
| /// This value, chosen after careful deliberation, corresponds to _Jul 23, 2006_, |
| /// which is the date of the first commit for what would become Rust. |
| #[cfg(any(unix, windows))] |
| const DETERMINISTIC_TIMESTAMP: u64 = 1153704088; |
| |
| pub(crate) const GNU_SPARSE_HEADERS_COUNT: usize = 4; |
| |
| pub(crate) const GNU_EXT_SPARSE_HEADERS_COUNT: usize = 21; |
| |
| /// Representation of the header of an entry in an archive |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct Header { |
| bytes: [u8; 512], |
| } |
| |
| /// Declares the information that should be included when filling a Header |
| /// from filesystem metadata. |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| #[non_exhaustive] |
| pub enum HeaderMode { |
| /// All supported metadata, including mod/access times and ownership will |
| /// be included. |
| Complete, |
| |
| /// Only metadata that is directly relevant to the identity of a file will |
| /// be included. In particular, ownership and mod/access times are excluded. |
| Deterministic, |
| } |
| |
| /// Representation of the header of an entry in an archive |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct OldHeader { |
| pub name: [u8; 100], |
| pub mode: [u8; 8], |
| pub uid: [u8; 8], |
| pub gid: [u8; 8], |
| pub size: [u8; 12], |
| pub mtime: [u8; 12], |
| pub cksum: [u8; 8], |
| pub linkflag: [u8; 1], |
| pub linkname: [u8; 100], |
| pub pad: [u8; 255], |
| } |
| |
| /// Representation of the header of an entry in an archive |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct UstarHeader { |
| pub name: [u8; 100], |
| pub mode: [u8; 8], |
| pub uid: [u8; 8], |
| pub gid: [u8; 8], |
| pub size: [u8; 12], |
| pub mtime: [u8; 12], |
| pub cksum: [u8; 8], |
| pub typeflag: [u8; 1], |
| pub linkname: [u8; 100], |
| |
| // UStar format |
| pub magic: [u8; 6], |
| pub version: [u8; 2], |
| pub uname: [u8; 32], |
| pub gname: [u8; 32], |
| pub dev_major: [u8; 8], |
| pub dev_minor: [u8; 8], |
| pub prefix: [u8; 155], |
| pub pad: [u8; 12], |
| } |
| |
| /// Representation of the header of an entry in an archive |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct GnuHeader { |
| pub name: [u8; 100], |
| pub mode: [u8; 8], |
| pub uid: [u8; 8], |
| pub gid: [u8; 8], |
| pub size: [u8; 12], |
| pub mtime: [u8; 12], |
| pub cksum: [u8; 8], |
| pub typeflag: [u8; 1], |
| pub linkname: [u8; 100], |
| |
| // GNU format |
| pub magic: [u8; 6], |
| pub version: [u8; 2], |
| pub uname: [u8; 32], |
| pub gname: [u8; 32], |
| pub dev_major: [u8; 8], |
| pub dev_minor: [u8; 8], |
| pub atime: [u8; 12], |
| pub ctime: [u8; 12], |
| pub offset: [u8; 12], |
| pub longnames: [u8; 4], |
| pub unused: [u8; 1], |
| pub sparse: [GnuSparseHeader; GNU_SPARSE_HEADERS_COUNT], |
| pub isextended: [u8; 1], |
| pub realsize: [u8; 12], |
| pub pad: [u8; 17], |
| } |
| |
| /// Description of the header of a spare entry. |
| /// |
| /// Specifies the offset/number of bytes of a chunk of data in octal. |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct GnuSparseHeader { |
| pub offset: [u8; 12], |
| pub numbytes: [u8; 12], |
| } |
| |
| /// Representation of the entry found to represent extended GNU sparse files. |
| /// |
| /// When a `GnuHeader` has the `isextended` flag set to `1` then the contents of |
| /// the next entry will be one of these headers. |
| #[repr(C)] |
| #[allow(missing_docs)] |
| pub struct GnuExtSparseHeader { |
| pub sparse: [GnuSparseHeader; GNU_EXT_SPARSE_HEADERS_COUNT], |
| pub isextended: [u8; 1], |
| pub padding: [u8; 7], |
| } |
| |
| impl Header { |
| /// Creates a new blank GNU header. |
| /// |
| /// The GNU style header is the default for this library and allows various |
| /// extensions such as long path names, long link names, and setting the |
| /// atime/ctime metadata attributes of files. |
| pub fn new_gnu() -> Header { |
| let mut header = Header { bytes: [0; 512] }; |
| unsafe { |
| let gnu = cast_mut::<_, GnuHeader>(&mut header); |
| gnu.magic = *b"ustar "; |
| gnu.version = *b" \0"; |
| } |
| header.set_mtime(0); |
| header |
| } |
| |
| /// Creates a new blank UStar header. |
| /// |
| /// The UStar style header is an extension of the original archive header |
| /// which enables some extra metadata along with storing a longer (but not |
| /// too long) path name. |
| /// |
| /// UStar is also the basis used for pax archives. |
| pub fn new_ustar() -> Header { |
| let mut header = Header { bytes: [0; 512] }; |
| unsafe { |
| let gnu = cast_mut::<_, UstarHeader>(&mut header); |
| gnu.magic = *b"ustar\0"; |
| gnu.version = *b"00"; |
| } |
| header.set_mtime(0); |
| header |
| } |
| |
| /// Creates a new blank old header. |
| /// |
| /// This header format is the original archive header format which all other |
| /// versions are compatible with (e.g. they are a superset). This header |
| /// format limits the path name limit and isn't able to contain extra |
| /// metadata like atime/ctime. |
| pub fn new_old() -> Header { |
| let mut header = Header { bytes: [0; 512] }; |
| header.set_mtime(0); |
| header |
| } |
| |
| fn is_ustar(&self) -> bool { |
| let ustar = unsafe { cast::<_, UstarHeader>(self) }; |
| ustar.magic[..] == b"ustar\0"[..] && ustar.version[..] == b"00"[..] |
| } |
| |
| fn is_gnu(&self) -> bool { |
| let ustar = unsafe { cast::<_, UstarHeader>(self) }; |
| ustar.magic[..] == b"ustar "[..] && ustar.version[..] == b" \0"[..] |
| } |
| |
| /// View this archive header as a raw "old" archive header. |
| /// |
| /// This view will always succeed as all archive header formats will fill |
| /// out at least the fields specified in the old header format. |
| pub fn as_old(&self) -> &OldHeader { |
| unsafe { cast(self) } |
| } |
| |
| /// Same as `as_old`, but the mutable version. |
| pub fn as_old_mut(&mut self) -> &mut OldHeader { |
| unsafe { cast_mut(self) } |
| } |
| |
| /// View this archive header as a raw UStar archive header. |
| /// |
| /// The UStar format is an extension to the tar archive format which enables |
| /// longer pathnames and a few extra attributes such as the group and user |
| /// name. |
| /// |
| /// This cast may not succeed as this function will test whether the |
| /// magic/version fields of the UStar format have the appropriate values, |
| /// returning `None` if they aren't correct. |
| pub fn as_ustar(&self) -> Option<&UstarHeader> { |
| if self.is_ustar() { |
| Some(unsafe { cast(self) }) |
| } else { |
| None |
| } |
| } |
| |
| /// Same as `as_ustar_mut`, but the mutable version. |
| pub fn as_ustar_mut(&mut self) -> Option<&mut UstarHeader> { |
| if self.is_ustar() { |
| Some(unsafe { cast_mut(self) }) |
| } else { |
| None |
| } |
| } |
| |
| /// View this archive header as a raw GNU archive header. |
| /// |
| /// The GNU format is an extension to the tar archive format which enables |
| /// longer pathnames and a few extra attributes such as the group and user |
| /// name. |
| /// |
| /// This cast may not succeed as this function will test whether the |
| /// magic/version fields of the GNU format have the appropriate values, |
| /// returning `None` if they aren't correct. |
| pub fn as_gnu(&self) -> Option<&GnuHeader> { |
| if self.is_gnu() { |
| Some(unsafe { cast(self) }) |
| } else { |
| None |
| } |
| } |
| |
| /// Same as `as_gnu`, but the mutable version. |
| pub fn as_gnu_mut(&mut self) -> Option<&mut GnuHeader> { |
| if self.is_gnu() { |
| Some(unsafe { cast_mut(self) }) |
| } else { |
| None |
| } |
| } |
| |
| /// Treats the given byte slice as a header. |
| /// |
| /// Panics if the length of the passed slice is not equal to 512. |
| pub fn from_byte_slice(bytes: &[u8]) -> &Header { |
| assert_eq!(bytes.len(), mem::size_of::<Header>()); |
| assert_eq!(mem::align_of_val(bytes), mem::align_of::<Header>()); |
| unsafe { &*(bytes.as_ptr() as *const Header) } |
| } |
| |
| /// Returns a view into this header as a byte array. |
| pub fn as_bytes(&self) -> &[u8; 512] { |
| &self.bytes |
| } |
| |
| /// Returns a view into this header as a byte array. |
| pub fn as_mut_bytes(&mut self) -> &mut [u8; 512] { |
| &mut self.bytes |
| } |
| |
| /// Blanket sets the metadata in this header from the metadata argument |
| /// provided. |
| /// |
| /// This is useful for initializing a `Header` from the OS's metadata from a |
| /// file. By default, this will use `HeaderMode::Complete` to include all |
| /// metadata. |
| pub fn set_metadata(&mut self, meta: &fs::Metadata) { |
| self.fill_from(meta, HeaderMode::Complete); |
| } |
| |
| /// Sets only the metadata relevant to the given HeaderMode in this header |
| /// from the metadata argument provided. |
| pub fn set_metadata_in_mode(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
| self.fill_from(meta, mode); |
| } |
| |
| /// Returns the size of entry's data this header represents. |
| /// |
| /// This is different from `Header::size` for sparse files, which have |
| /// some longer `size()` but shorter `entry_size()`. The `entry_size()` |
| /// listed here should be the number of bytes in the archive this header |
| /// describes. |
| /// |
| /// May return an error if the field is corrupted. |
| pub fn entry_size(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.as_old().size).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting size for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Returns the file size this header represents. |
| /// |
| /// May return an error if the field is corrupted. |
| pub fn size(&self) -> io::Result<u64> { |
| if self.entry_type().is_gnu_sparse() { |
| self.as_gnu() |
| .ok_or_else(|| other("sparse header was not a gnu header")) |
| .and_then(|h| h.real_size()) |
| } else { |
| self.entry_size() |
| } |
| } |
| |
| /// Encodes the `size` argument into the size field of this header. |
| pub fn set_size(&mut self, size: u64) { |
| num_field_wrapper_into(&mut self.as_old_mut().size, size); |
| } |
| |
| /// Returns the raw path name stored in this header. |
| /// |
| /// This method may fail if the pathname is not valid Unicode and this is |
| /// called on a Windows platform. |
| /// |
| /// Note that this function will convert any `\` characters to directory |
| /// separators. |
| pub fn path(&self) -> io::Result<Cow<Path>> { |
| bytes2path(self.path_bytes()) |
| } |
| |
| /// Returns the pathname stored in this header as a byte array. |
| /// |
| /// This function is guaranteed to succeed, but you may wish to call the |
| /// `path` method to convert to a `Path`. |
| /// |
| /// Note that this function will convert any `\` characters to directory |
| /// separators. |
| pub fn path_bytes(&self) -> Cow<[u8]> { |
| if let Some(ustar) = self.as_ustar() { |
| ustar.path_bytes() |
| } else { |
| let name = truncate(&self.as_old().name); |
| Cow::Borrowed(name) |
| } |
| } |
| |
| /// Gets the path in a "lossy" way, used for error reporting ONLY. |
| fn path_lossy(&self) -> String { |
| String::from_utf8_lossy(&self.path_bytes()).to_string() |
| } |
| |
| /// Sets the path name for this header. |
| /// |
| /// This function will set the pathname listed in this header, encoding it |
| /// in the appropriate format. May fail if the path is too long or if the |
| /// path specified is not Unicode and this is a Windows platform. Will |
| /// strip out any "." path component, which signifies the current directory. |
| /// |
| /// Note: This function does not support names over 100 bytes, or paths |
| /// over 255 bytes, even for formats that support longer names. Instead, |
| /// use `Builder` methods to insert a long-name extension at the same time |
| /// as the file content. |
| pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { |
| self._set_path(p.as_ref()) |
| } |
| |
| fn _set_path(&mut self, path: &Path) -> io::Result<()> { |
| if let Some(ustar) = self.as_ustar_mut() { |
| return ustar.set_path(path); |
| } |
| copy_path_into(&mut self.as_old_mut().name, path, false).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting path for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Returns the link name stored in this header, if any is found. |
| /// |
| /// This method may fail if the pathname is not valid Unicode and this is |
| /// called on a Windows platform. `Ok(None)` being returned, however, |
| /// indicates that the link name was not present. |
| /// |
| /// Note that this function will convert any `\` characters to directory |
| /// separators. |
| pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> { |
| match self.link_name_bytes() { |
| Some(bytes) => bytes2path(bytes).map(Some), |
| None => Ok(None), |
| } |
| } |
| |
| /// Returns the link name stored in this header as a byte array, if any. |
| /// |
| /// This function is guaranteed to succeed, but you may wish to call the |
| /// `link_name` method to convert to a `Path`. |
| /// |
| /// Note that this function will convert any `\` characters to directory |
| /// separators. |
| pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> { |
| let old = self.as_old(); |
| if old.linkname[0] != 0 { |
| Some(Cow::Borrowed(truncate(&old.linkname))) |
| } else { |
| None |
| } |
| } |
| |
| /// Sets the link name for this header. |
| /// |
| /// This function will set the linkname listed in this header, encoding it |
| /// in the appropriate format. May fail if the link name is too long or if |
| /// the path specified is not Unicode and this is a Windows platform. Will |
| /// strip out any "." path component, which signifies the current directory. |
| /// |
| /// To use GNU long link names, prefer instead [`crate::Builder::append_link`]. |
| pub fn set_link_name<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { |
| self._set_link_name(p.as_ref()) |
| } |
| |
| fn _set_link_name(&mut self, path: &Path) -> io::Result<()> { |
| copy_path_into(&mut self.as_old_mut().linkname, path, true).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting link name for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Sets the link name for this header without any transformation. |
| /// |
| /// This function is like [`Self::set_link_name`] but accepts an arbitrary byte array. |
| /// Hence it will not perform any canonicalization, such as replacing duplicate `//` with `/`. |
| pub fn set_link_name_literal<P: AsRef<[u8]>>(&mut self, p: P) -> io::Result<()> { |
| self._set_link_name_literal(p.as_ref()) |
| } |
| |
| fn _set_link_name_literal(&mut self, bytes: &[u8]) -> io::Result<()> { |
| copy_into(&mut self.as_old_mut().linkname, bytes) |
| } |
| |
| /// Returns the mode bits for this file |
| /// |
| /// May return an error if the field is corrupted. |
| pub fn mode(&self) -> io::Result<u32> { |
| octal_from(&self.as_old().mode) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting mode for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `mode` provided into this header. |
| pub fn set_mode(&mut self, mode: u32) { |
| octal_into(&mut self.as_old_mut().mode, mode); |
| } |
| |
| /// Returns the value of the owner's user ID field |
| /// |
| /// May return an error if the field is corrupted. |
| pub fn uid(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.as_old().uid) |
| .map(|u| u as u64) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting uid for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `uid` provided into this header. |
| pub fn set_uid(&mut self, uid: u64) { |
| num_field_wrapper_into(&mut self.as_old_mut().uid, uid); |
| } |
| |
| /// Returns the value of the group's user ID field |
| pub fn gid(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.as_old().gid) |
| .map(|u| u as u64) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting gid for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `gid` provided into this header. |
| pub fn set_gid(&mut self, gid: u64) { |
| num_field_wrapper_into(&mut self.as_old_mut().gid, gid); |
| } |
| |
| /// Returns the last modification time in Unix time format |
| pub fn mtime(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.as_old().mtime).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting mtime for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `mtime` provided into this header. |
| /// |
| /// Note that this time is typically a number of seconds passed since |
| /// January 1, 1970. |
| pub fn set_mtime(&mut self, mtime: u64) { |
| num_field_wrapper_into(&mut self.as_old_mut().mtime, mtime); |
| } |
| |
| /// Return the user name of the owner of this file. |
| /// |
| /// A return value of `Ok(Some(..))` indicates that the user name was |
| /// present and was valid utf-8, `Ok(None)` indicates that the user name is |
| /// not present in this archive format, and `Err` indicates that the user |
| /// name was present but was not valid utf-8. |
| pub fn username(&self) -> Result<Option<&str>, str::Utf8Error> { |
| match self.username_bytes() { |
| Some(bytes) => str::from_utf8(bytes).map(Some), |
| None => Ok(None), |
| } |
| } |
| |
| /// Returns the user name of the owner of this file, if present. |
| /// |
| /// A return value of `None` indicates that the user name is not present in |
| /// this header format. |
| pub fn username_bytes(&self) -> Option<&[u8]> { |
| if let Some(ustar) = self.as_ustar() { |
| Some(ustar.username_bytes()) |
| } else if let Some(gnu) = self.as_gnu() { |
| Some(gnu.username_bytes()) |
| } else { |
| None |
| } |
| } |
| |
| /// Sets the username inside this header. |
| /// |
| /// This function will return an error if this header format cannot encode a |
| /// user name or the name is too long. |
| pub fn set_username(&mut self, name: &str) -> io::Result<()> { |
| if let Some(ustar) = self.as_ustar_mut() { |
| return ustar.set_username(name); |
| } |
| if let Some(gnu) = self.as_gnu_mut() { |
| gnu.set_username(name) |
| } else { |
| Err(other("not a ustar or gnu archive, cannot set username")) |
| } |
| } |
| |
| /// Return the group name of the owner of this file. |
| /// |
| /// A return value of `Ok(Some(..))` indicates that the group name was |
| /// present and was valid utf-8, `Ok(None)` indicates that the group name is |
| /// not present in this archive format, and `Err` indicates that the group |
| /// name was present but was not valid utf-8. |
| pub fn groupname(&self) -> Result<Option<&str>, str::Utf8Error> { |
| match self.groupname_bytes() { |
| Some(bytes) => str::from_utf8(bytes).map(Some), |
| None => Ok(None), |
| } |
| } |
| |
| /// Returns the group name of the owner of this file, if present. |
| /// |
| /// A return value of `None` indicates that the group name is not present in |
| /// this header format. |
| pub fn groupname_bytes(&self) -> Option<&[u8]> { |
| if let Some(ustar) = self.as_ustar() { |
| Some(ustar.groupname_bytes()) |
| } else if let Some(gnu) = self.as_gnu() { |
| Some(gnu.groupname_bytes()) |
| } else { |
| None |
| } |
| } |
| |
| /// Sets the group name inside this header. |
| /// |
| /// This function will return an error if this header format cannot encode a |
| /// group name or the name is too long. |
| pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { |
| if let Some(ustar) = self.as_ustar_mut() { |
| return ustar.set_groupname(name); |
| } |
| if let Some(gnu) = self.as_gnu_mut() { |
| gnu.set_groupname(name) |
| } else { |
| Err(other("not a ustar or gnu archive, cannot set groupname")) |
| } |
| } |
| |
| /// Returns the device major number, if present. |
| /// |
| /// This field may not be present in all archives, and it may not be |
| /// correctly formed in all archives. `Ok(Some(..))` means it was present |
| /// and correctly decoded, `Ok(None)` indicates that this header format does |
| /// not include the device major number, and `Err` indicates that it was |
| /// present and failed to decode. |
| pub fn device_major(&self) -> io::Result<Option<u32>> { |
| if let Some(ustar) = self.as_ustar() { |
| ustar.device_major().map(Some) |
| } else if let Some(gnu) = self.as_gnu() { |
| gnu.device_major().map(Some) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| /// Encodes the value `major` into the dev_major field of this header. |
| /// |
| /// This function will return an error if this header format cannot encode a |
| /// major device number. |
| pub fn set_device_major(&mut self, major: u32) -> io::Result<()> { |
| if let Some(ustar) = self.as_ustar_mut() { |
| ustar.set_device_major(major); |
| Ok(()) |
| } else if let Some(gnu) = self.as_gnu_mut() { |
| gnu.set_device_major(major); |
| Ok(()) |
| } else { |
| Err(other("not a ustar or gnu archive, cannot set dev_major")) |
| } |
| } |
| |
| /// Returns the device minor number, if present. |
| /// |
| /// This field may not be present in all archives, and it may not be |
| /// correctly formed in all archives. `Ok(Some(..))` means it was present |
| /// and correctly decoded, `Ok(None)` indicates that this header format does |
| /// not include the device minor number, and `Err` indicates that it was |
| /// present and failed to decode. |
| pub fn device_minor(&self) -> io::Result<Option<u32>> { |
| if let Some(ustar) = self.as_ustar() { |
| ustar.device_minor().map(Some) |
| } else if let Some(gnu) = self.as_gnu() { |
| gnu.device_minor().map(Some) |
| } else { |
| Ok(None) |
| } |
| } |
| |
| /// Encodes the value `minor` into the dev_minor field of this header. |
| /// |
| /// This function will return an error if this header format cannot encode a |
| /// minor device number. |
| pub fn set_device_minor(&mut self, minor: u32) -> io::Result<()> { |
| if let Some(ustar) = self.as_ustar_mut() { |
| ustar.set_device_minor(minor); |
| Ok(()) |
| } else if let Some(gnu) = self.as_gnu_mut() { |
| gnu.set_device_minor(minor); |
| Ok(()) |
| } else { |
| Err(other("not a ustar or gnu archive, cannot set dev_minor")) |
| } |
| } |
| |
| /// Returns the type of file described by this header. |
| pub fn entry_type(&self) -> EntryType { |
| EntryType::new(self.as_old().linkflag[0]) |
| } |
| |
| /// Sets the type of file that will be described by this header. |
| pub fn set_entry_type(&mut self, ty: EntryType) { |
| self.as_old_mut().linkflag = [ty.as_byte()]; |
| } |
| |
| /// Returns the checksum field of this header. |
| /// |
| /// May return an error if the field is corrupted. |
| pub fn cksum(&self) -> io::Result<u32> { |
| octal_from(&self.as_old().cksum) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting cksum for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// Sets the checksum field of this header based on the current fields in |
| /// this header. |
| pub fn set_cksum(&mut self) { |
| let cksum = self.calculate_cksum(); |
| octal_into(&mut self.as_old_mut().cksum, cksum); |
| } |
| |
| fn calculate_cksum(&self) -> u32 { |
| let old = self.as_old(); |
| let start = old as *const _ as usize; |
| let cksum_start = old.cksum.as_ptr() as *const _ as usize; |
| let offset = cksum_start - start; |
| let len = old.cksum.len(); |
| self.bytes[0..offset] |
| .iter() |
| .chain(iter::repeat(&b' ').take(len)) |
| .chain(&self.bytes[offset + len..]) |
| .fold(0, |a, b| a + (*b as u32)) |
| } |
| |
| fn fill_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
| self.fill_platform_from(meta, mode); |
| // Set size of directories to zero |
| self.set_size(if meta.is_dir() || meta.file_type().is_symlink() { |
| 0 |
| } else { |
| meta.len() |
| }); |
| if let Some(ustar) = self.as_ustar_mut() { |
| ustar.set_device_major(0); |
| ustar.set_device_minor(0); |
| } |
| if let Some(gnu) = self.as_gnu_mut() { |
| gnu.set_device_major(0); |
| gnu.set_device_minor(0); |
| } |
| } |
| |
| #[cfg(target_arch = "wasm32")] |
| #[allow(unused_variables)] |
| fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
| unimplemented!(); |
| } |
| |
| #[cfg(unix)] |
| fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
| match mode { |
| HeaderMode::Complete => { |
| self.set_mtime(meta.mtime() as u64); |
| self.set_uid(meta.uid() as u64); |
| self.set_gid(meta.gid() as u64); |
| self.set_mode(meta.mode() as u32); |
| } |
| HeaderMode::Deterministic => { |
| // We could in theory set the mtime to zero here, but not all tools seem to behave |
| // well when ingesting files with a 0 timestamp. |
| // For example, rust-lang/cargo#9512 shows that lldb doesn't ingest files with a |
| // zero timestamp correctly. |
| self.set_mtime(DETERMINISTIC_TIMESTAMP); |
| |
| self.set_uid(0); |
| self.set_gid(0); |
| |
| // Use a default umask value, but propagate the (user) execute bit. |
| let fs_mode = if meta.is_dir() || (0o100 & meta.mode() == 0o100) { |
| 0o755 |
| } else { |
| 0o644 |
| }; |
| self.set_mode(fs_mode); |
| } |
| } |
| |
| // Note that if we are a GNU header we *could* set atime/ctime, except |
| // the `tar` utility doesn't do that by default and it causes problems |
| // with 7-zip [1]. |
| // |
| // It's always possible to fill them out manually, so we just don't fill |
| // it out automatically here. |
| // |
| // [1]: https://github.com/alexcrichton/tar-rs/issues/70 |
| |
| // TODO: need to bind more file types |
| self.set_entry_type(entry_type(meta.mode())); |
| |
| fn entry_type(mode: u32) -> EntryType { |
| match mode as libc::mode_t & libc::S_IFMT { |
| libc::S_IFREG => EntryType::file(), |
| libc::S_IFLNK => EntryType::symlink(), |
| libc::S_IFCHR => EntryType::character_special(), |
| libc::S_IFBLK => EntryType::block_special(), |
| libc::S_IFDIR => EntryType::dir(), |
| libc::S_IFIFO => EntryType::fifo(), |
| _ => EntryType::new(b' '), |
| } |
| } |
| } |
| |
| #[cfg(windows)] |
| fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { |
| // There's no concept of a file mode on Windows, so do a best approximation here. |
| match mode { |
| HeaderMode::Complete => { |
| self.set_uid(0); |
| self.set_gid(0); |
| // The dates listed in tarballs are always seconds relative to |
| // January 1, 1970. On Windows, however, the timestamps are returned as |
| // dates relative to January 1, 1601 (in 100ns intervals), so we need to |
| // add in some offset for those dates. |
| let mtime = (meta.last_write_time() / (1_000_000_000 / 100)) - 11644473600; |
| self.set_mtime(mtime); |
| let fs_mode = { |
| const FILE_ATTRIBUTE_READONLY: u32 = 0x00000001; |
| let readonly = meta.file_attributes() & FILE_ATTRIBUTE_READONLY; |
| match (meta.is_dir(), readonly != 0) { |
| (true, false) => 0o755, |
| (true, true) => 0o555, |
| (false, false) => 0o644, |
| (false, true) => 0o444, |
| } |
| }; |
| self.set_mode(fs_mode); |
| } |
| HeaderMode::Deterministic => { |
| self.set_uid(0); |
| self.set_gid(0); |
| self.set_mtime(DETERMINISTIC_TIMESTAMP); // see above in unix |
| let fs_mode = if meta.is_dir() { 0o755 } else { 0o644 }; |
| self.set_mode(fs_mode); |
| } |
| } |
| |
| let ft = meta.file_type(); |
| self.set_entry_type(if ft.is_dir() { |
| EntryType::dir() |
| } else if ft.is_file() { |
| EntryType::file() |
| } else if ft.is_symlink() { |
| EntryType::symlink() |
| } else { |
| EntryType::new(b' ') |
| }); |
| } |
| |
| fn debug_fields(&self, b: &mut fmt::DebugStruct) { |
| if let Ok(entry_size) = self.entry_size() { |
| b.field("entry_size", &entry_size); |
| } |
| if let Ok(size) = self.size() { |
| b.field("size", &size); |
| } |
| if let Ok(path) = self.path() { |
| b.field("path", &path); |
| } |
| if let Ok(link_name) = self.link_name() { |
| b.field("link_name", &link_name); |
| } |
| if let Ok(mode) = self.mode() { |
| b.field("mode", &DebugAsOctal(mode)); |
| } |
| if let Ok(uid) = self.uid() { |
| b.field("uid", &uid); |
| } |
| if let Ok(gid) = self.gid() { |
| b.field("gid", &gid); |
| } |
| if let Ok(mtime) = self.mtime() { |
| b.field("mtime", &mtime); |
| } |
| if let Ok(username) = self.username() { |
| b.field("username", &username); |
| } |
| if let Ok(groupname) = self.groupname() { |
| b.field("groupname", &groupname); |
| } |
| if let Ok(device_major) = self.device_major() { |
| b.field("device_major", &device_major); |
| } |
| if let Ok(device_minor) = self.device_minor() { |
| b.field("device_minor", &device_minor); |
| } |
| if let Ok(cksum) = self.cksum() { |
| b.field("cksum", &cksum); |
| b.field("cksum_valid", &(cksum == self.calculate_cksum())); |
| } |
| } |
| } |
| |
| struct DebugAsOctal<T>(T); |
| |
| impl<T: fmt::Octal> fmt::Debug for DebugAsOctal<T> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| fmt::Octal::fmt(&self.0, f) |
| } |
| } |
| |
| unsafe fn cast<T, U>(a: &T) -> &U { |
| assert_eq!(mem::size_of_val(a), mem::size_of::<U>()); |
| assert_eq!(mem::align_of_val(a), mem::align_of::<U>()); |
| &*(a as *const T as *const U) |
| } |
| |
| unsafe fn cast_mut<T, U>(a: &mut T) -> &mut U { |
| assert_eq!(mem::size_of_val(a), mem::size_of::<U>()); |
| assert_eq!(mem::align_of_val(a), mem::align_of::<U>()); |
| &mut *(a as *mut T as *mut U) |
| } |
| |
| impl Clone for Header { |
| fn clone(&self) -> Header { |
| Header { bytes: self.bytes } |
| } |
| } |
| |
| impl fmt::Debug for Header { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| if let Some(me) = self.as_ustar() { |
| me.fmt(f) |
| } else if let Some(me) = self.as_gnu() { |
| me.fmt(f) |
| } else { |
| self.as_old().fmt(f) |
| } |
| } |
| } |
| |
| impl OldHeader { |
| /// Views this as a normal `Header` |
| pub fn as_header(&self) -> &Header { |
| unsafe { cast(self) } |
| } |
| |
| /// Views this as a normal `Header` |
| pub fn as_header_mut(&mut self) -> &mut Header { |
| unsafe { cast_mut(self) } |
| } |
| } |
| |
| impl fmt::Debug for OldHeader { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut f = f.debug_struct("OldHeader"); |
| self.as_header().debug_fields(&mut f); |
| f.finish() |
| } |
| } |
| |
| impl UstarHeader { |
| /// See `Header::path_bytes` |
| pub fn path_bytes(&self) -> Cow<[u8]> { |
| if self.prefix[0] == 0 && !self.name.contains(&b'\\') { |
| Cow::Borrowed(truncate(&self.name)) |
| } else { |
| let mut bytes = Vec::new(); |
| let prefix = truncate(&self.prefix); |
| if !prefix.is_empty() { |
| bytes.extend_from_slice(prefix); |
| bytes.push(b'/'); |
| } |
| bytes.extend_from_slice(truncate(&self.name)); |
| Cow::Owned(bytes) |
| } |
| } |
| |
| /// Gets the path in a "lossy" way, used for error reporting ONLY. |
| fn path_lossy(&self) -> String { |
| String::from_utf8_lossy(&self.path_bytes()).to_string() |
| } |
| |
| /// See `Header::set_path` |
| pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> { |
| self._set_path(p.as_ref()) |
| } |
| |
| fn _set_path(&mut self, path: &Path) -> io::Result<()> { |
| // This can probably be optimized quite a bit more, but for now just do |
| // something that's relatively easy and readable. |
| // |
| // First up, if the path fits within `self.name` then we just shove it |
| // in there. If not then we try to split it between some existing path |
| // components where it can fit in name/prefix. To do that we peel off |
| // enough until the path fits in `prefix`, then we try to put both |
| // halves into their destination. |
| let bytes = path2bytes(path)?; |
| let (maxnamelen, maxprefixlen) = (self.name.len(), self.prefix.len()); |
| if bytes.len() <= maxnamelen { |
| copy_path_into(&mut self.name, path, false).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting path for {}", err, self.path_lossy()), |
| ) |
| })?; |
| } else { |
| let mut prefix = path; |
| let mut prefixlen; |
| loop { |
| match prefix.parent() { |
| Some(parent) => prefix = parent, |
| None => { |
| return Err(other(&format!( |
| "path cannot be split to be inserted into archive: {}", |
| path.display() |
| ))); |
| } |
| } |
| prefixlen = path2bytes(prefix)?.len(); |
| if prefixlen <= maxprefixlen { |
| break; |
| } |
| } |
| copy_path_into(&mut self.prefix, prefix, false).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting path for {}", err, self.path_lossy()), |
| ) |
| })?; |
| let path = bytes2path(Cow::Borrowed(&bytes[prefixlen + 1..]))?; |
| copy_path_into(&mut self.name, &path, false).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting path for {}", err, self.path_lossy()), |
| ) |
| })?; |
| } |
| Ok(()) |
| } |
| |
| /// See `Header::username_bytes` |
| pub fn username_bytes(&self) -> &[u8] { |
| truncate(&self.uname) |
| } |
| |
| /// See `Header::set_username` |
| pub fn set_username(&mut self, name: &str) -> io::Result<()> { |
| copy_into(&mut self.uname, name.as_bytes()).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting username for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// See `Header::groupname_bytes` |
| pub fn groupname_bytes(&self) -> &[u8] { |
| truncate(&self.gname) |
| } |
| |
| /// See `Header::set_groupname` |
| pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { |
| copy_into(&mut self.gname, name.as_bytes()).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when setting groupname for {}", err, self.path_lossy()), |
| ) |
| }) |
| } |
| |
| /// See `Header::device_major` |
| pub fn device_major(&self) -> io::Result<u32> { |
| octal_from(&self.dev_major) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when getting device_major for {}", |
| err, |
| self.path_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::set_device_major` |
| pub fn set_device_major(&mut self, major: u32) { |
| octal_into(&mut self.dev_major, major); |
| } |
| |
| /// See `Header::device_minor` |
| pub fn device_minor(&self) -> io::Result<u32> { |
| octal_from(&self.dev_minor) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when getting device_minor for {}", |
| err, |
| self.path_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::set_device_minor` |
| pub fn set_device_minor(&mut self, minor: u32) { |
| octal_into(&mut self.dev_minor, minor); |
| } |
| |
| /// Views this as a normal `Header` |
| pub fn as_header(&self) -> &Header { |
| unsafe { cast(self) } |
| } |
| |
| /// Views this as a normal `Header` |
| pub fn as_header_mut(&mut self) -> &mut Header { |
| unsafe { cast_mut(self) } |
| } |
| } |
| |
| impl fmt::Debug for UstarHeader { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut f = f.debug_struct("UstarHeader"); |
| self.as_header().debug_fields(&mut f); |
| f.finish() |
| } |
| } |
| |
| impl GnuHeader { |
| /// See `Header::username_bytes` |
| pub fn username_bytes(&self) -> &[u8] { |
| truncate(&self.uname) |
| } |
| |
| /// Gets the fullname (group:user) in a "lossy" way, used for error reporting ONLY. |
| fn fullname_lossy(&self) -> String { |
| format!( |
| "{}:{}", |
| String::from_utf8_lossy(self.groupname_bytes()), |
| String::from_utf8_lossy(self.username_bytes()), |
| ) |
| } |
| |
| /// See `Header::set_username` |
| pub fn set_username(&mut self, name: &str) -> io::Result<()> { |
| copy_into(&mut self.uname, name.as_bytes()).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when setting username for {}", |
| err, |
| self.fullname_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::groupname_bytes` |
| pub fn groupname_bytes(&self) -> &[u8] { |
| truncate(&self.gname) |
| } |
| |
| /// See `Header::set_groupname` |
| pub fn set_groupname(&mut self, name: &str) -> io::Result<()> { |
| copy_into(&mut self.gname, name.as_bytes()).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when setting groupname for {}", |
| err, |
| self.fullname_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::device_major` |
| pub fn device_major(&self) -> io::Result<u32> { |
| octal_from(&self.dev_major) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when getting device_major for {}", |
| err, |
| self.fullname_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::set_device_major` |
| pub fn set_device_major(&mut self, major: u32) { |
| octal_into(&mut self.dev_major, major); |
| } |
| |
| /// See `Header::device_minor` |
| pub fn device_minor(&self) -> io::Result<u32> { |
| octal_from(&self.dev_minor) |
| .map(|u| u as u32) |
| .map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when getting device_minor for {}", |
| err, |
| self.fullname_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// See `Header::set_device_minor` |
| pub fn set_device_minor(&mut self, minor: u32) { |
| octal_into(&mut self.dev_minor, minor); |
| } |
| |
| /// Returns the last modification time in Unix time format |
| pub fn atime(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.atime).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting atime for {}", err, self.fullname_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `atime` provided into this header. |
| /// |
| /// Note that this time is typically a number of seconds passed since |
| /// January 1, 1970. |
| pub fn set_atime(&mut self, atime: u64) { |
| num_field_wrapper_into(&mut self.atime, atime); |
| } |
| |
| /// Returns the last modification time in Unix time format |
| pub fn ctime(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.ctime).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting ctime for {}", err, self.fullname_lossy()), |
| ) |
| }) |
| } |
| |
| /// Encodes the `ctime` provided into this header. |
| /// |
| /// Note that this time is typically a number of seconds passed since |
| /// January 1, 1970. |
| pub fn set_ctime(&mut self, ctime: u64) { |
| num_field_wrapper_into(&mut self.ctime, ctime); |
| } |
| |
| /// Returns the "real size" of the file this header represents. |
| /// |
| /// This is applicable for sparse files where the returned size here is the |
| /// size of the entire file after the sparse regions have been filled in. |
| pub fn real_size(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.realsize).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!( |
| "{} when getting real_size for {}", |
| err, |
| self.fullname_lossy() |
| ), |
| ) |
| }) |
| } |
| |
| /// Encodes the `real_size` provided into this header. |
| pub fn set_real_size(&mut self, real_size: u64) { |
| num_field_wrapper_into(&mut self.realsize, real_size); |
| } |
| |
| /// Indicates whether this header will be followed by additional |
| /// sparse-header records. |
| /// |
| /// Note that this is handled internally by this library, and is likely only |
| /// interesting if a `raw` iterator is being used. |
| pub fn is_extended(&self) -> bool { |
| self.isextended[0] == 1 |
| } |
| |
| /// Sets whether this header should be followed by additional sparse-header |
| /// records. |
| /// |
| /// To append a sparse [`std::fs::File`] to an archive, prefer using the |
| /// [`crate::Builder`] instead. |
| pub fn set_is_extended(&mut self, is_extended: bool) { |
| self.isextended[0] = if is_extended { 1 } else { 0 }; |
| } |
| |
| /// Views this as a normal `Header` |
| pub fn as_header(&self) -> &Header { |
| unsafe { cast(self) } |
| } |
| |
| /// Views this as a normal `Header` |
| pub fn as_header_mut(&mut self) -> &mut Header { |
| unsafe { cast_mut(self) } |
| } |
| } |
| |
| impl fmt::Debug for GnuHeader { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut f = f.debug_struct("GnuHeader"); |
| self.as_header().debug_fields(&mut f); |
| if let Ok(atime) = self.atime() { |
| f.field("atime", &atime); |
| } |
| if let Ok(ctime) = self.ctime() { |
| f.field("ctime", &ctime); |
| } |
| f.field("is_extended", &self.is_extended()) |
| .field("sparse", &DebugSparseHeaders(&self.sparse)) |
| .finish() |
| } |
| } |
| |
| struct DebugSparseHeaders<'a>(&'a [GnuSparseHeader]); |
| |
| impl<'a> fmt::Debug for DebugSparseHeaders<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut f = f.debug_list(); |
| for header in self.0 { |
| if !header.is_empty() { |
| f.entry(header); |
| } |
| } |
| f.finish() |
| } |
| } |
| |
| impl GnuSparseHeader { |
| /// Returns true if block is empty |
| pub fn is_empty(&self) -> bool { |
| self.offset[0] == 0 || self.numbytes[0] == 0 |
| } |
| |
| /// Offset of the block from the start of the file |
| /// |
| /// Returns `Err` for a malformed `offset` field. |
| pub fn offset(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.offset).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting offset from sparse header", err), |
| ) |
| }) |
| } |
| |
| /// Encodes the `offset` provided into this header. |
| pub fn set_offset(&mut self, offset: u64) { |
| num_field_wrapper_into(&mut self.offset, offset); |
| } |
| |
| /// Length of the block |
| /// |
| /// Returns `Err` for a malformed `numbytes` field. |
| pub fn length(&self) -> io::Result<u64> { |
| num_field_wrapper_from(&self.numbytes).map_err(|err| { |
| io::Error::new( |
| err.kind(), |
| format!("{} when getting length from sparse header", err), |
| ) |
| }) |
| } |
| |
| /// Encodes the `length` provided into this header. |
| pub fn set_length(&mut self, length: u64) { |
| num_field_wrapper_into(&mut self.numbytes, length); |
| } |
| } |
| |
| impl fmt::Debug for GnuSparseHeader { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| let mut f = f.debug_struct("GnuSparseHeader"); |
| if let Ok(offset) = self.offset() { |
| f.field("offset", &offset); |
| } |
| if let Ok(length) = self.length() { |
| f.field("length", &length); |
| } |
| f.finish() |
| } |
| } |
| |
| impl GnuExtSparseHeader { |
| /// Crates a new zero'd out sparse header entry. |
| pub fn new() -> GnuExtSparseHeader { |
| unsafe { mem::zeroed() } |
| } |
| |
| /// Returns a view into this header as a byte array. |
| pub fn as_bytes(&self) -> &[u8; 512] { |
| debug_assert_eq!(mem::size_of_val(self), 512); |
| unsafe { mem::transmute(self) } |
| } |
| |
| /// Returns a view into this header as a byte array. |
| pub fn as_mut_bytes(&mut self) -> &mut [u8; 512] { |
| debug_assert_eq!(mem::size_of_val(self), 512); |
| unsafe { mem::transmute(self) } |
| } |
| |
| /// Returns a slice of the underlying sparse headers. |
| /// |
| /// Some headers may represent empty chunks of both the offset and numbytes |
| /// fields are 0. |
| pub fn sparse(&self) -> &[GnuSparseHeader; 21] { |
| &self.sparse |
| } |
| |
| /// Same as `sparse` but mutable version. |
| pub fn sparse_mut(&mut self) -> &mut [GnuSparseHeader; 21] { |
| &mut self.sparse |
| } |
| |
| /// Indicates if another sparse header should be following this one. |
| pub fn is_extended(&self) -> bool { |
| self.isextended[0] == 1 |
| } |
| |
| /// Sets whether another sparse header should be following this one. |
| pub fn set_is_extended(&mut self, is_extended: bool) { |
| self.isextended[0] = if is_extended { 1 } else { 0 }; |
| } |
| } |
| |
| impl Default for GnuExtSparseHeader { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| fn octal_from(slice: &[u8]) -> io::Result<u64> { |
| let trun = truncate(slice); |
| let num = match str::from_utf8(trun) { |
| Ok(n) => n, |
| Err(_) => { |
| return Err(other(&format!( |
| "numeric field did not have utf-8 text: {}", |
| String::from_utf8_lossy(trun) |
| ))); |
| } |
| }; |
| match u64::from_str_radix(num.trim(), 8) { |
| Ok(n) => Ok(n), |
| Err(_) => Err(other(&format!("numeric field was not a number: {}", num))), |
| } |
| } |
| |
| fn octal_into<T: fmt::Octal>(dst: &mut [u8], val: T) { |
| let o = format!("{:o}", val); |
| let value = once(b'\0').chain(o.bytes().rev().chain(repeat(b'0'))); |
| for (slot, value) in dst.iter_mut().rev().zip(value) { |
| *slot = value; |
| } |
| } |
| |
| // Wrapper to figure out if we should fill the header field using tar's numeric |
| // extension (binary) or not (octal). |
| fn num_field_wrapper_into(dst: &mut [u8], src: u64) { |
| if src >= 8589934592 || (src >= 2097152 && dst.len() == 8) { |
| numeric_extended_into(dst, src); |
| } else { |
| octal_into(dst, src); |
| } |
| } |
| |
| // Wrapper to figure out if we should read the header field in binary (numeric |
| // extension) or octal (standard encoding). |
| fn num_field_wrapper_from(src: &[u8]) -> io::Result<u64> { |
| if src[0] & 0x80 != 0 { |
| Ok(numeric_extended_from(src)) |
| } else { |
| octal_from(src) |
| } |
| } |
| |
| // When writing numeric fields with is the extended form, the high bit of the |
| // first byte is set to 1 and the remainder of the field is treated as binary |
| // instead of octal ascii. |
| // This handles writing u64 to 8 (uid, gid) or 12 (size, *time) bytes array. |
| fn numeric_extended_into(dst: &mut [u8], src: u64) { |
| let len: usize = dst.len(); |
| for (slot, val) in dst.iter_mut().zip( |
| repeat(0) |
| .take(len - 8) // to zero init extra bytes |
| .chain((0..8).rev().map(|x| ((src >> (8 * x)) & 0xff) as u8)), |
| ) { |
| *slot = val; |
| } |
| dst[0] |= 0x80; |
| } |
| |
| fn numeric_extended_from(src: &[u8]) -> u64 { |
| let mut dst: u64 = 0; |
| let mut b_to_skip = 1; |
| if src.len() == 8 { |
| // read first byte without extension flag bit |
| dst = (src[0] ^ 0x80) as u64; |
| } else { |
| // only read last 8 bytes |
| b_to_skip = src.len() - 8; |
| } |
| for byte in src.iter().skip(b_to_skip) { |
| dst <<= 8; |
| dst |= *byte as u64; |
| } |
| dst |
| } |
| |
| fn truncate(slice: &[u8]) -> &[u8] { |
| match slice.iter().position(|i| *i == 0) { |
| Some(i) => &slice[..i], |
| None => slice, |
| } |
| } |
| |
| /// Copies `bytes` into the `slot` provided, returning an error if the `bytes` |
| /// array is too long or if it contains any nul bytes. |
| fn copy_into(slot: &mut [u8], bytes: &[u8]) -> io::Result<()> { |
| if bytes.len() > slot.len() { |
| Err(other("provided value is too long")) |
| } else if bytes.iter().any(|b| *b == 0) { |
| Err(other("provided value contains a nul byte")) |
| } else { |
| for (slot, val) in slot.iter_mut().zip(bytes.iter().chain(Some(&0))) { |
| *slot = *val; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Copies `path` into the `slot` provided |
| /// |
| /// Returns an error if: |
| /// |
| /// * the path is too long to fit |
| /// * a nul byte was found |
| /// * an invalid path component is encountered (e.g. a root path or parent dir) |
| /// * the path itself is empty |
| fn copy_path_into(mut slot: &mut [u8], path: &Path, is_link_name: bool) -> io::Result<()> { |
| let mut emitted = false; |
| let mut needs_slash = false; |
| for component in path.components() { |
| let bytes = path2bytes(Path::new(component.as_os_str()))?; |
| match (component, is_link_name) { |
| (Component::Prefix(..), false) | (Component::RootDir, false) => { |
| return Err(other("paths in archives must be relative")); |
| } |
| (Component::ParentDir, false) => { |
| return Err(other("paths in archives must not have `..`")); |
| } |
| // Allow "./" as the path |
| (Component::CurDir, false) if path.components().count() == 1 => {} |
| (Component::CurDir, false) => continue, |
| (Component::Normal(_), _) | (_, true) => {} |
| }; |
| if needs_slash { |
| copy(&mut slot, b"/")?; |
| } |
| if bytes.contains(&b'/') { |
| if let Component::Normal(..) = component { |
| return Err(other("path component in archive cannot contain `/`")); |
| } |
| } |
| copy(&mut slot, &*bytes)?; |
| if &*bytes != b"/" { |
| needs_slash = true; |
| } |
| emitted = true; |
| } |
| if !emitted { |
| return Err(other("paths in archives must have at least one component")); |
| } |
| if ends_with_slash(path) { |
| copy(&mut slot, &[b'/'])?; |
| } |
| return Ok(()); |
| |
| fn copy(slot: &mut &mut [u8], bytes: &[u8]) -> io::Result<()> { |
| copy_into(*slot, bytes)?; |
| let tmp = mem::replace(slot, &mut []); |
| *slot = &mut tmp[bytes.len()..]; |
| Ok(()) |
| } |
| } |
| |
| #[cfg(target_arch = "wasm32")] |
| fn ends_with_slash(p: &Path) -> bool { |
| p.to_string_lossy().ends_with('/') |
| } |
| |
| #[cfg(windows)] |
| fn ends_with_slash(p: &Path) -> bool { |
| let last = p.as_os_str().encode_wide().last(); |
| last == Some(b'/' as u16) || last == Some(b'\\' as u16) |
| } |
| |
| #[cfg(unix)] |
| fn ends_with_slash(p: &Path) -> bool { |
| p.as_os_str().as_bytes().ends_with(&[b'/']) |
| } |
| |
| #[cfg(any(windows, target_arch = "wasm32"))] |
| pub fn path2bytes(p: &Path) -> io::Result<Cow<[u8]>> { |
| p.as_os_str() |
| .to_str() |
| .map(|s| s.as_bytes()) |
| .ok_or_else(|| other(&format!("path {} was not valid Unicode", p.display()))) |
| .map(|bytes| { |
| if bytes.contains(&b'\\') { |
| // Normalize to Unix-style path separators |
| let mut bytes = bytes.to_owned(); |
| for b in &mut bytes { |
| if *b == b'\\' { |
| *b = b'/'; |
| } |
| } |
| Cow::Owned(bytes) |
| } else { |
| Cow::Borrowed(bytes) |
| } |
| }) |
| } |
| |
| #[cfg(unix)] |
| /// On unix this will never fail |
| pub fn path2bytes(p: &Path) -> io::Result<Cow<[u8]>> { |
| Ok(p.as_os_str().as_bytes()).map(Cow::Borrowed) |
| } |
| |
| #[cfg(windows)] |
| /// On windows we cannot accept non-Unicode bytes because it |
| /// is impossible to convert it to UTF-16. |
| pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { |
| return match bytes { |
| Cow::Borrowed(bytes) => { |
| let s = str::from_utf8(bytes).map_err(|_| not_unicode(bytes))?; |
| Ok(Cow::Borrowed(Path::new(s))) |
| } |
| Cow::Owned(bytes) => { |
| let s = String::from_utf8(bytes).map_err(|uerr| not_unicode(&uerr.into_bytes()))?; |
| Ok(Cow::Owned(PathBuf::from(s))) |
| } |
| }; |
| |
| fn not_unicode(v: &[u8]) -> io::Error { |
| other(&format!( |
| "only Unicode paths are supported on Windows: {}", |
| String::from_utf8_lossy(v) |
| )) |
| } |
| } |
| |
| #[cfg(unix)] |
| /// On unix this operation can never fail. |
| pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { |
| use std::ffi::{OsStr, OsString}; |
| |
| Ok(match bytes { |
| Cow::Borrowed(bytes) => Cow::Borrowed(Path::new(OsStr::from_bytes(bytes))), |
| Cow::Owned(bytes) => Cow::Owned(PathBuf::from(OsString::from_vec(bytes))), |
| }) |
| } |
| |
| #[cfg(target_arch = "wasm32")] |
| pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> { |
| Ok(match bytes { |
| Cow::Borrowed(bytes) => { |
| Cow::Borrowed({ Path::new(str::from_utf8(bytes).map_err(invalid_utf8)?) }) |
| } |
| Cow::Owned(bytes) => { |
| Cow::Owned({ PathBuf::from(String::from_utf8(bytes).map_err(invalid_utf8)?) }) |
| } |
| }) |
| } |
| |
| #[cfg(target_arch = "wasm32")] |
| fn invalid_utf8<T>(_: T) -> io::Error { |
| io::Error::new(io::ErrorKind::InvalidData, "Invalid utf-8") |
| } |