1 use std::ffi::OsStr;
2 use std::fs::{File, OpenOptions};
3 use std::os::windows::ffi::OsStrExt;
4 use std::os::windows::fs::OpenOptionsExt;
5 use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
6 use std::path::Path;
7 use std::{io, iter};
8 
9 use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
10 use windows_sys::Win32::Storage::FileSystem::{
11     MoveFileExW, ReOpenFile, SetFileAttributesW, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_TEMPORARY,
12     FILE_FLAG_DELETE_ON_CLOSE, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_DELETE,
13     FILE_SHARE_READ, FILE_SHARE_WRITE, MOVEFILE_REPLACE_EXISTING,
14 };
15 
16 use crate::util;
17 
to_utf16(s: &Path) -> Vec<u16>18 fn to_utf16(s: &Path) -> Vec<u16> {
19     s.as_os_str().encode_wide().chain(iter::once(0)).collect()
20 }
21 
not_supported<T>(msg: &str) -> io::Result<T>22 fn not_supported<T>(msg: &str) -> io::Result<T> {
23     Err(io::Error::new(io::ErrorKind::Other, msg))
24 }
25 
create_named( path: &Path, open_options: &mut OpenOptions, permissions: Option<&std::fs::Permissions>, ) -> io::Result<File>26 pub fn create_named(
27     path: &Path,
28     open_options: &mut OpenOptions,
29     permissions: Option<&std::fs::Permissions>,
30 ) -> io::Result<File> {
31     if permissions.map_or(false, |p| p.readonly()) {
32         return not_supported("changing permissions is not supported on this platform");
33     }
34     open_options
35         .create_new(true)
36         .read(true)
37         .write(true)
38         .custom_flags(FILE_ATTRIBUTE_TEMPORARY)
39         .open(path)
40 }
41 
create(dir: &Path) -> io::Result<File>42 pub fn create(dir: &Path) -> io::Result<File> {
43     util::create_helper(
44         dir,
45         OsStr::new(".tmp"),
46         OsStr::new(""),
47         crate::NUM_RAND_CHARS,
48         |path| {
49             OpenOptions::new()
50                 .create_new(true)
51                 .read(true)
52                 .write(true)
53                 .share_mode(0)
54                 .custom_flags(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE)
55                 .open(path)
56         },
57     )
58 }
59 
reopen(file: &File, _path: &Path) -> io::Result<File>60 pub fn reopen(file: &File, _path: &Path) -> io::Result<File> {
61     let handle = file.as_raw_handle();
62     unsafe {
63         let handle = ReOpenFile(
64             handle as HANDLE,
65             FILE_GENERIC_READ | FILE_GENERIC_WRITE,
66             FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
67             0,
68         );
69         if handle == INVALID_HANDLE_VALUE {
70             Err(io::Error::last_os_error())
71         } else {
72             Ok(FromRawHandle::from_raw_handle(handle as RawHandle))
73         }
74     }
75 }
76 
keep(path: &Path) -> io::Result<()>77 pub fn keep(path: &Path) -> io::Result<()> {
78     unsafe {
79         let path_w = to_utf16(path);
80         if SetFileAttributesW(path_w.as_ptr(), FILE_ATTRIBUTE_NORMAL) == 0 {
81             Err(io::Error::last_os_error())
82         } else {
83             Ok(())
84         }
85     }
86 }
87 
persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()>88 pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
89     unsafe {
90         let old_path_w = to_utf16(old_path);
91         let new_path_w = to_utf16(new_path);
92 
93         // Don't succeed if this fails. We don't want to claim to have successfully persisted a file
94         // still marked as temporary because this file won't have the same consistency guarantees.
95         if SetFileAttributesW(old_path_w.as_ptr(), FILE_ATTRIBUTE_NORMAL) == 0 {
96             return Err(io::Error::last_os_error());
97         }
98 
99         let mut flags = 0;
100 
101         if overwrite {
102             flags |= MOVEFILE_REPLACE_EXISTING;
103         }
104 
105         if MoveFileExW(old_path_w.as_ptr(), new_path_w.as_ptr(), flags) == 0 {
106             let e = io::Error::last_os_error();
107             // If this fails, the temporary file is now un-hidden and no longer marked temporary
108             // (slightly less efficient) but it will still work.
109             let _ = SetFileAttributesW(old_path_w.as_ptr(), FILE_ATTRIBUTE_TEMPORARY);
110             Err(e)
111         } else {
112             Ok(())
113         }
114     }
115 }
116