如何重置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
相关产品推荐
相关产品推荐

