diff --git a/Cargo.toml b/Cargo.toml index 8bda26b..141c5c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] members = [ - 'server' + 'server', + 'libarchive' ] diff --git a/libarchive/Cargo.toml b/libarchive/Cargo.toml new file mode 100644 index 0000000..31c9bae --- /dev/null +++ b/libarchive/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "libarchive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = ">= 0.2.0" +libarchive3-sys = "0.1" diff --git a/libarchive/src/archive.rs b/libarchive/src/archive.rs new file mode 100644 index 0000000..8f281b5 --- /dev/null +++ b/libarchive/src/archive.rs @@ -0,0 +1,400 @@ +use std::ffi::{CStr, CString}; +use std::str; +use std::{default::Default, path::Path}; + +use crate::error::ErrCode; +use libarchive3_sys::ffi; + +#[derive(Debug, PartialEq, Eq)] +pub enum ReadCompression { + All, + Bzip2, + Compress, + Gzip, + Lzip, + Lzma, + None, + Program(String), + Rpm, + Uu, + Xz, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ReadFormat { + SevenZip, + All, + Ar, + Cab, + Cpio, + Empty, + Gnutar, + Iso9660, + Lha, + Mtree, + Rar, + Raw, + Tar, + Xar, + Zip, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ReadFilter { + All, + Bzip2, + Compress, + Gzip, + Grzip, + Lrzip, + Lzip, + Lzma, + Lzop, + None, + Program(String), + ProgramSignature(String, Option ()>, usize), + Rpm, + Uu, + Xz, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum WriteFormat { + SevenZip, + ArBsd, + ArSvr4, + Cpio, + CpioNewc, + Gnutar, + Iso9660, + Mtree, + MtreeClassic, + Pax, + PaxRestricted, + Shar, + SharDump, + Ustar, + V7tar, + Xar, + Zip, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum WriteFilter { + B64Encode, + Bzip2, + Compress, + Grzip, + Gzip, + Lrzip, + Lzip, + Lzma, + Lzop, + None, + Program(String), + UuEncode, + Xz, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum FileType { + BlockDevice, + SymbolicLink, + Socket, + CharacterDevice, + Directory, + NamedPipe, + Mount, + RegularFile, +} + +/// The trait representing a handle to a libarchive archive. +/// +/// # Safety +/// Implementors of this trait **must** call the function to free the handle before they go out of +/// scope. I'd recommend using the `Drop` trait for this. +pub trait Handle { + /// Returns a *const to the interal `archive` c struct + /// + /// # Safety + /// This pointer dangles once the `archive` has been deallocated. + unsafe fn handle(&self) -> *const ffi::Struct_archive; + + /// Returns a *mut to the interal `archive` c struct + /// + /// # Safety + /// This pointer dangles once the `archive` has been deallocated. + unsafe fn handle_mut(&mut self) -> *mut ffi::Struct_archive; + + /// Get the error code from the most recent libarchive function call + fn err_code(&self) -> ErrCode { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + let code = unsafe { ffi::archive_errno(self.handle() as *mut _) }; + ErrCode(code) + } + + /// Get a str containing the error message from the most recent libarchive function call + fn err_msg(&self) -> &str { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + let c_str = unsafe { CStr::from_ptr(ffi::archive_error_string(self.handle() as *mut _)) }; + c_str.to_str().unwrap() + } +} + +/// The trait representing an `archive_entry` +/// +/// # Safety +/// See [`Handle`](trait.Handle.html) +pub trait Entry { + /// Gives a *const to the internal `archive_entry` c struct + /// + /// # Safety + /// The pointer returned here points into the `archive` struct. As lifetimes are not capable of + /// expressing self-referential structs (yet?) this must all be unsafe. This pointer dangles + /// if the `archive` struct held by the implementor of `Handle` is deallocated. + /// + /// Most (all?) of the functions in libarchive take `T*`, not `const T*`, so this `*const` will + /// probably have to be cast to a `*mut` to use it. Do not pass that `*mut` to a function that may + /// modify it, as that is UB. + unsafe fn entry(&self) -> *const ffi::Struct_archive_entry; + + /// Gives a *mut to the internal `archive_entry` c struct. + /// + /// # Safety + /// The pointer returned here points into the archive struct. As lifetimes are not capable of + /// expressing self-referential structs (yet?) this must all be unsafe. This pointer dangles + /// if the `archive` struct held by the implementor of `Handle` is deallocated. + unsafe fn entry_mut(&mut self) -> *mut ffi::Struct_archive_entry; + + /// Get the filetype of the entry. + fn filetype(&self) -> FileType { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + match unsafe { ffi::archive_entry_filetype(self.entry() as *mut _) } as u32 { + ffi::AE_IFBLK => FileType::BlockDevice, + ffi::AE_IFCHR => FileType::CharacterDevice, + ffi::AE_IFLNK => FileType::SymbolicLink, + ffi::AE_IFDIR => FileType::Directory, + ffi::AE_IFIFO => FileType::NamedPipe, + ffi::AE_IFMT => FileType::Mount, + ffi::AE_IFREG => FileType::RegularFile, + ffi::AE_IFSOCK => FileType::Socket, + code => unreachable!("undefined filetype: {}", code), + } + } + + /// Get the location of a hardlink, if it exists + fn hardlink(&self) -> Option<&str> { + let c_str: &CStr = unsafe { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + let ptr = ffi::archive_entry_hardlink(self.entry() as *mut _); + if ptr.is_null() { + return None; + } + CStr::from_ptr(ptr) + }; + c_str.to_str().ok() + //let buf: &[u8] = c_str.to_bytes(); + //Some(str::from_utf8(buf).unwrap()) + } + + /// Get the pathname of the file the entry refers to + fn pathname(&self) -> &str { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + let c_str: &CStr = + unsafe { CStr::from_ptr(ffi::archive_entry_pathname(self.entry() as *mut _)) }; + let buf: &[u8] = c_str.to_bytes(); + str::from_utf8(buf).unwrap() + } + + /// Get the mode of the file + fn mode(&self) -> u32 { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { ffi::archive_entry_mode(self.entry() as *mut _) } + } + + /// Get the id of the group that owns the file + fn gid(&self) -> i64 { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { ffi::archive_entry_gid(self.entry() as *mut _) } + } + + /// Get the id of the user that owns the file + fn uid(&self) -> i64 { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { ffi::archive_entry_uid(self.entry() as *mut _) } + } + + /// Get the `mtime` of the file (The time it was last modified) as a unix timestamp + fn time(&self) -> i64 { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { ffi::archive_entry_mtime(self.entry() as *mut _) } + } + + /// Get the size of the file, in bytes + fn size(&self) -> i64 { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { ffi::archive_entry_size(self.entry() as *mut _) } + } + + /// Get the destination of a symlink, if one exists + fn symlink(&self) -> Option<&str> { + match self.filetype() { + FileType::SymbolicLink => { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + unsafe { CStr::from_ptr(ffi::archive_entry_symlink(self.entry() as *mut _)) } + .to_str() + .ok() + } + _ => None, + } + } + + /// Set the filetype of the file. + fn set_filetype(&mut self, file_type: FileType) { + let file_type = match file_type { + FileType::BlockDevice => ffi::AE_IFBLK, + FileType::CharacterDevice => ffi::AE_IFCHR, + FileType::SymbolicLink => ffi::AE_IFLNK, + FileType::Directory => ffi::AE_IFDIR, + FileType::NamedPipe => ffi::AE_IFIFO, + FileType::Mount => ffi::AE_IFMT, + FileType::RegularFile => ffi::AE_IFREG, + FileType::Socket => ffi::AE_IFSOCK, + }; + unsafe { + ffi::archive_entry_set_filetype(self.entry_mut(), file_type); + } + } + + /// Set the destination of a link + fn set_link>(&mut self, path: P) { + unsafe { + let c_str = CString::new(path.as_ref().to_str().unwrap()).unwrap(); + ffi::archive_entry_set_link(self.entry_mut(), c_str.as_ptr()); + } + } + + /// Set the pathname of the file + fn set_pathname>(&mut self, path: P) { + unsafe { + let c_str = CString::new(path.as_ref().to_str().unwrap()).unwrap(); + ffi::archive_entry_set_pathname(self.entry_mut(), c_str.as_ptr()); + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ExtractOption { + // The user and group IDs should be set on the restored file. By default, the user and group + // IDs are not restored. + Owner, + // Full permissions (including SGID, SUID, and sticky bits) should be restored exactly as + // specified, without obeying the current umask. Note that SUID and SGID bits can only be + // restored if the user and group ID of the object on disk are correct. If + // `ExtractOption::Owner` is not specified, then SUID and SGID bits will only be restored if + // the default user and group IDs of newly-created objects on disk happen to match those + // specified in the archive entry. By default, only basic permissions are restored, and umask + // is obeyed. + Permissions, + // The timestamps (mtime, ctime, and atime) should be restored. By default, they are ignored. + // Note that restoring of atime is not currently supported. + Time, + // Existing files on disk will not be overwritten. By default, existing regular files are + // truncated and overwritten; existing directories will have their permissions updated; other + // pre-existing objects are unlinked and recreated from scratch. + NoOverwrite, + // Existing files on disk will be unlinked before any attempt to create them. In some cases, + // this can prove to be a significant performance improvement. By default, existing files are + // truncated and rewritten, but the file is not recreated. In particular, the default behavior + // does not break existing hard links. + Unlink, + // Attempt to restore ACLs. By default, extended ACLs are ignored. + ACL, + // Attempt to restore extended file flags. By default, file flags are ignored. + FFlags, + // Attempt to restore POSIX.1e extended attributes. By default, they are ignored. + XAttr, + // Refuse to extract any object whose final location would be altered by a symlink on disk. + // This is intended to help guard against a variety of mischief caused by archives that + // (deliberately or otherwise) extract files outside of the current directory. The default is + // not to perform this check. If ARCHIVE_EXTRACT_UNLINK is specified together with this option, + // the library will remove any intermediate symlinks it finds and return an error only if such + // symlink could not be removed. + SecureSymlinks, + // Refuse to extract a path that contains a `..` element anywhere within it. The default is to + // not refuse such paths. Note that paths ending in `..` always cause an error, regardless of + // this flag. + SecureNoDotDot, + // Default: Create parent directories as needed + NoAutoDir, + // Default: Overwrite files, even if one on disk is newer + NoOverwriteNewer, + // Scan data for blocks of NUL bytes and try to recreate them with holes. This results in + // sparse files, independent of whether the archive format supports or uses them. + Sparse, + // Default: Do not restore Mac extended metadata + // This has no effect except on Mac OS + MacMetadata, + // Default: Use HFS+ compression if it was compressed + // This has no effect except on Mac OS v10.6 or later + NoHFSCompression, + // Default: Do not use HFS+ compression if it was not compressed + // This has no effect except on Mac OS v10.6 or later + HFSCompressionForced, + // Default: Do not reject entries with absolute paths + SecureNoAbsolutePaths, + // Default: Do not clear no-change flags when unlinking object + ClearNoChangeFFlags, +} + +pub struct ExtractOptions { + pub flags: i32, +} + +impl ExtractOptions { + pub fn new() -> Self { + ExtractOptions::default() + } + + pub fn add(&mut self, opt: ExtractOption) -> &mut Self { + let flag = match opt { + ExtractOption::Owner => ffi::ARCHIVE_EXTRACT_OWNER, + ExtractOption::Permissions => ffi::ARCHIVE_EXTRACT_PERM, + ExtractOption::Time => ffi::ARCHIVE_EXTRACT_TIME, + ExtractOption::NoOverwrite => ffi::ARCHIVE_EXTRACT_NO_OVERWRITE, + ExtractOption::Unlink => ffi::ARCHIVE_EXTRACT_UNLINK, + ExtractOption::ACL => ffi::ARCHIVE_EXTRACT_ACL, + ExtractOption::FFlags => ffi::ARCHIVE_EXTRACT_FFLAGS, + ExtractOption::XAttr => ffi::ARCHIVE_EXTRACT_XATTR, + ExtractOption::SecureSymlinks => ffi::ARCHIVE_EXTRACT_SECURE_SYMLINKS, + ExtractOption::SecureNoDotDot => ffi::ARCHIVE_EXTRACT_SECURE_NODOTDOT, + ExtractOption::NoAutoDir => ffi::ARCHIVE_EXTRACT_NO_AUTODIR, + ExtractOption::NoOverwriteNewer => ffi::ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER, + ExtractOption::Sparse => ffi::ARCHIVE_EXTRACT_SPARSE, + ExtractOption::MacMetadata => ffi::ARCHIVE_EXTRACT_MAC_METADATA, + ExtractOption::NoHFSCompression => ffi::ARCHIVE_EXTRACT_NO_HFS_COMPRESSION, + ExtractOption::HFSCompressionForced => ffi::ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED, + ExtractOption::SecureNoAbsolutePaths => ffi::ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS, + ExtractOption::ClearNoChangeFFlags => ffi::ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS, + }; + self.flags |= flag; + self + } +} + +impl Default for ExtractOptions { + fn default() -> ExtractOptions { + ExtractOptions { flags: 0 } + } +} diff --git a/libarchive/src/error.rs b/libarchive/src/error.rs new file mode 100644 index 0000000..2243865 --- /dev/null +++ b/libarchive/src/error.rs @@ -0,0 +1,50 @@ +use crate::archive; +use std::error; +use std::fmt; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct ErrCode(pub i32); + +impl fmt::Display for ErrCode { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{}", self.0) + } +} + +#[derive(Debug)] +pub enum ArchiveError { + Consumed, + HeaderPosition, + Sys(ErrCode, String), +} + +impl error::Error for ArchiveError {} + +impl fmt::Display for ArchiveError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + ArchiveError::Consumed => write!(fmt, "Builder already consumed"), + ArchiveError::HeaderPosition => write!(fmt, "Header position expected to be 0"), + ArchiveError::Sys(ref code, ref msg) => { + write!(fmt, "{} (libarchive err_code={})", msg, code) + } + } + } +} + +impl<'a> From<&'a dyn archive::Handle> for ArchiveError { + fn from(handle: &'a dyn archive::Handle) -> ArchiveError { + ArchiveError::Sys(handle.err_code(), handle.err_msg().to_owned()) + } +} + +impl<'a> From<&'a dyn archive::Handle> for Result<()> { + fn from(handle: &'a dyn archive::Handle) -> Result<()> { + match handle.err_code() { + ErrCode(0) => Ok(()), + _ => Err(ArchiveError::from(handle)), + } + } +} diff --git a/libarchive/src/lib.rs b/libarchive/src/lib.rs new file mode 100644 index 0000000..7ab9fbb --- /dev/null +++ b/libarchive/src/lib.rs @@ -0,0 +1,20 @@ +pub mod archive; +pub mod error; +pub mod read; + +pub use error::Result; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/libarchive/src/read/builder.rs b/libarchive/src/read/builder.rs new file mode 100644 index 0000000..50cbd59 --- /dev/null +++ b/libarchive/src/read/builder.rs @@ -0,0 +1,178 @@ +use crate::archive::{Handle, ReadCompression, ReadFilter, ReadFormat}; +use libarchive3_sys::ffi; +use crate::error::{ArchiveError, Result}; +use std::ffi::CString; +use std::mem; +use std::path::Path; +use super::file::ArchiveFile; +use crate::read::Archive; + +/// A struct that allows configuration of the reader before it is created. +pub struct Builder { + handle: *mut ffi::Struct_archive, + consumed: bool, +} + +impl Builder { + pub fn new() -> Self { + Builder::default() + } + + /// Enable support for a given compression method. + pub fn support_compression(&mut self, compression: ReadCompression) -> Result<()> { + #[rustfmt::skip] + let result = match compression { + ReadCompression::All => unsafe { ffi::archive_read_support_compression_all(self.handle_mut()) }, + ReadCompression::Bzip2 => unsafe { ffi::archive_read_support_compression_bzip2(self.handle_mut()) }, + ReadCompression::Compress => unsafe { ffi::archive_read_support_compression_compress(self.handle_mut()) }, + ReadCompression::Gzip => unsafe { ffi::archive_read_support_compression_gzip(self.handle_mut()) }, + ReadCompression::Lzip => unsafe { ffi::archive_read_support_compression_lzip(self.handle_mut()) }, + ReadCompression::Lzma => unsafe { ffi::archive_read_support_compression_lzma(self.handle_mut()) }, + ReadCompression::None => unsafe { ffi::archive_read_support_compression_none(self.handle_mut()) }, + ReadCompression::Rpm => unsafe { ffi::archive_read_support_compression_rpm(self.handle_mut()) }, + ReadCompression::Uu => unsafe { ffi::archive_read_support_compression_uu(self.handle_mut()) }, + ReadCompression::Xz => unsafe { ffi::archive_read_support_compression_xz(self.handle_mut()) }, + ReadCompression::Program(prog) => { + let c_prog = CString::new(prog).unwrap(); + unsafe { + ffi::archive_read_support_compression_program( + self.handle_mut(), + c_prog.as_ptr(), + ) + } + } + }; + match result { + ffi::ARCHIVE_OK => Ok(()), + _ => Result::from(self as &dyn Handle), + } + } + + /// Enable support for a given filter + pub fn support_filter(&mut self, filter: ReadFilter) -> Result<()> { + #[rustfmt::skip] + let result = match filter { + ReadFilter::All => unsafe { ffi::archive_read_support_filter_all(self.handle_mut()) }, + ReadFilter::Bzip2 => unsafe { ffi::archive_read_support_filter_bzip2(self.handle_mut()) }, + ReadFilter::Compress => unsafe { ffi::archive_read_support_filter_compress(self.handle_mut()) }, + ReadFilter::Grzip => unsafe { ffi::archive_read_support_filter_grzip(self.handle_mut()) }, + ReadFilter::Gzip => unsafe { ffi::archive_read_support_filter_gzip(self.handle_mut()) }, + ReadFilter::Lrzip => unsafe { ffi::archive_read_support_filter_lrzip(self.handle_mut()) }, + ReadFilter::Lzip => unsafe { ffi::archive_read_support_filter_lzip(self.handle_mut()) }, + ReadFilter::Lzma => unsafe { ffi::archive_read_support_filter_lzma(self.handle_mut()) }, + ReadFilter::Lzop => unsafe { ffi::archive_read_support_filter_lzop(self.handle_mut()) }, + ReadFilter::None => unsafe { ffi::archive_read_support_filter_none(self.handle_mut()) }, + ReadFilter::Rpm => unsafe { ffi::archive_read_support_filter_rpm(self.handle_mut()) }, + ReadFilter::Uu => unsafe { ffi::archive_read_support_filter_uu(self.handle_mut()) }, + ReadFilter::Xz => unsafe { ffi::archive_read_support_filter_xz(self.handle_mut()) }, + ReadFilter::Program(prog) => { + let c_prog = CString::new(prog).unwrap(); + unsafe { + ffi::archive_read_support_filter_program(self.handle_mut(), c_prog.as_ptr()) + } + } + ReadFilter::ProgramSignature(prog, cb, size) => { + let c_prog = CString::new(prog).unwrap(); + unsafe { + ffi::archive_read_support_filter_program_signature( + self.handle_mut(), + c_prog.as_ptr(), + mem::transmute(cb), + size, + ) + } + } + }; + match result { + ffi::ARCHIVE_OK => Ok(()), + _ => Result::from(self as &dyn Handle), + } + } + + /// Enable support for a given format. + pub fn support_format(&mut self, format: ReadFormat) -> Result<()> { + // SAFETY: Casting to *mut because these c functions take T* not const T*. They do not + // modify the pointer, so this is sound. + #[rustfmt::skip] + let result = match format { + ReadFormat::SevenZip => unsafe { ffi::archive_read_support_format_7zip(self.handle_mut()) }, + ReadFormat::All => unsafe { ffi::archive_read_support_format_all(self.handle_mut()) }, + ReadFormat::Ar => unsafe { ffi::archive_read_support_format_ar(self.handle_mut()) }, + ReadFormat::Cab => unsafe { ffi::archive_read_support_format_cab(self.handle_mut()) }, + ReadFormat::Cpio => unsafe { ffi::archive_read_support_format_cpio(self.handle_mut()) }, + ReadFormat::Empty => unsafe { ffi::archive_read_support_format_empty(self.handle_mut()) }, + ReadFormat::Gnutar => unsafe { ffi::archive_read_support_format_gnutar(self.handle_mut()) }, + ReadFormat::Iso9660 => unsafe { ffi::archive_read_support_format_iso9660(self.handle_mut()) }, + ReadFormat::Lha => unsafe { ffi::archive_read_support_format_lha(self.handle_mut()) }, + ReadFormat::Mtree => unsafe { ffi::archive_read_support_format_mtree(self.handle_mut()) }, + ReadFormat::Rar => unsafe { ffi::archive_read_support_format_rar(self.handle_mut()) }, + ReadFormat::Raw => unsafe { ffi::archive_read_support_format_raw(self.handle_mut()) }, + ReadFormat::Tar => unsafe { ffi::archive_read_support_format_tar(self.handle_mut()) }, + ReadFormat::Xar => unsafe { ffi::archive_read_support_format_xar(self.handle_mut()) }, + ReadFormat::Zip => unsafe { ffi::archive_read_support_format_zip(self.handle_mut()) }, + }; + match result { + ffi::ARCHIVE_OK => Ok(()), + _ => Result::from(self as &dyn Handle), + } + } + + /// Open a file with this builder, consuming it and returning a `FileReader` + pub fn open_file>(self, file: T) -> Result { + self.check_consumed()?; + ArchiveFile::open(self, file) + } + + /// Open a stream with this builder, consuming it and returning a `StreamReader` + // pub fn open_stream(self, src: T) -> Result { + // self.check_consumed()?; + // StreamReader::open(self, src) + // } + + pub fn check_consumed(&self) -> Result<()> { + if self.consumed { + Err(ArchiveError::Consumed) + } else { + Ok(()) + } + } + + pub fn consume(&mut self) { + self.consumed = true; + } +} + +impl Handle for Builder { + unsafe fn handle(&self) -> *const ffi::Struct_archive { + self.handle as *const _ + } + + unsafe fn handle_mut(&mut self) -> *mut ffi::Struct_archive { + self.handle + } +} + +impl Drop for Builder { + fn drop(&mut self) { + if !self.consumed { + unsafe { + ffi::archive_read_free(self.handle); + } + } + } +} + +impl Default for Builder { + fn default() -> Self { + unsafe { + let handle = ffi::archive_read_new(); + if handle.is_null() { + panic!("Allocation error"); + } + Builder { + handle, + consumed: false, + } + } + } +} diff --git a/libarchive/src/read/file.rs b/libarchive/src/read/file.rs new file mode 100644 index 0000000..ebf2619 --- /dev/null +++ b/libarchive/src/read/file.rs @@ -0,0 +1,54 @@ +use libarchive3_sys::ffi; +use super::builder::Builder; +use crate::read::{Handle, Archive}; +use std::ffi::CString; +use std::path::Path; +use crate::error::ArchiveError; + +const BLOCK_SIZE: usize = 10240; + +pub struct ArchiveFile { + handle: *mut ffi::Struct_archive, + // entry: ReaderEntry, +} + +impl Handle for ArchiveFile { + unsafe fn handle(&self) -> *const ffi::Struct_archive { + self.handle as *const _ + } + + unsafe fn handle_mut(&mut self) -> *mut ffi::Struct_archive { + self.handle + } +} + +impl Drop for ArchiveFile { + fn drop(&mut self) { + unsafe { + ffi::archive_read_free(self.handle_mut()); + } + } +} + +impl Archive for ArchiveFile { + fn new(handle: *mut ffi::Struct_archive) -> Self { + Self { + handle + } + } + + fn open>(mut builder: Builder, path: P) -> crate::Result { + builder.check_consumed()?; + let c_file = CString::new(path.as_ref().to_string_lossy().as_bytes()).unwrap(); + unsafe { + match ffi::archive_read_open_filename(builder.handle_mut(), c_file.as_ptr(), BLOCK_SIZE) + { + ffi::ARCHIVE_OK => { + builder.consume(); + Ok(Self::new(builder.handle_mut())) + } + _ => Err(ArchiveError::from(&builder as &dyn Handle)), + } + } + } +} diff --git a/libarchive/src/read/mod.rs b/libarchive/src/read/mod.rs new file mode 100644 index 0000000..ea2565a --- /dev/null +++ b/libarchive/src/read/mod.rs @@ -0,0 +1,15 @@ +mod builder; +mod file; + +use crate::archive::Handle; +use builder::Builder; +use std::path::Path; +use libarchive3_sys::ffi; + +// Represents a read view of an archive +pub trait Archive: Handle + Sized { + fn new(handle: *mut ffi::Struct_archive) -> Self; + + fn open>(builder: Builder, path: P) -> crate::Result; + // entries +} diff --git a/server/Cargo.toml b/server/Cargo.toml index e2b56b9..f64b642 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] axum = "0.6.18" futures = "0.3.28" +libarchive = { path = "../libarchive" } tokio = { version = "1.29.1", features = ["full"] } tokio-util = { version = "0.7.8", features = ["io"] } tower = { version = "0.4.13", features = ["make"] }