Tempfile: Create temp file without opening it

Hi,

I need a destination temporary file for copying. The tempfile docs isn't big on examples; there is nothing similar to what I really want. This is a snippet of pseudocode I'd like to implement:

let tmp = tempfile:: ???;  // Would be great to specify .ext, too.

let tmp_path = tmp.path();

tmp.close();

fs::copy(src_path, tmp_path).expect( ... );

modify(tmp_path);  // It's brief, third party and wants a closed file.

fs::copy(tmp_path, dst_path).expect( ... );

tmp.remove();

Can I have something like this with tempfile?

  • For setting an extension - use the Builder and in particular, the suffix method to set the extension.
  • As for a "closed" file, I am assuming you're working on Windows, because on modern Unix environments, multiple programs can read and write to the same file simultaneously. Now the way tempfile crate works is that, when you drop the handle, the file is closed and removed. So if you call close, then the file will probably be deleted. The path will probably be still be valid. So you can still probably pass it.

On unix file system single file can be accessed from zero, one or many paths. It seems what you want is some tmp path, not the file itself. fs::copy() doesn't require the dst file exist.

You can make temp path via making tempdir() and append some fixed filename after its path.

1 Like

This is a classic security hole on unixy systems. In between the copying back and forth the file could be replaced by another user. Instead of st::fs::copy, use std::io::copy with the opened file.

The only reason to do what you're asking would be if you need to pass the temporary filename to another program that won't accept it on stdin or stdout. If that's what you're needing to do, then you need a temporary directory.

If you're writing windows-only code, however, I have know clue what advice you should take.

Tbh, I don't really understand the requirements here. How can modify require a closed file? Anything could open the file and you'd be none the wiser unless they lock it in some way.

I tried a temporary directory. Something's wrong, though one cannot tell what right away:

hread 'main' panicked at '🔹Error while copying "/home/alexey/common/Downloads/UpDown/Books/Audio/Vladimir Nabokov - Ada, or ardor (1969)/Disc 1/01 Track 1.mp3" to "/tmp/.tmphqLiSS/tmpaudio.mp3".🔹: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:578:34
stack backtrace:
   0: rust_begin_unwind
             at /rustc/5531927e8af9b99ad923af4c827c91038bca51ee/library/std/src/panicking.rs:498:5
   1: core::panicking::panic_fmt
             at /rustc/5531927e8af9b99ad923af4c827c91038bca51ee/library/core/src/panicking.rs:107:14
   2: core::result::unwrap_failed
             at /rustc/5531927e8af9b99ad923af4c827c91038bca51ee/library/core/src/result.rs:1661:5
   3: procrustes::GlobalState::track_copy::file_copy
   4: procrustes::GlobalState::album_copy
   5: procrustes::main

The paths look quite plausible, just as I expected. The code looks like this:

        fn file_copy(src: &PathBuf, dst: &PathBuf) {
            fs::copy(&src, &dst).expect(
                format!(
                    "{}Error while copying \"{}\" to \"{}\".{}",
                    BDELIM_ICON,
                    &src.to_str().unwrap(),
                    &dst.to_str().unwrap(),
                    BDELIM_ICON,
                )
                .as_str(),
            );
        }

                ...
                let ext = &src.extension().unwrap();
                let tmp_dir = TempDir::new();
                let tmp = tmp_dir
                    .unwrap()
                    .path()
                    .join(format!("tmpaudio.{}", ext.to_str().unwrap()));

                file_copy(&src, &tmp);
                self.track_set_tags(ii, &src, &tmp);
                file_copy(&tmp, &dst);

                fs::remove_file(&tmp).expect(
                    format!(
                        " {} Error while deleting \"{}\" file.",
                        WARNING_ICON,
                        &dst.to_str().unwrap()
                    )
                    .as_str(),
                );
                ...

this code is the issue. tmp_dir.unwrap() moves tmp_dir. Once the expression tmp_dir.unwrap().path().join(format!("tmpaudio.{}", ext.to_str().unwrap())) finishes the TempDir value gets dropped which removes the temporary directory. You can fix it by doing the unwrap before assigning to tmp_dir:

let tmp_dir = TempDir::new().unwrap();
let tmp = tmp_dir
    .path()
    .join(format!("tmpaudio.{}", ext.to_str().unwrap()));

Yes!

The working code:

    /// Copies [src] to [dst], sets tags using a temporary file.
    ///
    fn file_copy_and_set_tags_via_tmp(&mut self, ii: usize, src: &PathBuf, dst: &PathBuf) {
        let ext = &src.extension().unwrap();
        let tmp_dir = TempDir::new().unwrap(); // Keep it!
        let tmp = tmp_dir
            .path()
            .join(format!("tmpaudio.{}", ext.to_str().unwrap()));

        self.file_copy(&src, &tmp);
        self.file_set_tags(ii, &src, &tmp);
        self.file_copy(&tmp, &dst);

        fs::remove_file(&tmp).expect(
            format!(
                "{}Error while deleting \"{}\" file.{}",
                BDELIM_ICON,
                &tmp.to_str().unwrap(),
                BDELIM_ICON,
            )
            .as_str(),
        );
    }

I'm still uncomfortable about it. What's the rule of thumb to avoid this in the future?

Security is not an issue here, fortunately.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.