rusttar

How to reset an entry of a tar to the beginning for multiple reads


In the below code, I am copying the contents of a file present inside a tar to other files. After the first successful copy file position of the tar entry is now at the end, how do I reset the file position to the beginning after a successful copy operation. And, also how to reset archive position to the beginning?

use std::fs::File;
use std::path::Path;
use std::io::{Error, ErrorKind, Read, Seek, Write};
use std::ffi::OsStr;

use tar::{Archive, Entry};
use flate2::read::GzDecoder;

enum ArchiveType {
    Tar,
    TarGz,
}

fn get_archive(archive_type: ArchiveType, archive: &Path) -> Archive<Box<dyn Read>> {
    let file = File::open(archive).expect(&format!("Failed to open archive {:?}", archive));
    match archive_type {
        ArchiveType::Tar => Archive::new(Box::new(file)),
        ArchiveType::TarGz => Archive::new(Box::new(GzDecoder::new(file))),
    }
}

fn get_entry<'a, R: Read>(
    archive: &'a mut Archive<R>,
    entry_name: &'a str,
) -> Result<Entry<'a, R>, Error> {
    let normalized_entry_name = entry_name.trim_start_matches("./");
    for entry in archive.entries_with_seek()? {
        let entry = entry?;
        // comparison between 'entry_name' and path of 'entry'
        // is done only with file_name() we ignore subfolders.
        // if multiple entries with same name, only first will be found.
        if entry
            .path()
            .as_ref()
            .ok() // Convert Result to Option<Cow<Path>> (ignoring errors)
            .and_then(|path| path.file_name()) // Extract file name (Option<&OsStr>)
            .and_then(OsStr::to_str) // Convert to Option<&str> safely
            .map(|name| {
                println!("{}: {}", name, normalized_entry_name);
                name == normalized_entry_name}) // Compare
            .unwrap_or(false)
        {
            return Ok(entry);
        }
    }
    Err(Error::new(ErrorKind::Other, "failed to get entry from the archive"))
}

fn write_to_file<R: Read, W: Write>(mut reader: R, mut writer: W) {
    let mut buf = vec![0; 1024];
    let mut bytes_read = buf.capacity();
    while bytes_read > 0 {
        bytes_read = reader.read(&mut buf).expect("failed to read from the file");
        println!("bytesssss_readddd: {}", bytes_read);
        if bytes_read > 0 {
            writer.write_all(&buf[..bytes_read]).expect("failed to write to the file");
        }
    }
    writer.flush().expect("failed to flush the writer"); 
}

fn main() {
    let mut archive =
    get_archive(ArchiveType::Tar, Path::new("ap.tar"));

    let file_name = "main.rs".to_string();
    {
        let mut entry = get_entry(&mut archive, &file_name).expect("error while getting entry");
        let dst_file = &File::create("b.txt").expect("unable to create the file");
        write_to_file(&mut entry, dst_file);
    }
    {
        let mut entry = get_entry(&mut archive, &file_name).expect("error while getting entry");
        let dst_file = &File::create("c.txt").expect("unable to create the file");
        write_to_file(&mut entry, dst_file);
    }
}

Error

error[E0599]: the method `entries_with_seek` exists for mutable reference `&mut Archive<R>`, but its trait bounds were not satisfied
  --> src/main.rs:27:26
   |
27 |     for entry in archive.entries_with_seek()? {
   |                          ^^^^^^^^^^^^^^^^^ method cannot be called on `&mut Archive<R>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `R: Seek`
help: consider restricting the type parameter to satisfy the trait bound
   |
25 | ) -> Result<Entry<'a, R>, Error> where R: Seek {
   |                                  +++++++++++++

For more information about this error, try `rustc --explain E0599`.

Cargo.toml

[dependencies]
tar = "0.4.44"
flate2 = "1.1.0"

Solution

  • You can't restart an Entry it doesn't implement Seek, so you'd recreate it from the Archive, to be able to do that the underlying Reader must implement Seek and you have to use entries_with_seek instead of the regular entries (or manually seek to the start before calling entries).

    Since trait objects only implement the listed traits and their supertraits a Box<dyn Read> is not sufficient to use with entries_with_seek. You'll have to change it to either of the following: