diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 829dd1c..0000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.51" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51136d2..ce8ffe7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,22 +6,53 @@ on: jobs: - check: - name: Test + check_tokio: + name: Test tokio runs-on: ${{ matrix.os }} strategy: matrix: - build: [msrv, stable, beta, nightly, macos, windows] + build: [msrv, stable, nightly, macos, windows] include: - build: msrv os: ubuntu-latest - rust: 1.63 + rust: 1.71 - build: stable os: ubuntu-latest rust: stable - - build: beta + - build: nightly + os: ubuntu-latest + rust: nightly + - build: macos + os: macos-latest + rust: stable + - build: windows + os: windows-latest + rust: stable + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + toolchain: ${{ matrix.rust }} + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features runtime-tokio + + check_async: + name: Test async-std + runs-on: ${{ matrix.os }} + strategy: + matrix: + build: [msrv, stable, nightly, macos, windows] + include: + - build: msrv os: ubuntu-latest - rust: beta + rust: 1.71 + - build: stable + os: ubuntu-latest + rust: stable - build: nightly os: ubuntu-latest rust: nightly @@ -41,6 +72,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --no-default-features --features runtime-async-std fmt: name: Check formatting @@ -58,8 +90,8 @@ jobs: command: fmt args: --all -- --check - lint: - name: Clippy Linting + lint_tokio: + name: Clippy Linting tokio runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -72,8 +104,23 @@ jobs: - uses: actions-rs/cargo@v1 with: command: clippy - args: --all + args: --no-default-features --features runtime-tokio + lint_async: + name: Clippy Linting async-std + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: --no-default-features --features runtime-async-std docs: name: Check docs runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 68f071f..6d681d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" edition = "2018" exclude = ["tests/archives/*"] resolver = "2" +rust-version = "1.71" description = """ A Rust implementation of an async TAR file reader and writer. This library does not @@ -20,21 +21,26 @@ contents are never required to be entirely resident in memory all at once. """ [dependencies] -async-std = { version = "1.12.0", features = ["unstable"] } +async-std = { version = "1.12", optional = true } +tokio = { version = "1", optional = true, features = ["fs", "io-util"] } filetime = "0.2.8" -pin-project = "1.0.8" +tokio-stream = { version = "0.1.11", features = ["fs"], optional = true } +futures-core = "0.3.25" [dev-dependencies] -async-std = { version = "1.12.0", features = ["unstable", "attributes"] } +async-std = { version = "1.12", features = ["unstable", "attributes"] } +tokio = { version = "1", features = ["rt", "io-std", "rt-multi-thread", "macros"] } static_assertions = "1.1.0" tempfile = "3" [target."cfg(unix)".dependencies] libc = "0.2" -xattr = { version = "0.2", optional = true } +xattr = { version = "1.6", optional = true } [target.'cfg(target_os = "redox")'.dependencies] -redox_syscall = "0.2" +redox_syscall = "0.5" [features] -default = [ "xattr" ] +default = ["xattr", "runtime-async-std"] +runtime-async-std = ["async-std"] +runtime-tokio = ["tokio", "tokio-stream"] diff --git a/README.md b/README.md index 2f645e3..a576dce 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ > Based on the great [tar-rs](https://github.com/alexcrichton/tar-rs). +## Features + +- `runtime-async-std`: enabled by default, makes this crate compatible with `async-std` +- `runtime-tokio`: makes this crate compatible with `tokio`. + ## Reading an archive ```rust,no_run @@ -81,7 +86,7 @@ fn main() { # MSRV -Minimal stable rust version: 1.63 +Minimal stable rust version: 1.71 *An increase to the MSRV is accompanied by a minor version bump* diff --git a/examples/extract_file.rs b/examples/extract_file.rs index 283423d..1c909d3 100644 --- a/examples/extract_file.rs +++ b/examples/extract_file.rs @@ -4,28 +4,43 @@ //! name as the first argument provided, and then prints the contents of that //! file to stdout. -extern crate async_tar; - +#[cfg(feature = "runtime-async-std")] use async_std::{ io::{copy, stdin, stdout}, path::Path, - prelude::*, + stream::StreamExt, }; use std::env::args_os; +#[cfg(feature = "runtime-tokio")] +use std::path::Path; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{copy, stdin, stdout}; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; use async_tar::Archive; -fn main() { - async_std::task::block_on(async { - let first_arg = args_os().nth(1).unwrap(); - let filename = Path::new(&first_arg); - let ar = Archive::new(stdin()); - let mut entries = ar.entries().unwrap(); - while let Some(file) = entries.next().await { - let mut f = file.unwrap(); - if f.path().unwrap() == filename { - copy(&mut f, &mut stdout()).await.unwrap(); - } +async fn inner_main() { + let first_arg = args_os().nth(1).unwrap(); + let filename = Path::new(&first_arg); + let ar = Archive::new(stdin()); + let mut entries = ar.entries().unwrap(); + while let Some(file) = entries.next().await { + let mut f = file.unwrap(); + if f.path().unwrap() == filename { + copy(&mut f, &mut stdout()).await.unwrap(); } - }); + } +} + +#[cfg(feature = "runtime-async-std")] +fn main() { + async_std::task::block_on(inner_main()); +} + +#[cfg(feature = "runtime-tokio")] +fn main() { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(inner_main()); } diff --git a/examples/list.rs b/examples/list.rs index d92fa10..180ab07 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -2,19 +2,32 @@ //! //! Takes a tarball on stdin and prints out all of the entries inside. -extern crate async_tar; - -use async_std::{io::stdin, prelude::*}; +#[cfg(feature = "runtime-async-std")] +use async_std::{io::stdin, stream::StreamExt}; +#[cfg(feature = "runtime-tokio")] +use tokio::io::stdin; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; use async_tar::Archive; +async fn inner_main() { + let ar = Archive::new(stdin()); + let mut entries = ar.entries().unwrap(); + while let Some(file) = entries.next().await { + let f = file.unwrap(); + println!("{}", f.path().unwrap().display()); + } +} + +#[cfg(feature = "runtime-async-std")] +fn main() { + async_std::task::block_on(inner_main()); +} + +#[cfg(feature = "runtime-tokio")] fn main() { - async_std::task::block_on(async { - let ar = Archive::new(stdin()); - let mut entries = ar.entries().unwrap(); - while let Some(file) = entries.next().await { - let f = file.unwrap(); - println!("{}", f.path().unwrap().display()); - } - }); + tokio::runtime::Runtime::new() + .unwrap() + .block_on(inner_main()); } diff --git a/examples/raw_list.rs b/examples/raw_list.rs index 860d345..51ed812 100644 --- a/examples/raw_list.rs +++ b/examples/raw_list.rs @@ -2,52 +2,65 @@ //! //! Takes a tarball on stdin and prints out all of the entries inside. -extern crate async_tar; - +#[cfg(feature = "runtime-async-std")] use async_std::{io::stdin, prelude::*}; +#[cfg(feature = "runtime-tokio")] +use tokio::io::stdin; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; use async_tar::Archive; -fn main() { - async_std::task::block_on(async { - let ar = Archive::new(stdin()); - let mut i = 0; - let mut entries = ar.entries_raw().unwrap(); - while let Some(file) = entries.next().await { - println!("-------------------------- Entry {}", i); - let mut f = file.unwrap(); - println!("path: {}", f.path().unwrap().display()); - println!("size: {}", f.header().size().unwrap()); - println!("entry size: {}", f.header().entry_size().unwrap()); - println!("link name: {:?}", f.link_name().unwrap()); - println!("file type: {:#x}", f.header().entry_type().as_byte()); - println!("mode: {:#o}", f.header().mode().unwrap()); - println!("uid: {}", f.header().uid().unwrap()); - println!("gid: {}", f.header().gid().unwrap()); - println!("mtime: {}", f.header().mtime().unwrap()); - println!("username: {:?}", f.header().username().unwrap()); - println!("groupname: {:?}", f.header().groupname().unwrap()); +async fn inner_main() { + let ar = Archive::new(stdin()); + let mut i = 0; + let mut entries = ar.entries_raw().unwrap(); + while let Some(file) = entries.next().await { + println!("-------------------------- Entry {}", i); + let mut f = file.unwrap(); + println!("path: {}", f.path().unwrap().display()); + println!("size: {}", f.header().size().unwrap()); + println!("entry size: {}", f.header().entry_size().unwrap()); + println!("link name: {:?}", f.link_name().unwrap()); + println!("file type: {:#x}", f.header().entry_type().as_byte()); + println!("mode: {:#o}", f.header().mode().unwrap()); + println!("uid: {}", f.header().uid().unwrap()); + println!("gid: {}", f.header().gid().unwrap()); + println!("mtime: {}", f.header().mtime().unwrap()); + println!("username: {:?}", f.header().username().unwrap()); + println!("groupname: {:?}", f.header().groupname().unwrap()); - if f.header().as_ustar().is_some() { - println!("kind: UStar"); - } else if f.header().as_gnu().is_some() { - println!("kind: GNU"); - } else { - println!("kind: normal"); - } + if f.header().as_ustar().is_some() { + println!("kind: UStar"); + } else if f.header().as_gnu().is_some() { + println!("kind: GNU"); + } else { + println!("kind: normal"); + } - if let Ok(Some(extensions)) = f.pax_extensions().await { - println!("pax extensions:"); - for e in extensions { - let e = e.unwrap(); - println!( - "\t{:?} = {:?}", - String::from_utf8_lossy(e.key_bytes()), - String::from_utf8_lossy(e.value_bytes()) - ); - } + if let Ok(Some(extensions)) = f.pax_extensions().await { + println!("pax extensions:"); + for e in extensions { + let e = e.unwrap(); + println!( + "\t{:?} = {:?}", + String::from_utf8_lossy(e.key_bytes()), + String::from_utf8_lossy(e.value_bytes()) + ); } - i += 1; } - }); + i += 1; + } +} + +#[cfg(feature = "runtime-async-std")] +fn main() { + async_std::task::block_on(inner_main()); +} + +#[cfg(feature = "runtime-tokio")] +fn main() { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(inner_main()); } diff --git a/examples/write.rs b/examples/write.rs index 3b6c5c6..c76fd1e 100644 --- a/examples/write.rs +++ b/examples/write.rs @@ -1,16 +1,27 @@ -extern crate async_tar; - +#[cfg(feature = "runtime-async-std")] use async_std::fs::File; use async_tar::Builder; +#[cfg(feature = "runtime-tokio")] +use tokio::fs::File; + +async fn inner_main() { + let file = File::create("foo.tar").await.unwrap(); + let mut a = Builder::new(file); + + a.append_path("README.md").await.unwrap(); + a.append_file("lib.rs", &mut File::open("src/lib.rs").await.unwrap()) + .await + .unwrap(); +} +#[cfg(feature = "runtime-async-std")] fn main() { - async_std::task::block_on(async { - let file = File::create("foo.tar").await.unwrap(); - let mut a = Builder::new(file); + async_std::task::block_on(inner_main()); +} - a.append_path("README.md").await.unwrap(); - a.append_file("lib.rs", &mut File::open("src/lib.rs").await.unwrap()) - .await - .unwrap(); - }); +#[cfg(feature = "runtime-tokio")] +fn main() { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(inner_main()); } diff --git a/src/archive.rs b/src/archive.rs index de5f807..91e6f70 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,25 +1,34 @@ +#[cfg(feature = "runtime-tokio")] +use std::path::Path; use std::{ cmp, pin::Pin, sync::{Arc, Mutex}, + task::{Context, Poll}, }; +#[cfg(feature = "runtime-async-std")] use async_std::{ fs, io, io::prelude::*, path::Path, - prelude::*, - stream::Stream, - task::{Context, Poll}, + stream::{Stream, StreamExt}, }; -use pin_project::pin_project; +use futures_core::ready; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + fs, + io::{self, AsyncRead as Read, AsyncReadExt}, +}; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::{Stream, StreamExt}; use crate::{ - Entry, GnuExtSparseHeader, GnuSparseHeader, Header, entry::{EntryFields, EntryIo}, error::TarError, - other, + fs_canonicalize, other, pax::pax_extensions, + symlink_metadata, Entry, GnuExtSparseHeader, GnuSparseHeader, Header, }; /// A top-level representation of an archive file. @@ -38,7 +47,6 @@ impl Clone for Archive { } } -#[pin_project] #[derive(Debug)] pub struct ArchiveInner { pos: u64, @@ -46,7 +54,6 @@ pub struct ArchiveInner { preserve_permissions: bool, preserve_mtime: bool, ignore_zeros: bool, - #[pin] obj: R, } @@ -213,7 +220,8 @@ impl Archive { /// /// # Examples /// - /// ```no_run + #[cfg_attr(feature = "runtime-async-std", doc = "```no_run")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs::File; @@ -229,7 +237,7 @@ impl Archive { let mut pinned = Pin::new(&mut entries); let dst = dst.as_ref(); - if dst.symlink_metadata().await.is_err() { + if symlink_metadata(dst).await.is_err() { fs::create_dir_all(&dst) .await .map_err(|e| TarError::new(&format!("failed to create `{}`", dst.display()), e))?; @@ -240,8 +248,8 @@ impl Archive { // extended-length path with a 32,767 character limit. Otherwise all // unpacked paths over 260 characters will fail on creation with a // NotFound exception. - let dst = &dst - .canonicalize() + + let dst = &fs_canonicalize(dst) .await .unwrap_or_else(|_| dst.to_path_buf()); @@ -275,7 +283,6 @@ struct State { } /// Stream of `Entry`s. -#[pin_project] #[derive(Debug)] pub struct Entries { archive: Archive, @@ -288,7 +295,7 @@ pub struct Entries { macro_rules! ready_opt_err { ($val:expr) => { - match async_std::task::ready!($val) { + match ready!($val) { Some(Ok(val)) => val, Some(Err(err)) => return Poll::Ready(Some(Err(err))), None => return Poll::Ready(None), @@ -298,7 +305,7 @@ macro_rules! ready_opt_err { macro_rules! ready_err { ($val:expr) => { - match async_std::task::ready!($val) { + match ready!($val) { Ok(val) => val, Err(err) => return Poll::Ready(Some(Err(err))), } @@ -308,91 +315,99 @@ macro_rules! ready_err { impl Stream for Entries { type Item = io::Result>>; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let mut this = self.project(); + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { + let Self { + current, + fields, + gnu_longname, + gnu_longlink, + archive, + pax_extensions, + .. + } = &mut *self; let State { next, current_header, current_header_pos, - pax_extensions, + pax_extensions: current_pax_extensions, .. - } = &mut this.current; + } = current; - let fields = if let Some(fields) = this.fields.as_mut() { + let new_fields = if let Some(fields) = fields.as_mut() { fields } else { - *this.fields = Some(EntryFields::from(ready_opt_err!(poll_next_raw( - this.archive, + *fields = Some(EntryFields::from(ready_opt_err!(poll_next_raw( + archive, next, current_header, current_header_pos, - pax_extensions.as_deref(), + current_pax_extensions.as_deref(), cx )))); continue; }; let is_recognized_header = - fields.header.as_gnu().is_some() || fields.header.as_ustar().is_some(); - if is_recognized_header && fields.header.entry_type().is_gnu_longname() { - if this.gnu_longname.is_some() { + new_fields.header.as_gnu().is_some() || new_fields.header.as_ustar().is_some(); + if is_recognized_header && new_fields.header.entry_type().is_gnu_longname() { + if gnu_longname.is_some() { return Poll::Ready(Some(Err(other( "two long name entries describing \ the same member", )))); } - *this.gnu_longname = Some(ready_err!(Pin::new(fields).poll_read_all(cx))); - *this.fields = None; + *gnu_longname = Some(ready_err!(Pin::new(new_fields).poll_read_all(cx))); + *fields = None; continue; } - if is_recognized_header && fields.header.entry_type().is_gnu_longlink() { - if this.gnu_longlink.is_some() { + if is_recognized_header && new_fields.header.entry_type().is_gnu_longlink() { + if gnu_longlink.is_some() { return Poll::Ready(Some(Err(other( "two long name entries describing \ the same member", )))); } - *this.gnu_longlink = Some(ready_err!(Pin::new(fields).poll_read_all(cx))); - *this.fields = None; + *gnu_longlink = Some(ready_err!(Pin::new(new_fields).poll_read_all(cx))); + *fields = None; continue; } - if is_recognized_header && fields.header.entry_type().is_pax_local_extensions() { - if this.pax_extensions.is_some() { + if is_recognized_header && new_fields.header.entry_type().is_pax_local_extensions() { + if pax_extensions.is_some() { return Poll::Ready(Some(Err(other( "two pax extensions entries describing \ the same member", )))); } - *this.pax_extensions = Some(ready_err!(Pin::new(fields).poll_read_all(cx))); - this.current.pax_extensions = this.pax_extensions.clone(); - *this.fields = None; + *pax_extensions = Some(ready_err!(Pin::new(new_fields).poll_read_all(cx))); + *current_pax_extensions = pax_extensions.clone(); + *fields = None; continue; } - fields.long_pathname = this.gnu_longname.take(); - fields.long_linkname = this.gnu_longlink.take(); - fields.pax_extensions = this.pax_extensions.take(); + new_fields.long_pathname = gnu_longname.take(); + new_fields.long_linkname = gnu_longlink.take(); + new_fields.pax_extensions = pax_extensions.take(); let State { next, current_header_pos, current_ext, .. - } = &mut this.current; + } = current; ready_err!(poll_parse_sparse_header( - this.archive, + archive, next, current_ext, current_header_pos, - fields, + new_fields, cx )); - return Poll::Ready(Some(Ok(this.fields.take().unwrap().into_entry()))); + return Poll::Ready(Some(Ok(fields.take().unwrap().into_entry()))); } } } @@ -428,7 +443,7 @@ fn poll_next_raw( // Seek to the start of the next header in the archive if current_header.is_none() { let delta = *next - archive.inner.lock().unwrap().pos; - match async_std::task::ready!(poll_skip(archive.clone(), cx, delta)) { + match ready!(poll_skip(archive.clone(), cx, delta)) { Ok(_) => {} Err(err) => return Poll::Ready(Some(Err(err))), } @@ -440,7 +455,7 @@ fn poll_next_raw( let header = current_header.as_mut().unwrap(); // EOF is an indicator that we are at the end of the archive. - match async_std::task::ready!(poll_try_read_all( + match ready!(poll_try_read_all( archive.clone(), cx, header.as_mut_bytes(), @@ -633,7 +648,7 @@ fn poll_parse_sparse_header( blocks", )); } else if cur < off { - let block = io::repeat(0).take(off - cur); + let block = io::repeat(0).take((off - cur) as _); data.push(EntryIo::Pad(block)); } cur = off @@ -662,7 +677,7 @@ fn poll_parse_sparse_header( let ext = current_ext.as_mut().unwrap(); while ext.is_extended() { - match async_std::task::ready!(poll_try_read_all( + match ready!(poll_try_read_all( archive.clone(), cx, ext.as_mut_bytes(), @@ -697,6 +712,7 @@ fn poll_parse_sparse_header( Poll::Ready(Ok(())) } +#[cfg(feature = "runtime-async-std")] impl Read for Archive { fn poll_read( self: Pin<&mut Self>, @@ -707,7 +723,7 @@ impl Read for Archive { let mut inner = Pin::new(&mut *lock); let r = Pin::new(&mut inner.obj); - let res = async_std::task::ready!(r.poll_read(cx, into)); + let res = ready!(r.poll_read(cx, into)); match res { Ok(i) => { inner.pos += i as u64; @@ -718,10 +734,34 @@ impl Read for Archive { } } +#[cfg(feature = "runtime-tokio")] +impl Read for Archive { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + into: &mut tokio::io::ReadBuf, + ) -> Poll> { + let mut lock = self.inner.lock().unwrap(); + let mut inner = Pin::new(&mut *lock); + let r = Pin::new(&mut inner.obj); + + let start = into.filled().len(); + match ready!(r.poll_read(cx, into)) { + Ok(()) => { + let diff = into.filled().len() - start; + inner.pos += diff as u64; + Poll::Ready(Ok(())) + } + Err(err) => Poll::Ready(Err(err)), + } + } +} + /// Try to fill the buffer from the reader. /// /// If the reader reaches its end before filling the buffer at all, returns `false`. /// Otherwise returns `true`. +#[cfg(feature = "runtime-async-std")] fn poll_try_read_all( mut source: R, cx: &mut Context<'_>, @@ -729,7 +769,7 @@ fn poll_try_read_all( pos: &mut usize, ) -> Poll> { while *pos < buf.len() { - match async_std::task::ready!(Pin::new(&mut source).poll_read(cx, &mut buf[*pos..])) { + match ready!(Pin::new(&mut source).poll_read(cx, &mut buf[*pos..])) { Ok(0) => { if *pos == 0 { return Poll::Ready(Ok(false)); @@ -746,7 +786,39 @@ fn poll_try_read_all( Poll::Ready(Ok(true)) } +#[cfg(feature = "runtime-tokio")] +fn poll_try_read_all( + mut source: R, + cx: &mut Context<'_>, + buf: &mut [u8], + pos: &mut usize, +) -> Poll> { + while *pos < buf.len() { + let mut read_buf = io::ReadBuf::new(&mut buf[*pos..]); + let start = read_buf.filled().len(); + match ready!(Pin::new(&mut source).poll_read(cx, &mut read_buf)) { + Ok(()) => { + let diff = read_buf.filled().len() - start; + if diff == 0 { + if *pos == 0 { + return Poll::Ready(Ok(false)); + } + + return Poll::Ready(Err(other("failed to read entire block"))); + } else { + *pos += diff; + } + } + Err(err) => return Poll::Ready(Err(err)), + } + } + + *pos = 0; + Poll::Ready(Ok(true)) +} + /// Skip n bytes on the given source. +#[cfg(feature = "runtime-async-std")] fn poll_skip( mut source: R, cx: &mut Context<'_>, @@ -755,7 +827,7 @@ fn poll_skip( let mut buf = [0u8; 4096 * 8]; while amt > 0 { let n = cmp::min(amt, buf.len() as u64); - match async_std::task::ready!(Pin::new(&mut source).poll_read(cx, &mut buf[..n as usize])) { + match ready!(Pin::new(&mut source).poll_read(cx, &mut buf[..n as usize])) { Ok(0) => { return Poll::Ready(Err(other("unexpected EOF during skip"))); } @@ -769,12 +841,40 @@ fn poll_skip( Poll::Ready(Ok(())) } +/// Skip n bytes on the given source. +#[cfg(feature = "runtime-tokio")] +fn poll_skip( + mut source: R, + cx: &mut Context<'_>, + mut amt: u64, +) -> Poll> { + let mut buf = [0u8; 4096 * 8]; + while amt > 0 { + let n = cmp::min(amt, buf.len() as u64); + let mut read_buf = io::ReadBuf::new(&mut buf[..n as usize]); + let start = read_buf.filled().len(); + match ready!(Pin::new(&mut source).poll_read(cx, &mut read_buf)) { + Ok(()) => { + let diff = read_buf.filled().len() - start; + if diff == 0 { + return Poll::Ready(Err(other("unexpected EOF during skip"))); + } else { + amt -= diff as u64; + } + } + Err(err) => return Poll::Ready(Err(err)), + } + } + + Poll::Ready(Ok(())) +} + #[cfg(test)] mod tests { use super::*; - assert_impl_all!(async_std::fs::File: Send, Sync); - assert_impl_all!(Entries: Send, Sync); - assert_impl_all!(Archive: Send, Sync); - assert_impl_all!(Entry>: Send, Sync); + assert_impl_all!(fs::File: Send, Sync); + assert_impl_all!(Entries: Send, Sync); + assert_impl_all!(Archive: Send, Sync); + assert_impl_all!(Entry>: Send, Sync); } diff --git a/src/builder.rs b/src/builder.rs index 7de2147..aaed415 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,15 +1,29 @@ use std::borrow::Cow; +#[cfg(feature = "runtime-async-std")] +use async_std::fs::Metadata; +#[cfg(feature = "runtime-async-std")] use async_std::{ fs, io::{self, Read, Write}, path::Path, prelude::*, }; +#[cfg(feature = "runtime-tokio")] +use std::fs::Metadata; +#[cfg(feature = "runtime-tokio")] +use std::path::Path; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + fs, + io::{self, AsyncRead as Read, AsyncReadExt, AsyncWrite as Write, AsyncWriteExt}, +}; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; use crate::{ header::{bytes2path, path2bytes, HeaderMode}, - other, EntryType, Header, + metadata, other, symlink_metadata, EntryType, Header, }; /// A structure for building archives @@ -98,7 +112,8 @@ impl Builder { /// /// # Examples /// - /// ``` + #[cfg_attr(feature = "runtime-async-std", doc = "```")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_tar::{Builder, Header}; @@ -280,7 +295,8 @@ impl Builder { /// /// # Examples /// - /// ```no_run + #[cfg_attr(feature = "runtime-async-std", doc = "```no_run")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs::File; @@ -320,7 +336,8 @@ impl Builder { /// /// # Examples /// - /// ``` + #[cfg_attr(feature = "runtime-async-std", doc = "```")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs; @@ -356,7 +373,8 @@ impl Builder { /// /// # Examples /// - /// ``` + #[cfg_attr(feature = "runtime-async-std", doc = "```")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs; @@ -368,7 +386,7 @@ impl Builder { /// // with a different name. /// ar.append_dir_all("bardir", ".").await?; /// # - /// # Ok(()) }) } + /// # Ok(()) })} /// ``` pub async fn append_dir_all(&mut self, path: P, src_path: Q) -> io::Result<()> where @@ -431,14 +449,14 @@ async fn append_path_with_name( follow: bool, ) -> io::Result<()> { let stat = if follow { - fs::metadata(path).await.map_err(|err| { + metadata(path).await.map_err(|err| { io::Error::new( err.kind(), format!("{} when getting metadata for {}", err, path.display()), ) })? } else { - fs::symlink_metadata(path).await.map_err(|err| { + symlink_metadata(path).await.map_err(|err| { io::Error::new( err.kind(), format!("{} when getting metadata for {}", err, path.display()), @@ -564,7 +582,7 @@ async fn prepare_header_link( async fn append_fs( dst: &mut (dyn Write + Unpin + Send + Sync), path: &Path, - meta: &fs::Metadata, + meta: &Metadata, read: &mut (dyn Read + Unpin + Sync + Send), mode: HeaderMode, link_name: Option<&Path>, @@ -593,9 +611,21 @@ async fn append_dir_all( while let Some((src, is_dir, is_symlink)) = stack.pop() { let dest = path.join(src.strip_prefix(src_path).unwrap()); + #[cfg(feature = "runtime-async-std")] + async fn check_is_dir(path: &Path) -> bool { + path.is_dir().await + } + #[cfg(feature = "runtime-tokio")] + async fn check_is_dir(path: &Path) -> bool { + path.is_dir() + } + // In case of a symlink pointing to a directory, is_dir is false, but src.is_dir() will return true - if is_dir || (is_symlink && follow && src.is_dir().await) { + if is_dir || (is_symlink && follow && check_is_dir(&src).await) { + #[cfg(feature = "runtime-async-std")] let mut entries = fs::read_dir(&src).await?; + #[cfg(feature = "runtime-tokio")] + let mut entries = tokio_stream::wrappers::ReadDirStream::new(fs::read_dir(&src).await?); while let Some(entry) = entries.next().await { let entry = entry?; let file_type = entry.file_type().await?; @@ -615,6 +645,7 @@ async fn append_dir_all( Ok(()) } +#[cfg(feature = "runtime-async-std")] impl Drop for Builder { fn drop(&mut self) { async_std::task::block_on(async move { @@ -627,6 +658,6 @@ impl Drop for Builder { mod tests { use super::*; - assert_impl_all!(async_std::fs::File: Send, Sync); - assert_impl_all!(Builder: Send, Sync); + assert_impl_all!(fs::File: Send, Sync); + assert_impl_all!(Builder: Send, Sync); } diff --git a/src/entry.rs b/src/entry.rs index 1c79013..62ad243 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -5,18 +5,31 @@ use std::{ task::{Context, Poll}, }; +#[cfg(feature = "runtime-async-std")] use async_std::{ fs, - fs::OpenOptions, + fs::{OpenOptions, Permissions}, io::{self, prelude::*, Error, ErrorKind, SeekFrom}, path::{Component, Path, PathBuf}, }; -use pin_project::pin_project; +use futures_core::ready; +#[cfg(feature = "runtime-tokio")] +use std::{ + fs::Permissions, + path::{Component, Path, PathBuf}, +}; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + fs, + fs::OpenOptions, + io::{self, AsyncRead as Read, AsyncReadExt, AsyncSeekExt, Error, ErrorKind, SeekFrom}, +}; use filetime::{self, FileTime}; use crate::{ - error::TarError, header::bytes2path, other, pax::pax_extensions, Archive, Header, PaxExtensions, + error::TarError, fs_canonicalize, header::bytes2path, other, pax::pax_extensions, + symlink_metadata, Archive, Header, PaxExtensions, }; /// A read-only view into an entry of an archive. @@ -24,9 +37,7 @@ use crate::{ /// This structure is a window into a portion of a borrowed archive which can /// be inspected. It acts as a file handle by implementing the Reader trait. An /// entry cannot be rewritten once inserted into an archive. -#[pin_project] pub struct Entry { - #[pin] fields: EntryFields, _ignored: marker::PhantomData>, } @@ -41,7 +52,6 @@ impl fmt::Debug for Entry { // private implementation detail of `Entry`, but concrete (no type parameters) // and also all-public to be constructed from other modules. -#[pin_project] pub struct EntryFields { pub long_pathname: Option>, pub long_linkname: Option>, @@ -50,12 +60,10 @@ pub struct EntryFields { pub size: u64, pub header_pos: u64, pub file_pos: u64, - #[pin] pub data: Vec>, pub unpack_xattrs: bool, pub preserve_permissions: bool, pub preserve_mtime: bool, - #[pin] pub(crate) read_state: Option>, } @@ -78,10 +86,9 @@ impl fmt::Debug for EntryFields { } } -#[pin_project(project = EntryIoProject)] pub enum EntryIo { - Pad(#[pin] io::Take), - Data(#[pin] io::Take), + Pad(io::Take), + Data(io::Take), } impl fmt::Debug for EntryIo { @@ -118,7 +125,7 @@ impl Entry { /// /// It is recommended to use this method instead of inspecting the `header` /// directly to ensure that various archive formats are handled correctly. - pub fn path(&self) -> io::Result> { + pub fn path(&self) -> io::Result> { self.fields.path() } @@ -128,7 +135,7 @@ impl Entry { /// separators, and it will not always return the same value as /// `self.header().path_bytes()` as some archive formats have support for /// longer path names described in separate entries. - pub fn path_bytes(&self) -> Cow<[u8]> { + pub fn path_bytes(&self) -> Cow<'_, [u8]> { self.fields.path_bytes() } @@ -145,7 +152,7 @@ impl Entry { /// /// It is recommended to use this method instead of inspecting the `header` /// directly to ensure that various archive formats are handled correctly. - pub fn link_name(&self) -> io::Result>> { + pub fn link_name(&self) -> io::Result>> { self.fields.link_name() } @@ -154,7 +161,7 @@ impl Entry { /// Note that this will not always return the same value as /// `self.header().link_name_bytes()` as some archive formats have support for /// longer path names described in separate entries. - pub fn link_name_bytes(&self) -> Option> { + pub fn link_name_bytes(&self) -> Option> { self.fields.link_name_bytes() } @@ -225,7 +232,8 @@ impl Entry { /// /// # Examples /// - /// ```no_run + #[cfg_attr(feature = "runtime-async-std", doc = "```no_run")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs::File; @@ -260,7 +268,8 @@ impl Entry { /// /// # Examples /// - /// ```no_run + #[cfg_attr(feature = "runtime-async-std", doc = "```no_run")] + #[cfg_attr(feature = "runtime-tokio", doc = "```ignore")] /// # fn main() -> Result<(), Box> { async_std::task::block_on(async { /// # /// use async_std::fs::File; @@ -311,14 +320,25 @@ impl Entry { } } +#[cfg(feature = "runtime-async-std")] impl Read for Entry { fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, into: &mut [u8], ) -> Poll> { - let mut this = self.project(); - Pin::new(&mut *this.fields).poll_read(cx, into) + Pin::new(&mut self.fields).poll_read(cx, into) + } +} + +#[cfg(feature = "runtime-tokio")] +impl Read for Entry { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + into: &mut tokio::io::ReadBuf, + ) -> Poll> { + Pin::new(&mut self.fields).poll_read(cx, into) } } @@ -343,7 +363,7 @@ impl EntryFields { let mut buf = Vec::with_capacity(cap as usize); // Copied from futures::ReadToEnd - match async_std::task::ready!(poll_read_all_internal(self, cx, &mut buf)) { + match ready!(poll_read_all_internal(self, cx, &mut buf)) { Ok(_) => Poll::Ready(Ok(buf)), Err(err) => Poll::Ready(Err(err)), } @@ -360,7 +380,7 @@ impl EntryFields { bytes2path(self.path_bytes()) } - fn path_bytes(&self) -> Cow<[u8]> { + fn path_bytes(&self) -> Cow<'_, [u8]> { if let Some(ref bytes) = self.long_pathname { if let Some(&0) = bytes.last() { Cow::Borrowed(&bytes[..bytes.len() - 1]) @@ -386,14 +406,14 @@ impl EntryFields { String::from_utf8_lossy(&self.path_bytes()).to_string() } - fn link_name(&self) -> io::Result>> { + fn link_name(&self) -> io::Result>> { match self.link_name_bytes() { Some(bytes) => bytes2path(bytes).map(Some), None => Ok(None), } } - fn link_name_bytes(&self) -> Option> { + fn link_name_bytes(&self) -> Option> { match self.long_linkname { Some(ref bytes) => { if let Some(&0) = bytes.last() { @@ -590,7 +610,12 @@ impl EntryFields { #[cfg(any(unix, target_os = "redox"))] async fn symlink(src: &Path, dst: &Path) -> io::Result<()> { - async_std::os::unix::fs::symlink(src, dst).await + #[cfg(feature = "runtime-async-std")] + async_std::os::unix::fs::symlink(src, dst).await?; + #[cfg(feature = "runtime-tokio")] + tokio::fs::symlink(src, dst).await?; + + Ok(()) } } else if kind.is_pax_global_extensions() || kind.is_pax_local_extensions() @@ -720,7 +745,7 @@ impl EntryFields { use std::os::unix::prelude::*; let mode = if preserve { mode } else { mode & 0o777 }; - let perm = fs::Permissions::from_mode(mode as _); + let perm = Permissions::from_mode(mode as _); match f { Some(f) => f.set_permissions(perm).await, None => fs::set_permissions(dst, perm).await, @@ -820,7 +845,8 @@ impl EntryFields { async fn ensure_dir_created(&self, dst: &Path, dir: &Path) -> io::Result<()> { let mut ancestor = dir; let mut dirs_to_create = Vec::new(); - while ancestor.symlink_metadata().await.is_err() { + + while symlink_metadata(ancestor).await.is_err() { dirs_to_create.push(ancestor); if let Some(parent) = ancestor.parent() { ancestor = parent; @@ -839,13 +865,13 @@ impl EntryFields { async fn validate_inside_dst(&self, dst: &Path, file_dst: &Path) -> io::Result { // Abort if target (canonical) parent is outside of `dst` - let canon_parent = file_dst.canonicalize().await.map_err(|err| { + let canon_parent = fs_canonicalize(file_dst).await.map_err(|err| { Error::new( err.kind(), format!("{} while canonicalizing {}", err, file_dst.display()), ) })?; - let canon_target = dst.canonicalize().await.map_err(|err| { + let canon_target = fs_canonicalize(dst).await.map_err(|err| { Error::new( err.kind(), format!("{} while canonicalizing {}", err, dst.display()), @@ -866,29 +892,28 @@ impl EntryFields { } } +#[cfg(feature = "runtime-async-std")] impl Read for EntryFields { fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, into: &mut [u8], ) -> Poll> { - let mut this = self.project(); loop { - if this.read_state.is_none() { - if this.data.as_ref().is_empty() { - *this.read_state = None; + if self.read_state.is_none() { + if self.data.is_empty() { + self.read_state = None; } else { - let data = &mut *this.data; - *this.read_state = Some(data.remove(0)); + self.read_state = Some(self.data.remove(0)); } } - if let Some(ref mut io) = &mut *this.read_state { + if let Some(ref mut io) = &mut self.read_state { let ret = Pin::new(io).poll_read(cx, into); match ret { Poll::Ready(Ok(0)) => { - *this.read_state = None; - if this.data.as_ref().is_empty() { + self.read_state = None; + if self.data.is_empty() { return Poll::Ready(Ok(0)); } continue; @@ -910,15 +935,76 @@ impl Read for EntryFields { } } +#[cfg(feature = "runtime-tokio")] +impl Read for EntryFields { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + into: &mut tokio::io::ReadBuf, + ) -> Poll> { + loop { + if self.read_state.is_none() { + if self.data.is_empty() { + self.read_state = None; + } else { + self.read_state = Some(self.data.remove(0)); + } + } + + if let Some(ref mut io) = &mut self.read_state { + let start = into.filled().len(); + let ret = Pin::new(io).poll_read(cx, into); + match ret { + Poll::Ready(Ok(())) => { + let diff = into.filled().len() - start; + if diff == 0 { + self.read_state = None; + if self.data.is_empty() { + return Poll::Ready(Ok(())); + } + continue; + } else { + return Poll::Ready(Ok(())); + } + } + Poll::Ready(Err(err)) => { + return Poll::Ready(Err(err)); + } + Poll::Pending => { + return Poll::Pending; + } + } + } + // Unable to pull another value from `data`, so we are done. + return Poll::Ready(Ok(())); + } + } +} + +#[cfg(feature = "runtime-async-std")] impl Read for EntryIo { fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, into: &mut [u8], ) -> Poll> { - match self.project() { - EntryIoProject::Pad(io) => io.poll_read(cx, into), - EntryIoProject::Data(io) => io.poll_read(cx, into), + match &mut *self { + EntryIo::Pad(io) => Pin::new(io).poll_read(cx, into), + EntryIo::Data(io) => Pin::new(io).poll_read(cx, into), + } + } +} + +#[cfg(feature = "runtime-tokio")] +impl Read for EntryIo { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + into: &mut tokio::io::ReadBuf, + ) -> Poll> { + match &mut *self { + EntryIo::Pad(io) => Pin::new(io).poll_read(cx, into), + EntryIo::Data(io) => Pin::new(io).poll_read(cx, into), } } } @@ -936,6 +1022,7 @@ impl Drop for Guard<'_> { } } +#[cfg(feature = "runtime-async-std")] fn poll_read_all_internal( mut rd: Pin<&mut R>, cx: &mut Context<'_>, @@ -958,7 +1045,7 @@ fn poll_read_all_internal( } } - match async_std::task::ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) { + match ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) { Ok(0) => { ret = Poll::Ready(Ok(g.len)); break; @@ -973,3 +1060,48 @@ fn poll_read_all_internal( ret } + +#[cfg(feature = "runtime-tokio")] +fn poll_read_all_internal( + mut rd: Pin<&mut R>, + cx: &mut Context<'_>, + buf: &mut Vec, +) -> Poll> { + let mut g = Guard { + len: buf.len(), + buf, + }; + let ret; + loop { + if g.len == g.buf.len() { + unsafe { + g.buf.reserve(32); + let capacity = g.buf.capacity(); + g.buf.set_len(capacity); + + let buf = &mut g.buf[g.len..]; + std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()); + } + } + + let mut read_buf = io::ReadBuf::new(&mut g.buf[g.len..]); + let start = read_buf.filled().len(); + match ready!(rd.as_mut().poll_read(cx, &mut read_buf)) { + Ok(()) => { + let diff = read_buf.filled().len() - start; + if diff == 0 { + ret = Poll::Ready(Ok(g.len)); + break; + } else { + g.len += diff; + } + } + Err(e) => { + ret = Poll::Ready(Err(e)); + break; + } + } + } + + ret +} diff --git a/src/entry_type.rs b/src/entry_type.rs index 8c1106a..9db1df1 100644 --- a/src/entry_type.rs +++ b/src/entry_type.rs @@ -1,8 +1,9 @@ -// See https://en.wikipedia.org/wiki/Tar_%28computing%29#UStar_format -/// Indicate for the type of file described by a header. -/// -/// Each `Header` has an `entry_type` method returning an instance of this type -/// which can be used to inspect what the header is describing. +//! Indicate for the type of file described by a header. +//! +//! Each `Header` has an `entry_type` method returning an instance of this type +//! which can be used to inspect what the header is describing. +//! +//! See /// A non-exhaustive enum representing the possible entry types #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/src/error.rs b/src/error.rs index 829c727..4fc50d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ use std::{error, fmt}; +#[cfg(feature = "runtime-async-std")] use async_std::io::{self, Error}; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{self, Error}; #[derive(Debug)] pub struct TarError { diff --git a/src/header.rs b/src/header.rs index dfc87b3..3a6a0a2 100644 --- a/src/header.rs +++ b/src/header.rs @@ -5,10 +5,23 @@ use std::os::windows::prelude::*; use std::{borrow::Cow, fmt, iter, iter::repeat, mem, str}; +#[cfg(all(windows, feature = "runtime-async-std"))] +use async_std::fs; +#[cfg(feature = "runtime-async-std")] use async_std::{ - fs, io, + fs::Metadata, + io, path::{Component, Path, PathBuf}, }; +#[cfg(feature = "runtime-tokio")] +use std::{ + fs::Metadata, + path::{Component, Path, PathBuf}, +}; +#[cfg(all(windows, feature = "runtime-tokio"))] +use tokio::fs; +#[cfg(feature = "runtime-tokio")] +use tokio::io; use crate::{other, EntryType}; @@ -276,13 +289,13 @@ impl Header { /// 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) { + pub fn set_metadata(&mut self, meta: &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) { + pub fn set_metadata_in_mode(&mut self, meta: &Metadata, mode: HeaderMode) { self.fill_from(meta, mode); } @@ -328,7 +341,7 @@ impl Header { /// /// Note that this function will convert any `\` characters to directory /// separators. - pub fn path(&self) -> io::Result> { + pub fn path(&self) -> io::Result> { bytes2path(self.path_bytes()) } @@ -339,7 +352,7 @@ impl Header { /// /// Note that this function will convert any `\` characters to directory /// separators. - pub fn path_bytes(&self) -> Cow<[u8]> { + pub fn path_bytes(&self) -> Cow<'_, [u8]> { if let Some(ustar) = self.as_ustar() { ustar.path_bytes() } else { @@ -397,7 +410,7 @@ impl Header { /// /// Note that this function will convert any `\` characters to directory /// separators. - pub fn link_name(&self) -> io::Result>> { + pub fn link_name(&self) -> io::Result>> { match self.link_name_bytes() { Some(bytes) => bytes2path(bytes).map(Some), None => Ok(None), @@ -411,7 +424,7 @@ impl Header { /// /// Note that this function will convert any `\` characters to directory /// separators. - pub fn link_name_bytes(&self) -> Option> { + pub fn link_name_bytes(&self) -> Option> { let old = self.as_old(); if old.linkname[0] == 0 { None @@ -701,7 +714,7 @@ impl Header { .fold(0, |a, b| a + (*b as u32)) } - fn fill_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { + fn fill_from(&mut self, meta: &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() { @@ -726,7 +739,7 @@ impl Header { } #[cfg(any(unix, target_os = "redox"))] - fn fill_platform_from(&mut self, meta: &fs::Metadata, mode: HeaderMode) { + fn fill_platform_from(&mut self, meta: &Metadata, mode: HeaderMode) { match mode { HeaderMode::Complete => { self.set_mtime(meta.mtime() as u64); @@ -936,7 +949,7 @@ impl fmt::Debug for OldHeader { impl UstarHeader { /// See `Header::path_bytes` - pub fn path_bytes(&self) -> Cow<[u8]> { + pub fn path_bytes(&self) -> Cow<'_, [u8]> { if self.prefix[0] == 0 && !self.name.contains(&b'\\') { Cow::Borrowed(truncate(&self.name)) } else { @@ -1470,7 +1483,7 @@ fn truncate(slice: &[u8]) -> &[u8] { 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) { + } else if bytes.contains(&0) { Err(other("provided value contains a nul byte")) } else { for (slot, val) in slot.iter_mut().zip(bytes.iter().chain(Some(&0))) { @@ -1527,7 +1540,7 @@ fn copy_path_into_inner( return Err(other("paths in archives must have at least one component")); } if ends_with_slash(path) { - copy(&mut slot, &[b'/'])?; + copy(&mut slot, b"/")?; } return Ok(()); @@ -1578,7 +1591,7 @@ fn ends_with_slash(p: &Path) -> bool { #[cfg(any(unix, target_os = "redox"))] fn ends_with_slash(p: &Path) -> bool { - p.as_os_str().as_bytes().ends_with(&[b'/']) + p.as_os_str().as_bytes().ends_with(b"/") } #[cfg(any(windows, target_arch = "wasm32"))] @@ -1605,7 +1618,7 @@ pub fn path2bytes(p: &Path) -> io::Result> { #[cfg(any(unix, target_os = "redox"))] /// On unix this will never fail -pub fn path2bytes(p: &Path) -> io::Result> { +pub fn path2bytes(p: &Path) -> io::Result> { Ok(Cow::Borrowed(p.as_os_str().as_bytes())) } diff --git a/src/lib.rs b/src/lib.rs index 1a6e7f3..0ac1bb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,3 +48,36 @@ extern crate static_assertions; fn other(msg: &str) -> Error { Error::new(ErrorKind::Other, msg) } + +#[cfg(feature = "runtime-async-std")] +pub(crate) async fn fs_canonicalize( + path: &async_std::path::Path, +) -> async_std::io::Result { + path.canonicalize().await +} +#[cfg(feature = "runtime-tokio")] +pub(crate) async fn fs_canonicalize( + path: &std::path::Path, +) -> tokio::io::Result { + tokio::fs::canonicalize(path).await +} + +#[cfg(feature = "runtime-async-std")] +async fn symlink_metadata( + p: &async_std::path::Path, +) -> async_std::io::Result { + p.symlink_metadata().await +} +#[cfg(feature = "runtime-tokio")] +async fn symlink_metadata(p: &std::path::Path) -> tokio::io::Result { + tokio::fs::symlink_metadata(p).await +} + +#[cfg(feature = "runtime-async-std")] +async fn metadata(p: &async_std::path::Path) -> async_std::io::Result { + p.metadata().await +} +#[cfg(feature = "runtime-tokio")] +async fn metadata(p: &std::path::Path) -> tokio::io::Result { + tokio::fs::metadata(p).await +} diff --git a/src/pax.rs b/src/pax.rs index 7db4774..a1793b3 100644 --- a/src/pax.rs +++ b/src/pax.rs @@ -1,6 +1,9 @@ use std::{slice, str}; +#[cfg(feature = "runtime-async-std")] use async_std::io; +#[cfg(feature = "runtime-tokio")] +use tokio::io; use crate::other; @@ -18,7 +21,7 @@ pub struct PaxExtension<'entry> { value: &'entry [u8], } -pub fn pax_extensions(a: &[u8]) -> PaxExtensions { +pub fn pax_extensions(a: &[u8]) -> PaxExtensions<'_> { PaxExtensions { data: a.split(|a| *a == b'\n'), } diff --git a/tests/all.rs b/tests/all.rs index 1c29a3b..bd754a8 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -1,20 +1,24 @@ -extern crate async_tar; -extern crate filetime; -extern crate tempfile; -#[cfg(all(unix, feature = "xattr"))] -extern crate xattr; +use std::iter::repeat; +#[cfg(feature = "runtime-async-std")] use async_std::{ fs::{self, File}, - io::{self, Cursor, Read, Write}, + io::{self, Read, ReadExt, Write, WriteExt}, path::{Path, PathBuf}, - prelude::*, + stream::StreamExt, }; -use std::iter::repeat; - use async_tar::{Archive, ArchiveBuilder, Builder, EntryType, Header}; use filetime::FileTime; +#[cfg(feature = "runtime-tokio")] +use std::path::{Path, PathBuf}; use tempfile::{Builder as TempBuilder, TempDir}; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + fs::{self, File}, + io::{self, AsyncRead as Read, AsyncReadExt, AsyncWrite as Write, AsyncWriteExt}, +}; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; macro_rules! t { ($e:expr) => { @@ -35,20 +39,21 @@ mod header; /// test that we can concatenate the simple.tar archive and extract the same entries twice when we /// use the ignore_zeros option. -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn simple_concat() { let bytes = tar!("simple.tar"); let mut archive_bytes = Vec::new(); archive_bytes.extend(bytes); - let original_names: Vec = decode_names(Archive::new(Cursor::new(&archive_bytes))).await; + let original_names: Vec = decode_names(Archive::new(&archive_bytes[..])).await; let expected: Vec<&str> = original_names.iter().map(|n| n.as_str()).collect(); // concat two archives (with null in-between); archive_bytes.extend(bytes); // test now that when we read the archive, it stops processing at the first zero header. - let actual = decode_names(Archive::new(Cursor::new(&archive_bytes))).await; + let actual = decode_names(Archive::new(&archive_bytes[..])).await; assert_eq!(expected, actual); // extend expected by itself. @@ -59,7 +64,7 @@ async fn simple_concat() { o }; - let builder = ArchiveBuilder::new(Cursor::new(&archive_bytes)).set_ignore_zeros(true); + let builder = ArchiveBuilder::new(&archive_bytes[..]).set_ignore_zeros(true); let ar = builder.build(); let actual = decode_names(ar).await; @@ -81,9 +86,10 @@ async fn simple_concat() { } } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn header_impls() { - let ar = Archive::new(Cursor::new(tar!("simple.tar"))); + let ar = Archive::new(tar!("simple.tar")); let hn = Header::new_old(); let hnb = hn.as_bytes(); let mut entries = t!(ar.entries()); @@ -97,9 +103,10 @@ async fn header_impls() { } } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn header_impls_missing_last_header() { - let ar = Archive::new(Cursor::new(tar!("simple_missing_last_header.tar"))); + let ar = Archive::new(tar!("simple_missing_last_header.tar")); let hn = Header::new_old(); let hnb = hn.as_bytes(); let mut entries = t!(ar.entries()); @@ -114,9 +121,10 @@ async fn header_impls_missing_last_header() { } } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn reading_files() { - let rdr = Cursor::new(tar!("reading_files.tar")); + let rdr = tar!("reading_files.tar"); let ar = Archive::new(rdr); let mut entries = t!(ar.entries()); @@ -135,7 +143,8 @@ async fn reading_files() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn writing_files() { let mut ar = Builder::new(Vec::new()); let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -148,7 +157,7 @@ async fn writing_files() { .await); let data = t!(ar.into_inner().await); - let ar = Archive::new(Cursor::new(data)); + let ar = Archive::new(&data[..]); let mut entries = t!(ar.entries()); let mut f = t!(entries.next().await.unwrap()); @@ -161,7 +170,8 @@ async fn writing_files() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn large_filename() { let mut ar = Builder::new(Vec::new()); let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -181,8 +191,8 @@ async fn large_filename() { .await); t!(ar.append_data(&mut header, &too_long, &b"test"[..]).await); - let rd = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rd); + let rd = t!(ar.into_inner().await); + let ar = Archive::new(&rd[..]); let mut entries = t!(ar.entries()); // The short entry added with `append` @@ -217,7 +227,8 @@ async fn large_filename() { // starting with ".." of a long path gets split at 100-byte mark // so that ".." goes into header and gets interpreted as parent dir // (and rejected) . -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn large_filename_with_dot_dot_at_100_byte_mark() { let mut ar = Builder::new(Vec::new()); @@ -233,8 +244,8 @@ async fn large_filename_with_dot_dot_at_100_byte_mark() { .append_data(&mut header, &long_name_with_dot_dot, &b"test"[..]) .await); - let rd = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rd); + let rd = t!(ar.into_inner().await); + let ar = Archive::new(&rd[..]); let mut entries = t!(ar.entries()); let mut f = entries.next().await.unwrap().unwrap(); @@ -246,9 +257,10 @@ async fn large_filename_with_dot_dot_at_100_byte_mark() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn reading_entries() { - let rdr = Cursor::new(tar!("reading_files.tar")); + let rdr = tar!("reading_files.tar"); let ar = Archive::new(rdr); let mut entries = t!(ar.entries()); let mut a = t!(entries.next().await.unwrap()); @@ -272,36 +284,32 @@ async fn check_dirtree(td: &TempDir) { let dir_a = td.path().join("a"); let dir_b = td.path().join("a/b"); let file_c = td.path().join("a/c"); - assert!( - fs::metadata(&dir_a) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); - assert!( - fs::metadata(&dir_b) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); - assert!( - fs::metadata(&file_c) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(&dir_a) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); + assert!(fs::metadata(&dir_b) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); + assert!(fs::metadata(&file_c) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn extracting_directories() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); - let rdr = Cursor::new(tar!("directory.tar")); + let rdr = tar!("directory.tar"); let ar = Archive::new(rdr); t!(ar.unpack(td.path()).await); check_dirtree(&td).await; } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(all(unix, feature = "xattr"))] async fn xattrs() { // If /tmp is a tmpfs, xattr will fail @@ -309,7 +317,7 @@ async fn xattrs() { let td = t!(TempBuilder::new() .prefix("async-tar") .tempdir_in("/var/tmp")); - let rdr = Cursor::new(tar!("xattrs.tar")); + let rdr = tar!("xattrs.tar"); let builder = ArchiveBuilder::new(rdr).set_unpack_xattrs(true); let ar = builder.build(); t!(ar.unpack(td.path()).await); @@ -318,7 +326,8 @@ async fn xattrs() { assert_eq!(val.unwrap(), b"epm"); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(all(unix, feature = "xattr"))] async fn no_xattrs() { // If /tmp is a tmpfs, xattr will fail @@ -326,7 +335,7 @@ async fn no_xattrs() { let td = t!(TempBuilder::new() .prefix("async-tar") .tempdir_in("/var/tmp")); - let rdr = Cursor::new(tar!("xattrs.tar")); + let rdr = tar!("xattrs.tar"); let builder = ArchiveBuilder::new(rdr).set_unpack_xattrs(false); let ar = builder.build(); t!(ar.unpack(td.path()).await); @@ -337,7 +346,8 @@ async fn no_xattrs() { ); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn writing_and_extracting_directories() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -351,13 +361,14 @@ async fn writing_and_extracting_directories() { .await); t!(ar.finish().await); - let rdr = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rdr); + let rdr = t!(ar.into_inner().await); + let ar = Archive::new(&rdr[..]); t!(ar.unpack(td.path()).await); check_dirtree(&td).await; } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn writing_directories_recursively() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -376,39 +387,32 @@ async fn writing_directories_recursively() { t!(ar.append_dir_all("foobar", base_dir).await); let data = t!(ar.into_inner().await); - let ar = Archive::new(Cursor::new(data)); + let ar = Archive::new(&data[..]); t!(ar.unpack(td.path()).await); let base_dir = td.path().join("foobar"); - assert!( - fs::metadata(&base_dir) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&base_dir) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); let file1_path = base_dir.join("file1"); - assert!( - fs::metadata(&file1_path) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(&file1_path) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); let sub_dir = base_dir.join("sub"); - assert!( - fs::metadata(&sub_dir) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&sub_dir) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); let file2_path = sub_dir.join("file2"); - assert!( - fs::metadata(&file2_path) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(&file2_path) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn append_dir_all_blank_dest() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -427,39 +431,32 @@ async fn append_dir_all_blank_dest() { t!(ar.append_dir_all("", base_dir).await); let data = t!(ar.into_inner().await); - let ar = Archive::new(Cursor::new(data)); + let ar = Archive::new(&data[..]); t!(ar.unpack(td.path()).await); let base_dir = td.path(); - assert!( - fs::metadata(&base_dir) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&base_dir) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); let file1_path = base_dir.join("file1"); - assert!( - fs::metadata(&file1_path) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(&file1_path) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); let sub_dir = base_dir.join("sub"); - assert!( - fs::metadata(&sub_dir) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&sub_dir) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); let file2_path = sub_dir.join("file2"); - assert!( - fs::metadata(&file2_path) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(&file2_path) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn append_dir_all_does_not_work_on_non_directory() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); let path = td.path().join("test"); @@ -470,23 +467,23 @@ async fn append_dir_all_does_not_work_on_non_directory() { assert!(result.is_err()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn extracting_duplicate_dirs() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); - let rdr = Cursor::new(tar!("duplicate_dirs.tar")); + let rdr = tar!("duplicate_dirs.tar"); let ar = Archive::new(rdr); t!(ar.unpack(td.path()).await); let some_dir = td.path().join("some_dir"); - assert!( - fs::metadata(&some_dir) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&some_dir) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn unpack_old_style_bsd_dir() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -500,19 +497,20 @@ async fn unpack_old_style_bsd_dir() { t!(ar.append(&header, &mut io::empty()).await); // Extracting - let rdr = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rdr); + let rdr = t!(ar.into_inner().await); + let ar = Archive::new(&rdr[..]); t!(ar.clone().unpack(td.path()).await); // Iterating - let rdr = Cursor::new(ar.into_inner().map_err(|_| ()).unwrap().into_inner()); + let rdr = ar.into_inner().map_err(|_| ()).unwrap(); let ar = Archive::new(rdr); assert!(t!(ar.entries()).all(|fr| fr.is_ok()).await); assert!(td.path().join("testdir").is_dir()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn handling_incorrect_file_size() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -529,17 +527,20 @@ async fn handling_incorrect_file_size() { t!(ar.append(&header, &mut file).await); // Extracting - let rdr = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rdr); + let rdr: Vec = t!(ar.into_inner().await); + println!("extracting"); + let ar = Archive::new(&rdr[..]); assert!(ar.clone().unpack(td.path()).await.is_err()); // Iterating - let rdr = Cursor::new(ar.into_inner().map_err(|_| ()).unwrap().into_inner()); - let ar = Archive::new(rdr); + let _ = ar.into_inner().map_err(|_| ()).unwrap(); + println!("iterating"); + let ar = Archive::new(&rdr[..]); assert!(t!(ar.entries()).any(|fr| fr.is_err()).await); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn extracting_malicious_tarball() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -589,26 +590,18 @@ async fn extracting_malicious_tarball() { assert!(fs::metadata("/tmp/abs_evil.txt6").await.is_err()); assert!(fs::metadata("/tmp/rel_evil.txt").await.is_err()); assert!(fs::metadata("/tmp/rel_evil.txt").await.is_err()); - assert!( - fs::metadata(td.path().join("../tmp/rel_evil.txt")) - .await - .is_err() - ); - assert!( - fs::metadata(td.path().join("../rel_evil2.txt")) - .await - .is_err() - ); - assert!( - fs::metadata(td.path().join("../rel_evil3.txt")) - .await - .is_err() - ); - assert!( - fs::metadata(td.path().join("../rel_evil4.txt")) - .await - .is_err() - ); + assert!(fs::metadata(td.path().join("../tmp/rel_evil.txt")) + .await + .is_err()); + assert!(fs::metadata(td.path().join("../rel_evil2.txt")) + .await + .is_err()); + assert!(fs::metadata(td.path().join("../rel_evil3.txt")) + .await + .is_err()); + assert!(fs::metadata(td.path().join("../rel_evil4.txt")) + .await + .is_err()); // The `some` subdirectory should not be created because the only // filename that references this has '..'. @@ -619,66 +612,62 @@ async fn extracting_malicious_tarball() { // `abs_evil6.txt`. let tmp_root = td.path().join("tmp"); - assert!( - fs::metadata(&tmp_root) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) - ); + assert!(fs::metadata(&tmp_root) + .await + .map(|m| m.is_dir()) + .unwrap_or(false)); let mut entries = fs::read_dir(&tmp_root).await.unwrap(); - while let Some(entry) = entries.next().await { - let entry = entry.unwrap(); - println!("- {:?}", entry.file_name()); + #[cfg(feature = "runtime-async-std")] + { + while let Some(entry) = entries.next().await { + let entry = entry.unwrap(); + println!("- {:?}", entry.file_name()); + } + } + #[cfg(feature = "runtime-tokio")] + { + while let Some(entry) = entries.next_entry().await.unwrap() { + println!("- {:?}", entry.file_name()); + } } - assert!( - fs::metadata(tmp_root.join("abs_evil.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(tmp_root.join("abs_evil.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); // not present due to // being interpreted differently on windows #[cfg(not(target_os = "windows"))] - assert!( - fs::metadata(tmp_root.join("abs_evil2.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); - assert!( - fs::metadata(tmp_root.join("abs_evil3.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); - assert!( - fs::metadata(tmp_root.join("abs_evil4.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(tmp_root.join("abs_evil2.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); + assert!(fs::metadata(tmp_root.join("abs_evil3.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); + assert!(fs::metadata(tmp_root.join("abs_evil4.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); // not present due to // being interpreted differently on windows #[cfg(not(target_os = "windows"))] - assert!( - fs::metadata(tmp_root.join("abs_evil5.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); - assert!( - fs::metadata(tmp_root.join("abs_evil6.txt")) - .await - .map(|m| m.is_file()) - .unwrap_or(false) - ); + assert!(fs::metadata(tmp_root.join("abs_evil5.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); + assert!(fs::metadata(tmp_root.join("abs_evil6.txt")) + .await + .map(|m| m.is_file()) + .unwrap_or(false)); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn octal_spaces() { - let rdr = Cursor::new(tar!("spaces.tar")); + let rdr = tar!("spaces.tar"); let ar = Archive::new(rdr); let entry = ar.entries().unwrap().next().await.unwrap().unwrap(); @@ -690,7 +679,8 @@ async fn octal_spaces() { assert_eq!(entry.header().cksum().unwrap(), 0o4253); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn extracting_malformed_tar_null_blocks() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -717,18 +707,20 @@ async fn extracting_malformed_tar_null_blocks() { assert!(ar.unpack(td.path()).await.is_ok()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn empty_filename() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); - let rdr = Cursor::new(tar!("empty_filename.tar")); + let rdr = tar!("empty_filename.tar"); let ar = Archive::new(rdr); assert!(ar.unpack(td.path()).await.is_ok()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn file_times() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); - let rdr = Cursor::new(tar!("file_times.tar")); + let rdr = tar!("file_times.tar"); let ar = Archive::new(rdr); t!(ar.unpack(td.path()).await); @@ -741,14 +733,17 @@ async fn file_times() { assert_eq!(atime.nanoseconds(), 0); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn backslash_treated_well() { // Insert a file into an archive with a backslash let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); let mut ar = Builder::new(Vec::::new()); t!(ar.append_dir("foo\\bar", td.path()).await); - let ar = Archive::new(Cursor::new(t!(ar.into_inner().await))); - let f = t!(t!(ar.entries()).next().await.unwrap()); + let data = t!(ar.into_inner().await); + let ar = Archive::new(&data[..]); + let mut entries = t!(ar.entries()); + let f = t!(entries.next().await.unwrap()); if cfg!(unix) { assert_eq!(t!(f.header().path()).to_str(), Some("foo\\bar")); } else { @@ -776,7 +771,8 @@ async fn backslash_treated_well() { } #[cfg(unix)] -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn nul_bytes_in_path() { use std::{ffi::OsStr, os::unix::prelude::*}; @@ -787,9 +783,10 @@ async fn nul_bytes_in_path() { assert!(err.to_string().contains("contains a nul byte")); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn links() { - let ar = Archive::new(Cursor::new(tar!("link.tar"))); + let ar = Archive::new(tar!("link.tar")); let mut entries = t!(ar.entries()); let link = t!(entries.next().await.unwrap()); assert_eq!( @@ -800,11 +797,12 @@ async fn links() { assert!(t!(other.header().link_name()).is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(unix)] // making symlinks on windows is hard async fn unpack_links() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); - let ar = Archive::new(Cursor::new(tar!("link.tar"))); + let ar = Archive::new(tar!("link.tar")); t!(ar.unpack(td.path()).await); let md = t!(fs::symlink_metadata(td.path().join("lnk")).await); @@ -816,7 +814,8 @@ async fn unpack_links() { t!(File::open(td.path().join("lnk")).await); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn pax_simple() { let ar = Archive::new(tar!("pax.tar")); let mut entries = t!(ar.entries()); @@ -836,7 +835,8 @@ async fn pax_simple() { assert_eq!(third.value(), Ok("1453146164.953123768")); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn pax_path() { let ar = Archive::new(tar!("pax2.tar")); let mut entries = t!(ar.entries()); @@ -845,9 +845,10 @@ async fn pax_path() { assert!(first.path().unwrap().ends_with("aaaaaaaaaaaaaaa")); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn pax_precedence() { - let mut ar = Archive::new(tar!("pax-header-precedence.tar")); + let ar = Archive::new(tar!("pax-header-precedence.tar")); let mut entries = t!(ar.entries()); let first = t!(entries.next().await.unwrap()); @@ -862,7 +863,8 @@ async fn pax_precedence() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn long_name_trailing_nul() { let mut b = Builder::new(Vec::::new()); @@ -887,7 +889,8 @@ async fn long_name_trailing_nul() { assert_eq!(&*e.path_bytes(), b"foo"); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn long_linkname_trailing_nul() { let mut b = Builder::new(Vec::::new()); @@ -912,7 +915,8 @@ async fn long_linkname_trailing_nul() { assert_eq!(&*e.link_name_bytes().unwrap(), b"foo"); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn encoded_long_name_has_trailing_nul() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); let path = td.path().join("foo"); @@ -935,9 +939,10 @@ async fn encoded_long_name_has_trailing_nul() { assert!(header_name.starts_with(b"././@LongLink\x00")); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn reading_sparse() { - let rdr = Cursor::new(tar!("sparse.tar")); + let rdr = tar!("sparse.tar"); let ar = Archive::new(rdr); let mut entries = t!(ar.entries()); @@ -985,9 +990,10 @@ async fn reading_sparse() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn extract_sparse() { - let rdr = Cursor::new(tar!("sparse.tar")); + let rdr = tar!("sparse.tar"); let ar = Archive::new(rdr); let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); t!(ar.unpack(td.path()).await); @@ -1034,7 +1040,8 @@ async fn extract_sparse() { assert!(s[0x2fa0 + 6..0x4000].chars().all(|x| x == '\u{0}')); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn path_separators() { let mut ar = Builder::new(Vec::new()); let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -1065,8 +1072,8 @@ async fn path_separators() { .append_file(&long_path, &mut t!(File::open(&path).await)) .await); - let rd = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rd); + let rd = t!(ar.into_inner().await); + let ar = Archive::new(&rd[..]); let mut entries = t!(ar.entries()); let entry = t!(entries.next().await.unwrap()); @@ -1077,10 +1084,13 @@ async fn path_separators() { assert_eq!(t!(entry.path()), long_path); assert!(!entry.path_bytes().contains(&b'\\')); - assert!(entries.next().await.is_none()); + let entry = entries.next().await; + dbg!(&entry); + assert!(entry.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(unix)] async fn append_path_symlink() { use std::{borrow::Cow, env, os::unix::fs::symlink}; @@ -1102,8 +1112,8 @@ async fn append_path_symlink() { t!(symlink(&long_linkname, &long_pathname)); t!(ar.append_path(&long_pathname).await); - let rd = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rd); + let rd = t!(ar.into_inner().await); + let ar = Archive::new(&rd[..]); let mut entries = t!(ar.entries()); let entry = t!(entries.next().await.unwrap()); @@ -1133,7 +1143,8 @@ async fn append_path_symlink() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn name_with_slash_doesnt_fool_long_link_and_bsd_compat() { let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -1154,19 +1165,20 @@ async fn name_with_slash_doesnt_fool_long_link_and_bsd_compat() { t!(ar.append(&header, &mut io::empty()).await); // Extracting - let rdr = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rdr); + let rdr = t!(ar.into_inner().await); + let ar = Archive::new(&rdr[..]); t!(ar.clone().unpack(td.path()).await); // Iterating - let rdr = Cursor::new(ar.into_inner().map_err(|_| ()).unwrap().into_inner()); + let rdr = ar.into_inner().map_err(|_| ()).unwrap(); let ar = Archive::new(rdr); assert!(t!(ar.entries()).all(|fr| fr.is_ok()).await); assert!(td.path().join("foo").is_file()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn insert_local_file_different_name() { let mut ar = Builder::new(Vec::new()); let td = t!(TempBuilder::new().prefix("async-tar").tempdir()); @@ -1181,8 +1193,8 @@ async fn insert_local_file_different_name() { .await .unwrap(); - let rd = Cursor::new(t!(ar.into_inner().await)); - let ar = Archive::new(rd); + let rd = t!(ar.into_inner().await); + let ar = Archive::new(&rd[..]); let mut entries = t!(ar.entries()); let entry = t!(entries.next().await.unwrap()); assert_eq!(t!(entry.path()), Path::new("archive/dir")); @@ -1191,7 +1203,8 @@ async fn insert_local_file_different_name() { assert!(entries.next().await.is_none()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(unix)] async fn tar_directory_containing_symlink_to_directory() { use std::os::unix::fs::symlink; @@ -1208,10 +1221,11 @@ async fn tar_directory_containing_symlink_to_directory() { ar.finish().await.unwrap(); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn long_path() { let td = t!(TempBuilder::new().prefix("tar-rs").tempdir()); - let rdr = Cursor::new(tar!("7z_long_path.tar")); + let rdr = tar!("7z_long_path.tar"); let ar = Archive::new(rdr); ar.unpack(td.path()).await.unwrap(); } diff --git a/tests/entry.rs b/tests/entry.rs index 788fed2..af52709 100644 --- a/tests/entry.rs +++ b/tests/entry.rs @@ -1,11 +1,17 @@ -extern crate async_tar; -extern crate tempfile; - +#[cfg(feature = "runtime-async-std")] use async_std::{ - fs::{create_dir, File}, + fs::{self, create_dir, File}, prelude::*, }; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + fs::{self, create_dir, File}, + io::AsyncReadExt, +}; +#[cfg(feature = "runtime-tokio")] +use tokio_stream::StreamExt; + use tempfile::Builder; macro_rules! t { @@ -17,7 +23,8 @@ macro_rules! t { }; } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn absolute_symlink() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -35,7 +42,7 @@ async fn absolute_symlink() { let td = t!(Builder::new().prefix("tar").tempdir()); t!(ar.unpack(td.path()).await); - t!(td.path().join("foo").symlink_metadata()); + t!(fs::symlink_metadata(td.path().join("foo")).await); let ar = async_tar::Archive::new(&bytes[..]); let mut entries = t!(ar.entries()); @@ -43,7 +50,8 @@ async fn absolute_symlink() { assert_eq!(&*entry.link_name_bytes().unwrap(), b"/bar"); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn absolute_hardlink() { let td = t!(Builder::new().prefix("tar").tempdir()); let mut ar = async_tar::Builder::new(Vec::new()); @@ -72,7 +80,8 @@ async fn absolute_hardlink() { t!(td.path().join("bar").metadata()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn relative_hardlink() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -100,7 +109,8 @@ async fn relative_hardlink() { t!(td.path().join("bar").metadata()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn absolute_link_deref_error() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -124,11 +134,12 @@ async fn absolute_link_deref_error() { let td = t!(Builder::new().prefix("tar").tempdir()); assert!(ar.unpack(td.path()).await.is_err()); - t!(td.path().join("foo").symlink_metadata()); + t!(fs::symlink_metadata(td.path().join("foo")).await); assert!(File::open(td.path().join("foo").join("bar")).await.is_err()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn relative_link_deref_error() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -152,11 +163,12 @@ async fn relative_link_deref_error() { let td = t!(Builder::new().prefix("tar").tempdir()); assert!(ar.unpack(td.path()).await.is_err()); - t!(td.path().join("foo").symlink_metadata()); + t!(fs::symlink_metadata(td.path().join("foo")).await); assert!(File::open(td.path().join("foo").join("bar")).await.is_err()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(unix)] async fn directory_maintains_permissions() { use ::std::os::unix::fs::PermissionsExt; @@ -182,7 +194,8 @@ async fn directory_maintains_permissions() { assert_eq!(md.permissions().mode(), 0o40777); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(not(windows))] // dangling symlinks have weird permissions async fn modify_link_just_created() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -221,7 +234,8 @@ async fn modify_link_just_created() { t!(File::open(td.path().join("foo/bar")).await); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(not(windows))] // dangling symlinks have weird permissions async fn modify_outside_with_relative_symlink() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -251,7 +265,8 @@ async fn modify_outside_with_relative_symlink() { assert!(!td.path().join("foo").exists()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn parent_paths_error() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -279,7 +294,8 @@ async fn parent_paths_error() { assert!(File::open(td.path().join("foo").join("bar")).await.is_err()); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] #[cfg(unix)] async fn good_parent_paths_ok() { use std::path::PathBuf; @@ -310,7 +326,8 @@ async fn good_parent_paths_ok() { t!(File::open(dst).await); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn modify_hard_link_just_created() { let mut ar = async_tar::Builder::new(Vec::new()); @@ -345,7 +362,8 @@ async fn modify_hard_link_just_created() { assert_eq!(contents.len(), 0); } -#[async_std::test] +#[cfg_attr(feature = "runtime-async-std", async_std::test)] +#[cfg_attr(feature = "runtime-tokio", tokio::test)] async fn modify_symlink_just_created() { let mut ar = async_tar::Builder::new(Vec::new());