1648 lines
53 KiB
Rust
1648 lines
53 KiB
Rust
#[cfg(unix)]
|
|
use std::os::unix::prelude::*;
|
|
#[cfg(windows)]
|
|
use std::os::windows::prelude::*;
|
|
#[cfg(target_os = "mikros")]
|
|
use std::os::mikros::prelude::*;
|
|
|
|
use std::borrow::Cow;
|
|
use std::fmt;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::iter;
|
|
use std::iter::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;
|
|
|
|
/// 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; 4],
|
|
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; 21],
|
|
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> {
|
|
octal_from(&self.realsize).map_err(|err| {
|
|
io::Error::new(
|
|
err.kind(),
|
|
format!(
|
|
"{} when getting real_size for {}",
|
|
err,
|
|
self.fullname_lossy()
|
|
),
|
|
)
|
|
})
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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> {
|
|
octal_from(&self.offset).map_err(|err| {
|
|
io::Error::new(
|
|
err.kind(),
|
|
format!("{} when getting offset from sparse header", err),
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Length of the block
|
|
///
|
|
/// Returns `Err` for a malformed `numbytes` field.
|
|
pub fn length(&self) -> io::Result<u64> {
|
|
octal_from(&self.numbytes).map_err(|err| {
|
|
io::Error::new(
|
|
err.kind(),
|
|
format!("{} when getting length from sparse header", err),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
/// Indicates if another sparse header should be following this one.
|
|
pub fn is_extended(&self) -> bool {
|
|
self.isextended[0] == 1
|
|
}
|
|
}
|
|
|
|
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 = o.bytes().rev().chain(repeat(b'0'));
|
|
for (slot, value) in dst.iter_mut().rev().skip(1).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(any(unix, target_os = "mikros"))]
|
|
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(any(unix, target_os = "mikros"))]
|
|
/// 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(any(unix, target_os = "mikros"))]
|
|
/// 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")
|
|
}
|