You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重置Tar条目与归档位置实现多次读取及解决编译错误

问题与解决方案

问题描述

我编写了如下Rust代码,用于将Tar归档内的文件内容复制到其他文件。首次复制成功后,Tar条目的文件位置已处于末尾,请问如何在复制操作完成后重置该条目位置至开头?同时,如何将归档的位置重置至开头?

原始代码

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[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"

解决方案

1. 修复编译错误

entries_with_seek方法要求归档的底层读取器实现Seek trait,需做两处调整:

  • 给get_entry的泛型参数添加R: Seek约束
  • 修改get_archive的返回类型,保留Seek能力(TarGz需用MultiGzDecoder替代GzDecoder,后者不支持Seek)

2. 重置Tar条目位置

如果条目底层读取器支持Seek,直接调用entry.seek(SeekFrom::Start(0))即可将指针重置到开头。未压缩Tar条目天然支持,TarGz条目通过MultiGzDecoder也能支持。

3. 重置归档位置

调用archive.seek(SeekFrom::Start(0))可将归档的读取指针重置到开头,之后能重新遍历所有条目。注意TarGz格式依赖MultiGzDecoder和可Seek的底层文件(如本地文件)。

修改后的完整代码

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

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

enum ArchiveType {
    Tar,
    TarGz,
}

// 调整返回类型,保留Seek+Read能力
fn get_archive(archive_type: ArchiveType, archive: &Path) -> Archive<Box<dyn Seek + Read>> {
    let file = File::open(archive).expect(&format!("Failed to open archive {:?}", archive));
    match archive_type {
        ArchiveType::Tar => Archive::new(Box::new(file)),
        // 使用MultiGzDecoder支持Seek
        ArchiveType::TarGz => Archive::new(Box::new(MultiGzDecoder::new(file))),
    }
}

// 添加R: Seek约束
fn get_entry<'a, R: Seek + 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?;
        if entry
            .path()
            .as_ref()
            .ok()
            .and_then(|path| path.file_name())
            .and_then(OsStr::to_str)
            .map(|name| name == normalized_entry_name)
            .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");
        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() -> Result<(), Error> {
    let mut archive = get_archive(ArchiveType::Tar, Path::new("ap.tar"));

    let file_name = "main.rs";
    
    // 第一次复制+重置条目
    {
        let mut entry = get_entry(&mut archive, file_name)?;
        let mut dst_file = File::create("b.txt")?;
        write_to_file(&mut entry, &mut dst_file);
        
        // 重置条目到开头,可再次读取
        entry.seek(SeekFrom::Start(0))?;
        let mut dst_file2 = File::create("b_again.txt")?;
        write_to_file(&mut entry, &mut dst_file2);
    }
    
    // 重置归档到开头
    archive.seek(SeekFrom::Start(0))?;
    
    // 第二次获取条目并复制
    {
        let mut entry = get_entry(&mut archive, file_name)?;
        let mut dst_file = File::create("c.txt")?;
        write_to_file(&mut entry, &mut dst_file);
    }
    
    Ok(())
}

关键说明

  • TarGz限制:MultiGzDecoder通过缓存解压数据实现Seek,内存开销比普通GzDecoder大,仅适用于本地文件等可Seek的数据源。
  • 兼容性:未压缩Tar的重置操作无额外开销,TarGz的重置依赖MultiGzDecoder的实现。

内容的提问来源于stack exchange,提问作者Harry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:05:05