I'm using own functions but want tmp file to be dropped automatically, and looked at tmpfile
crate, but it seems to have everything a bit not connected.
My use case:
- generate tmp file name, to avoid collisions, and with spcific suffix.
- call
my_saving_func(&fixture_data, &path)
, let it save the data
- read the data with the tested structure:
TestedStruct::from_path(&path)
Specific requirement: I could have modified the extension with std Path library, but it can only modify the last bit, while I need a chain of extensions, e.g. "my_file.osm.gz" => "my_file_2.osm.gz"
. This is a lot more tedious. Plus I'll need an entire struct and other stuff.
So I looked at tmpfile
and surprisingly I can't do it -- it only allows to create a tmp file and give a writer/reader object, but not just produce a path.
Is there an easier way for me to go?
It can produce a path, provided you use NamedTempFile
. However, while you can specify a prefix, you can't seem to specify a suffix. I don't think most temp file code expects people to have such exacting requirements.
Aside: the reason the default temp files won't give you a path is because on some systems (i.e. non-Windows), the temp file doesn't have a path. That's part of trying to make sure another process doesn't come along and intercept your temp file, potentially interfering with your program.
Maybe you want to create a temporary directory with tempdir
, then create files in that?
6 Likes
Yes, a tmp directory was a good option, but in the end I just wrapped my code in a structure and added a function to mutate filename the way I needed.
The only inconvenience is making it compatible with functions that take &str
.
impl TmpFile {
pub fn new(path: &str) -> Self {
let mut pb = PathBuf::from(path.to_owned());
let old_name = pb.file_name().unwrap().to_owned();
let mut iter = old_name.to_str().unwrap().split(".");
let first_part = iter.next().unwrap();
let other_parts = iter.collect::<Vec<&str>>();
let last_part = if other_parts.len() == 0 { "".to_owned() } else { other_parts.join(".") };
let mut cnt:usize = 0;
while pb.exists() {
cnt += 1;
let test_name = format!("{first_part}_{cnt}{last_part}");
pb.set_file_name(test_name);
}
Self { path: pb.clone(), owned: pb.to_str().unwrap().to_owned() }
}
pub fn exists(&self) -> bool {
self.path.exists()
}
}
impl AsRef<Path> for TmpFile {
fn as_ref(&self) -> &Path {
self.owned.as_ref()
}
}
impl AsRef<str> for TmpFile {
fn as_ref(&self) -> &str {
self.owned.as_ref()
}
}
impl Drop for TmpFile {
fn drop(&mut self) {
use std::fs::remove_file;
if self.path.exists() {
remove_file(self.path.as_os_str()).unwrap();
}
}
}
You have a (probably minor) potential security/denial of service vulnerability in that code: you check to see if the name already exists and, if it doesn't, create the file.
What you're meant to do is guess a name, create it with flags that cause the creation to fail if it already exists, and if it fails try the next name. This is to prevent another process from creating the file between you checking for it and creating it. Depending on your platform/creation flags, this can lead to the program being able to manipulate the file contents or just prevent your program from running.
This is also what something like tmpfile
does for you, along with any other relevant platform-specific protections.
Actually, that might be a better solution: fork tmpfile
, extract the code for creating temporary files, and modify it so you can control the suffix as you please.
2 Likes