File: fs.rs

package info (click to toggle)
rust-remove-dir-all 0.5.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 104 kB
  • sloc: makefile: 2
file content (277 lines) | stat: -rw-r--r-- 10,098 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use std::ffi::OsString;
use std::fs::{self, File, OpenOptions};
use std::os::windows::prelude::*;
use std::path::{Path, PathBuf};
use std::{io, ptr};

use winapi::shared::minwindef::*;
use winapi::shared::winerror::*;
use winapi::um::errhandlingapi::*;
use winapi::um::fileapi::*;
use winapi::um::minwinbase::*;
use winapi::um::winbase::*;
use winapi::um::winnt::*;

pub const VOLUME_NAME_DOS: DWORD = 0x0;

struct RmdirContext<'a> {
    base_dir: &'a Path,
    readonly: bool,
    counter: u64,
}

/// Reliably removes directory and all of it's children.
/// ```rust
/// extern crate remove_dir_all;
///
/// use std::fs;
/// use remove_dir_all::*;
///
/// fn main() {
///     fs::create_dir("./temp/").unwrap();
///     remove_dir_all("./temp/").unwrap();
/// }
/// ```
pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
    // On Windows it is not enough to just recursively remove the contents of a
    // directory and then the directory itself. Deleting does not happen
    // instantaneously, but is scheduled.
    // To work around this, we move the file or directory to some `base_dir`
    // right before deletion to avoid races.
    //
    // As `base_dir` we choose the parent dir of the directory we want to
    // remove. We very probably have permission to create files here, as we
    // already need write permission in this dir to delete the directory. And it
    // should be on the same volume.
    //
    // To handle files with names like `CON` and `morse .. .`,  and when a
    // directory structure is so deep it needs long path names the path is first
    // converted to a `//?/`-path with `get_path()`.
    //
    // To make sure we don't leave a moved file laying around if the process
    // crashes before we can delete the file, we do all operations on an file
    // handle. By opening a file with `FILE_FLAG_DELETE_ON_CLOSE` Windows will
    // always delete the file when the handle closes.
    //
    // All files are renamed to be in the `base_dir`, and have their name
    // changed to "rm-<counter>". After every rename the counter is increased.
    // Rename should not overwrite possibly existing files in the base dir. So
    // if it fails with `AlreadyExists`, we just increase the counter and try
    // again.
    //
    // For read-only files and directories we first have to remove the read-only
    // attribute before we can move or delete them. This also removes the
    // attribute from possible hardlinks to the file, so just before closing we
    // restore the read-only attribute.
    //
    // If 'path' points to a directory symlink or junction we should not
    // recursively remove the target of the link, but only the link itself.
    //
    // Moving and deleting is guaranteed to succeed if we are able to open the
    // file with `DELETE` permission. If others have the file open we only have
    // `DELETE` permission if they have specified `FILE_SHARE_DELETE`. We can
    // also delete the file now, but it will not disappear until all others have
    // closed the file. But no-one can open the file after we have flagged it
    // for deletion.

    // Open the path once to get the canonical path, file type and attributes.
    let (path, metadata) = {
        let path = path.as_ref();
        let mut opts = OpenOptions::new();
        opts.access_mode(FILE_READ_ATTRIBUTES);
        opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
        let file = opts.open(path)?;
        (get_path(&file)?, path.metadata()?)
    };

    let mut ctx = RmdirContext {
        base_dir: match path.parent() {
            Some(dir) => dir,
            None => {
                return Err(io::Error::new(
                    io::ErrorKind::PermissionDenied,
                    "Can't delete root directory",
                ))
            }
        },
        readonly: metadata.permissions().readonly(),
        counter: 0,
    };

    let filetype = metadata.file_type();
    if filetype.is_dir() {
        if !filetype.is_symlink() {
            remove_dir_all_recursive(path.as_ref(), &mut ctx)
        } else {
            remove_item(path.as_ref(), &mut ctx)
        }
    } else {
        Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Not a directory",
        ))
    }
}

fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> {
    if ctx.readonly {
        // remove read-only permision
        let mut permissions = path.metadata()?.permissions();
        permissions.set_readonly(false);

        fs::set_permissions(path, permissions)?;
    }

    let mut opts = OpenOptions::new();
    opts.access_mode(DELETE);
    opts.custom_flags(
        FILE_FLAG_BACKUP_SEMANTICS | // delete directory
                        FILE_FLAG_OPEN_REPARSE_POINT | // delete symlink
                        FILE_FLAG_DELETE_ON_CLOSE,
    );
    let file = opts.open(path)?;
    move_item(&file, ctx)?;

    if ctx.readonly {
        // restore read-only flag just in case there are other hard links
        match fs::metadata(&path) {
            Ok(metadata) => {
                let mut perm = metadata.permissions();
                perm.set_readonly(true);
                fs::set_permissions(&path, perm)?;
            }
            Err(ref err) if err.kind() == io::ErrorKind::NotFound => {}
            err => return err.map(|_| ()),
        }
    }

    Ok(())
}

fn move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()> {
    let mut tmpname = ctx.base_dir.join(format! {"rm-{}", ctx.counter});
    ctx.counter += 1;

    // Try to rename the file. If it already exists, just retry with an other
    // filename.
    while let Err(err) = rename(file, &tmpname, false) {
        if err.kind() != io::ErrorKind::AlreadyExists {
            return Err(err);
        };
        tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter));
        ctx.counter += 1;
    }

    Ok(())
}

fn rename(file: &File, new: &Path, replace: bool) -> io::Result<()> {
    // &self must be opened with DELETE permission
    use std::iter;
    #[cfg(target_pointer_width = "32")]
    const STRUCT_SIZE: usize = 12;
    #[cfg(target_pointer_width = "64")]
    const STRUCT_SIZE: usize = 20;

    // FIXME: check for internal NULs in 'new'
    let mut data: Vec<u16> = iter::repeat(0u16)
        .take(STRUCT_SIZE / 2)
        .chain(new.as_os_str().encode_wide())
        .collect();
    data.push(0);
    let size = data.len() * 2;

    unsafe {
        // Thanks to alignment guarantees on Windows this works
        // (8 for 32-bit and 16 for 64-bit)
        let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO;
        // The type of ReplaceIfExists is BOOL, but it actually expects a
        // BOOLEAN. This means true is -1, not c::TRUE.
        (*info).ReplaceIfExists = if replace { -1 } else { FALSE };
        (*info).RootDirectory = ptr::null_mut();
        (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD;
        let result = SetFileInformationByHandle(
            file.as_raw_handle(),
            FileRenameInfo,
            data.as_mut_ptr() as *mut _ as *mut _,
            size as DWORD,
        );

        if result == 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(())
        }
    }
}

fn get_path(f: &File) -> io::Result<PathBuf> {
    fill_utf16_buf(
        |buf, sz| unsafe { GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, VOLUME_NAME_DOS) },
        |buf| PathBuf::from(OsString::from_wide(buf)),
    )
}

fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> {
    let dir_readonly = ctx.readonly;
    for child in fs::read_dir(path)? {
        let child = child?;
        let child_type = child.file_type()?;
        ctx.readonly = child.metadata()?.permissions().readonly();
        if child_type.is_dir() {
            remove_dir_all_recursive(&child.path(), ctx)?;
        } else {
            remove_item(&child.path().as_ref(), ctx)?;
        }
    }
    ctx.readonly = dir_readonly;
    remove_item(path, ctx)
}

fn fill_utf16_buf<F1, F2, T>(mut f1: F1, f2: F2) -> io::Result<T>
where
    F1: FnMut(*mut u16, DWORD) -> DWORD,
    F2: FnOnce(&[u16]) -> T,
{
    // Start off with a stack buf but then spill over to the heap if we end up
    // needing more space.
    let mut stack_buf = [0u16; 512];
    let mut heap_buf = Vec::new();
    unsafe {
        let mut n = stack_buf.len();

        loop {
            let buf = if n <= stack_buf.len() {
                &mut stack_buf[..]
            } else {
                let extra = n - heap_buf.len();
                heap_buf.reserve(extra);
                heap_buf.set_len(n);
                &mut heap_buf[..]
            };

            // This function is typically called on windows API functions which
            // will return the correct length of the string, but these functions
            // also return the `0` on error. In some cases, however, the
            // returned "correct length" may actually be 0!
            //
            // To handle this case we call `SetLastError` to reset it to 0 and
            // then check it again if we get the "0 error value". If the "last
            // error" is still 0 then we interpret it as a 0 length buffer and
            // not an actual error.
            SetLastError(0);
            let k = match f1(buf.as_mut_ptr(), n as DWORD) {
                0 if GetLastError() == 0 => 0,
                0 => return Err(io::Error::last_os_error()),
                n => n,
            } as usize;
            if k == n && GetLastError() == ERROR_INSUFFICIENT_BUFFER {
                n *= 2;
            } else if k >= n {
                n = k;
            } else {
                return Ok(f2(&buf[..k]));
            }
        }
    }
}