git_tempfile/
forksafe.rs

1use std::{io::Write, path::Path};
2
3use tempfile::{NamedTempFile, TempPath};
4
5use crate::{handle, AutoRemove};
6
7enum TempfileOrTemppath {
8    Tempfile(NamedTempFile),
9    Temppath(TempPath),
10}
11
12pub(crate) struct ForksafeTempfile {
13    inner: TempfileOrTemppath,
14    cleanup: AutoRemove,
15    pub owning_process_id: u32,
16}
17
18impl ForksafeTempfile {
19    pub fn new(tempfile: NamedTempFile, cleanup: AutoRemove, mode: handle::Mode) -> Self {
20        use handle::Mode::*;
21        ForksafeTempfile {
22            inner: match mode {
23                Closed => TempfileOrTemppath::Temppath(tempfile.into_temp_path()),
24                Writable => TempfileOrTemppath::Tempfile(tempfile),
25            },
26            cleanup,
27            owning_process_id: std::process::id(),
28        }
29    }
30}
31
32impl ForksafeTempfile {
33    pub fn as_mut_tempfile(&mut self) -> Option<&mut NamedTempFile> {
34        match &mut self.inner {
35            TempfileOrTemppath::Tempfile(file) => Some(file),
36            TempfileOrTemppath::Temppath(_) => None,
37        }
38    }
39    pub fn close(self) -> Self {
40        if let TempfileOrTemppath::Tempfile(file) = self.inner {
41            ForksafeTempfile {
42                inner: TempfileOrTemppath::Temppath(file.into_temp_path()),
43                cleanup: self.cleanup,
44                owning_process_id: self.owning_process_id,
45            }
46        } else {
47            self
48        }
49    }
50    pub fn persist(mut self, path: impl AsRef<Path>) -> Result<Option<std::fs::File>, (std::io::Error, Self)> {
51        match self.inner {
52            TempfileOrTemppath::Tempfile(file) => match file.persist(path) {
53                Ok(file) => Ok(Some(file)),
54                Err(err) => Err((err.error, {
55                    self.inner = TempfileOrTemppath::Tempfile(err.file);
56                    self
57                })),
58            },
59            TempfileOrTemppath::Temppath(temppath) => match temppath.persist(path) {
60                Ok(_) => Ok(None),
61                Err(err) => Err((err.error, {
62                    self.inner = TempfileOrTemppath::Temppath(err.path);
63                    self
64                })),
65            },
66        }
67    }
68
69    pub fn into_temppath(self) -> TempPath {
70        match self.inner {
71            TempfileOrTemppath::Tempfile(file) => file.into_temp_path(),
72            TempfileOrTemppath::Temppath(path) => path,
73        }
74    }
75    pub fn into_tempfile(self) -> Option<NamedTempFile> {
76        match self.inner {
77            TempfileOrTemppath::Tempfile(file) => Some(file),
78            TempfileOrTemppath::Temppath(_) => None,
79        }
80    }
81    pub fn drop_impl(self) {
82        let file_path = match self.inner {
83            TempfileOrTemppath::Tempfile(file) => file.path().to_owned(),
84            TempfileOrTemppath::Temppath(path) => path.to_path_buf(),
85        };
86        let parent_directory = file_path.parent().expect("every tempfile has a parent directory");
87        self.cleanup.execute_best_effort(parent_directory);
88    }
89
90    pub fn drop_without_deallocation(self) {
91        let temppath = match self.inner {
92            TempfileOrTemppath::Tempfile(file) => {
93                let (mut file, temppath) = file.into_parts();
94                file.flush().ok();
95                temppath
96            }
97            TempfileOrTemppath::Temppath(path) => path,
98        };
99        std::fs::remove_file(&temppath).ok();
100        std::mem::forget(
101            self.cleanup
102                .execute_best_effort(temppath.parent().expect("every file has a directory")),
103        );
104        std::mem::forget(temppath); // leak memory to prevent deallocation
105    }
106}