mdsteele / rust-cfb

Rust library for reading/writing Compound File Binary (structured storage) files
MIT License
44 stars 20 forks source link

Stream appending seems not to work, and `SeekFrom::Start(0)` results in a panic #11

Closed Qqwy closed 2 years ago

Qqwy commented 2 years ago

Given the following simple example:

// This uses the dependencies:
// cfb = "0.6.1"
// anyhow = "1.0.53"

use cfb;
use std::path::Path;
use anyhow::Result;
use std::io::{Seek, Read, Write, SeekFrom};

pub fn main() {
    let path = "/tmp/example.cfbfile";
    let mut comp = open(path).or_else(|_| create_new(path)).unwrap();
    let data = vec![42, 43, 44, 45];
    {
        let mut stream = comp.open_stream("one").unwrap();
        stream.seek(SeekFrom::End(0)).unwrap();
        stream.write_all(&data).unwrap();

        // Comment the following four lines to prevent the panic. However, the data in the stream will not be appended.
        let mut buffer = Vec::new();
        stream.seek(SeekFrom::Start(0)).unwrap();
        stream.read_to_end(&mut buffer).unwrap();
        println!("Data after writing: {:?}", buffer);
    }
    comp.flush().unwrap();
}

fn open<P: AsRef<Path>>(path: P) -> Result<cfb::CompoundFile<std::fs::File>> {
    println!("Opening existing cfb file");
    let mut comp = cfb::open(path)?;
    {
        let mut stream = comp.open_stream("one")?;
        let mut buffer = Vec::new();
        stream.read_to_end(&mut buffer).unwrap();
        println!("Found data: {:?}", buffer);
    }
    comp.flush()?;

    Ok(comp)
}

fn create_new<P: AsRef<Path>>(path: P) -> Result<cfb::CompoundFile<std::fs::File>> {
    println!("Creating cfb file");
    let mut comp = cfb::create(path)?;
    {
        let _stream = comp.create_stream("one")?;
    }
    comp.flush()?;
    Ok(comp)
}

Expected result:

Upon running the code for the first time, I expect the file /tmp/example.cfbfile to be created, containing one stream named "one", whose contents is the byte sequence [42, 43, 44, 45]. Upon running it one more time, I expect it to open the same file, and append to the existing stream. Its contents should now be [42, 43, 44, 45, 42, 43, 44, 45]

When running the program more times, this appending should continue.

Actual result

A panic on the stream.seek(SeekFrom::Start(0)).unwrap(); line.

To be exact:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 9, kind: Uncategorized, message: "Bad file descriptor" }', examples/cfb.rs:16:41

This panic occurs on the code I had added to debug what was going on. When commenting the lines re-reading the stream after writing to it, the panic is prevented but the problem I originally encountered is visible: The stream will not get the new data appended to it. Instead, it seems to have its data replaced.


What is going wrong here? Am I making a mistake in using the library, or is there some internal problem?

mdsteele commented 2 years ago

After trying running the code you've pasted above, I think the problem is that you're using cfb::open(), which opens the file in read-only mode (to match the behavior of std::fs::File::open()), rather than cfb::open_rw(), which opens the file in read-write mode. So if the temp file already exists, your code will open the existing file in read-only mode, then fail with a "Bad file descriptor" error when it tries to update it.

Once I replace cfb::open() with cfb::open_rw() in your example code, it seems to work fine for me.

It's unfortunate that there's no type-level distinction between read-only and writable files—a read-only std::fs::File still supports the std::io::Write trait—since it means there's no compile-time error here.