Ok(Directory::new(self.inner.open_root_ref().await?))
}
+ /// Set a cache for the goodbye tables to reduce random disk access.
pub fn set_goodbye_table_cache<C>(&mut self, cache: Option<C>)
where
C: Cache<u64, [GoodbyeItem]> + Send + Sync + 'static,
}
impl<T: Clone + ReadAt> Accessor<T> {
+ /// Open the "root" directory entry of this pxar archive.
pub async fn open_root(&self) -> io::Result<Directory<T>> {
Ok(Directory::new(self.inner.open_root().await?))
}
self.inner.content_range()
}
+ /// Get the file's contents.
pub async fn contents(&self) -> io::Result<FileContents<T>> {
Ok(FileContents {
inner: self.inner.contents().await?,
})
}
+ /// Convenience shortcut for when only the metadata contained in the [`Entry`] struct is of
+ /// interest.
#[inline]
pub fn into_entry(self) -> Entry {
self.inner.into_entry()
}
+ /// Access the contained [`Entry`] for metadata access.
#[inline]
pub fn entry(&self) -> &Entry {
&self.inner.entry()
self.inner.count()
}
+ /// Get the next directory entry.
pub async fn next(&mut self) -> Option<io::Result<DirEntry<'a, T>>> {
match self.inner.next().await {
Ok(Some(inner)) => Some(Ok(DirEntry { inner })),
//! Random access for PXAR files.
+#![deny(missing_docs)]
+
use std::ffi::{OsStr, OsString};
use std::future::Future;
use std::io;
/// Range information used for unsafe raw random access:
#[derive(Clone, Debug)]
pub struct EntryRangeInfo {
+ /// Offset to the `FILENAME` header.
pub filename_header_offset: Option<u64>,
+ /// Byte range spanning an entry in a pxar archive.
pub entry_range: Range<u64>,
}
impl EntryRangeInfo {
+ /// Shortcut to create the "toplevel" range info without file name header offset.
pub fn toplevel(entry_range: Range<u64>) -> Self {
Self {
filename_header_offset: None,
buffer: Vec<u8>,
}
+/// A `SeqRead` adapter for a specific range inside another reader, with a temporary buffer due
+/// to lifetime constraints.
#[doc(hidden)]
pub struct SeqReadAtAdapter<T> {
input: T,
}
impl<T: ReadAt> SeqReadAtAdapter<T> {
+ /// Create a new `SeqRead` adapter given a range.
pub fn new(input: T, range: Range<u64>) -> Self {
if range.end < range.start {
panic!("BAD SEQ READ AT ADAPTER");
+//! Async `ReadAt` trait.
+
use std::any::Any;
use std::future::Future;
use std::io;
/// Like Poll but Pending yields a value.
pub enum MaybeReady<T, F> {
+ /// Same as [`Poll::Ready`].
Ready(T),
+
+ /// Same as [`Poll::Pending`], but contains a "cookie" identifying the ongoing operation.
+ /// Without this value, it is impossible to make further progress on the operation.
Pending(F),
}
/// Random access read implementation.
pub trait ReadAt {
+ /// Begin a read operation.
+ ///
+ /// Contrary to tokio and future's `AsyncRead` traits, this implements positional reads and
+ /// therefore allows multiple operations to run simultaneously. In order to accomplish this,
+ /// the result of this call includes a [`ReadAtOperation`] "cookie" identifying the particular
+ /// read operation. This is necessary, since with an async runtime multiple such calls can come
+ /// from the same thread and even the same task.
+ ///
+ /// It is possible that this operation succeeds immediately, in which case
+ /// `MaybeRead::Ready(Ok(bytes))` is returned containing the number of bytes read.
+ ///
+ /// If the operation takes longer to complete, returns `MaybeReady::Pending(cookie)`, and the
+ /// current taks will be notified via `cx.waker()` when progress can be made. Once that
+ /// happens, [`poll_complete`](ReadAt::poll_complete) should be called using the returned
+ /// `cookie`.
+ ///
+ /// On error, returns `MaybeRead::Ready(Err(err))`.
fn start_read_at<'a>(
self: Pin<&'a Self>,
cx: &mut Context,
offset: u64,
) -> MaybeReady<io::Result<usize>, ReadAtOperation<'a>>;
+ /// Attempt to complete a previously started read operation identified by the provided
+ /// [`ReadAtOperation`].
+ ///
+ /// If the read operation is finished, returns `MaybeReady::Ready(Ok(bytes))` containing the
+ /// number of bytes read.
+ ///
+ /// If the operation is not yet completed, returns `MaybeReady::Pending(cookie)`, returning the
+ /// (possibly modified) operation cookie again to be reused for the next call to
+ /// `poll_complete`.
+ ///
+ /// On error, returns `MaybeRead::Ready(Err(err))`.
fn poll_complete<'a>(
self: Pin<&'a Self>,
op: ReadAtOperation<'a>,
) -> MaybeReady<io::Result<usize>, ReadAtOperation<'a>>;
}
+/// A "cookie" identifying a particular [`ReadAt`] operation.
pub struct ReadAtOperation<'a> {
+ /// The implementor of the [`ReadAt`] trait is responsible for what type of data is contained
+ /// in here.
+ ///
+ /// Note that the contained data needs to implement `Drop` so that dropping the "cookie"
+ /// cancels the operation correctly.
+ ///
+ /// Apart from this field, the struct only contains phantom data.
pub cookie: Box<dyn Any + Send + Sync>,
_marker: PhantomData<&'a mut [u8]>,
}
impl<'a> ReadAtOperation<'a> {
+ /// Create a new [`ReadAtOperation`].
pub fn new<T: Into<Box<dyn Any + Send + Sync>>>(cookie: T) -> Self {
Self {
cookie: cookie.into(),
// awaitable helper:
+/// [`ReadAt`] extension trait, akin to `AsyncReadExt`.
pub trait ReadAtExt: ReadAt {
+ /// Equivalent to `async fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>`.
fn read_at<'a>(&'a self, buf: &'a mut [u8], offset: u64) -> ReadAtImpl<'a, Self>
where
Self: Sized,
)?))
}
+ /// Set a cache for the goodbye tables to reduce random disk access.
pub fn set_goodbye_table_cache<C>(&mut self, cache: Option<C>)
where
C: Cache<u64, [GoodbyeItem]> + Send + Sync + 'static,
}
impl<T: Clone + ReadAt> Accessor<T> {
+ /// Open the "root" directory entry of this pxar archive.
pub fn open_root(&self) -> io::Result<Directory<T>> {
Ok(Directory::new(poll_result_once(self.inner.open_root())?))
}
}
}
-/// Adapter for FileExt readers.
+/// Adapter for `FileExt` readers to make it usable via the `ReadAt` trait.
#[derive(Clone)]
pub struct FileReader<T> {
inner: T,
}
impl<T: FileExt> FileReader<T> {
+ /// Wrap a regular reader to access it via the `ReadAt` trait.
pub fn new(inner: T) -> Self {
Self { inner }
}
}
}
-/// Adapter for `Arc` or `Rc` to FileExt readers.
+/// Adapter for `Arc` or `Rc` to `FileExt` readers to make it usable via the `ReadAt` trait.
#[derive(Clone)]
pub struct FileRefReader<T: Clone> {
inner: T,
}
impl<T: Clone> FileRefReader<T> {
+ /// Wrap a reference to a `FileExt` reader.
pub fn new(inner: T) -> Self {
Self { inner }
}
self.inner.content_range()
}
+ /// Get the file's contents.
pub fn contents(&self) -> io::Result<FileContents<T>> {
Ok(FileContents {
inner: poll_result_once(self.inner.contents())?,
})
}
+ /// Convenience shortcut for when only the metadata contained in the [`Entry`] struct is of
+ /// interest.
#[inline]
pub fn into_entry(self) -> Entry {
self.inner.into_entry()
}
+ /// Access the contained [`Entry`] for metadata access.
#[inline]
pub fn entry(&self) -> &Entry {
&self.inner.entry()
//! Heap](https://en.wikipedia.org/wiki/Binary_heap) gives a short
//! intro howto store binary trees using an array.
+#![deny(missing_docs)]
+
use std::cmp::Ordering;
#[allow(clippy::many_single_char_names)]
//!
//! This is the implementation used by both the synchronous and async pxar wrappers.
+#![deny(missing_docs)]
+
use std::convert::TryFrom;
use std::ffi::OsString;
use std::io;
}
}
+/// Reader for file contents inside a pxar archive.
pub struct Contents<'a, T: SeqRead> {
input: &'a mut T,
at: &'a mut u64,
}
impl<'a, T: SeqRead> Contents<'a, T> {
- pub fn new(input: &'a mut T, at: &'a mut u64, len: u64) -> Self {
+ fn new(input: &'a mut T, at: &'a mut u64, len: u64) -> Self {
Self { input, at, len }
}
Decoder::new(StandardReader::new(input))
}
+ /// Get a direct reference to the reader contained inside the contained [`StandardReader`].
pub fn input(&mut self) -> &T {
self.inner.input().inner()
}
}
impl<T: io::Read> StandardReader<T> {
+ /// Make a new [`StandardReader`].
pub fn new(inner: T) -> Self {
Self { inner }
}
+ /// Get an immutable reference to the contained reader.
pub fn inner(&self) -> &T {
&self.inner
}
}
}
+/// Reader for file contents inside a pxar archive.
pub struct Contents<'a, T: SeqRead> {
inner: decoder::Contents<'a, T>,
}
}
}
+/// This is a "file" inside a pxar archive, to which the initially declared amount of data should
+/// be written.
+///
+/// Writing more or less than the designated amount is an error and will cause the produced archive
+/// to be broken.
#[repr(transparent)]
pub struct File<'a, S: SeqWrite> {
inner: encoder::FileImpl<'a, S>,
use crate::encoder::SeqWrite;
+ /// Pxar encoder write adapter for [`AsyncWrite`](tokio::io::AsyncWrite).
pub struct TokioWriter<T> {
inner: Option<T>,
}
impl<T: tokio::io::AsyncWrite> TokioWriter<T> {
+ /// Make a new [`SeqWrite`] wrapper for an object implementing
+ /// [`AsyncWrite`](tokio::io::AsyncWrite).
pub fn new(inner: T) -> Self {
Self { inner: Some(inner) }
}
//!
//! This is the implementation used by both the synchronous and async pxar wrappers.
+#![deny(missing_docs)]
+
use std::io;
use std::mem::{forget, size_of, size_of_val, take};
use std::os::unix::ffi::OsStrExt;
pub struct LinkOffset(u64);
impl LinkOffset {
+ /// Get the raw byte offset of this link.
#[inline]
pub fn raw(self) -> u64 {
self.0
/// synchronous wrapper and for both `tokio` and `future` `AsyncWrite` types in the asynchronous
/// wrapper.
pub trait SeqWrite {
+ /// Attempt to perform a sequential write to the file. On success, the number of written bytes
+ /// is returned as `Poll::Ready(Ok(bytes))`.
+ ///
+ /// If writing is not yet possible, `Poll::Pending` is returned and the current task will be
+ /// notified via the `cx.waker()` when writing becomes possible.
fn poll_seq_write(
self: Pin<&mut Self>,
cx: &mut Context,
buf: &[u8],
) -> Poll<io::Result<usize>>;
+ /// Attempt to flush the output, ensuring that all buffered data reaches the destination.
+ ///
+ /// On success, returns `Poll::Ready(Ok(()))`.
+ ///
+ /// If flushing cannot complete immediately, `Poll::Pending` is returned and the current task
+ /// will be notified via `cx.waker()` when progress can be made.
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>>;
}
}
/// Writer for a file object in a directory.
-pub struct FileImpl<'a, S: SeqWrite> {
+pub(crate) struct FileImpl<'a, S: SeqWrite> {
output: &'a mut S,
/// This file's `GoodbyeItem`. FIXME: We currently don't touch this, can we just push it
}
}
+/// This is a "file" inside a pxar archive, to which the initially declared amount of data should
+/// be written.
+///
+/// Writing more or less than the designated amount is an error and will cause the produced archive
+/// to be broken.
#[repr(transparent)]
pub struct File<'a, S: SeqWrite> {
inner: encoder::FileImpl<'a, S>,
}
}
-/// Pxar encoder write adapter for `std::io::Write`.
+/// Pxar encoder write adapter for [`Write`](std::io::Write).
pub struct StandardWriter<T> {
inner: Option<T>,
}
impl<T: io::Write> StandardWriter<T> {
+ /// Make a new [`SeqWrite`] wrapper for an object implementing [`Write`](std::io::Write).
pub fn new(inner: T) -> Self {
Self { inner: Some(inner) }
}
pub projid: u64,
}
+/// An entry in the "goodbye table" in a pxar archive. This is required for random access inside
+/// pxar archives.
#[derive(Clone, Debug, Endian)]
#[repr(C)]
pub struct GoodbyeItem {
}
impl GoodbyeItem {
+ /// Create a new [`GoodbyeItem`] by hashing the name, and storing the hash along with the
+ /// offset and size information.
pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
let hash = hash_filename(name);
Self { hash, offset, size }
}
}
+/// Hash a file name for use in the goodbye table.
pub fn hash_filename(name: &[u8]) -> u64 {
use std::hash::Hasher;
hasher.finish()
}
+/// Returns `true` if the path consists only of [`Normal`](std::path::Component::Normal)
+/// components.
pub fn path_is_legal_component(path: &Path) -> bool {
let mut components = path.components();
match components.next() {
components.next().is_none()
}
+/// Assert sure the path consists only of [`Normal`](std::path::Component::Normal) components.
+///
+/// Returns an [`io::Error`](std::io::Error) of type [`Other`](std::io::ErrorKind::Other) if that's
+/// not the case.
pub fn check_file_name(path: &Path) -> io::Result<()> {
if !path_is_legal_component(path) {
io_bail!("invalid file name in archive: {:?}", path);
}
}
+/// A builder for the file [`Metadata`] stored in pxar archives.
pub struct MetadataBuilder {
inner: Metadata,
}
impl MetadataBuilder {
+ /// Create a new [`MetadataBuilder`] given an initial type/mode bitset.
pub const fn new(type_and_mode: u64) -> Self {
Self {
inner: Metadata {
}
}
+ /// Build the [`Metadata`].
pub fn build(self) -> Metadata {
self.inner
}
}
impl Acl {
+ /// Shortcut to check if all fields of this [`Acl`] entry are empty.
pub fn is_empty(&self) -> bool {
self.users.is_empty()
&& self.groups.is_empty()
Fifo,
/// Regular file.
- File { size: u64, offset: Option<u64> },
+ File {
+ /// The file size in bytes.
+ size: u64,
+
+ /// The file's byte offset inside the archive, if available.
+ offset: Option<u64>,
+ },
/// Directory entry. When iterating through an archive, the contents follow next.
Directory,