Five years have passed, time for updates
[symlink] / src / windows / mod.rs
1 pub use std::os::windows::fs::{symlink_file, symlink_dir};
2 pub use std::fs::remove_dir as remove_symlink_dir;
3 use std::fs;
4 use std::io::{self, Error};
5 use std::mem;
6 use std::ffi::OsStr;
7 use std::os::windows::ffi::OsStrExt;
8 use std::os::windows::raw::HANDLE;
9 use std::path::Path;
10 use std::ptr;
11
12 mod c;
13
14 #[inline]
15 pub fn symlink_auto<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
16 if fs::metadata(src.as_ref())?.is_dir() {
17 symlink_dir(src.as_ref(), dst.as_ref())
18 } else {
19 symlink_file(src.as_ref(), dst.as_ref())
20 }
21 }
22
23 #[inline]
24 pub fn remove_symlink_auto<P: AsRef<Path>>(path: P) -> io::Result<()> {
25 // Ideally we’d be able to do fs::metadata(path.as_ref())?.file_type().{is_symlink_dir,
26 // is_symlink}() or similar, but the standard library doesn’t expose that; really, we care
27 // about whether the internal FileType object is a SymlinkFile or a SymlinkDir, but that’s not
28 // exposed in any way, so ☹. Instead, we copy all that mess of code and call the Windows API
29 // directly ourselves. Icky, isn’t it? (The alternative is copying the struct and transmuting;
30 // that’s even more icky, though quite a bit shorter.)
31 match symlink_type(path.as_ref())? {
32 SymlinkType::Dir => fs::remove_dir(path),
33 SymlinkType::File => fs::remove_file(path),
34 SymlinkType::Not => Err(io::Error::new(io::ErrorKind::InvalidInput,
35 "path is not a symlink")),
36 }
37 }
38
39 pub enum SymlinkType {
40 Not,
41 File,
42 Dir,
43 }
44
45 // Taken from rust/src/libstd/sys/windows/mod.rs
46 fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
47 let mut encoded: Vec<u16> = s.as_ref().encode_wide().collect();
48 if encoded.iter().any(|&u| u == 0) {
49 return Err(io::Error::new(io::ErrorKind::InvalidInput,
50 "strings passed to the Windows API cannot contain NULs"));
51 }
52 encoded.push(0);
53 Ok(encoded)
54 }
55
56 // Drawn from rust/src/libstd/sys/windows/fs.rs; derived from stat(path) and File::open(path,
57 // opts).file_attr().file_type().{is_symlink, is_symlink_dir}().
58 pub fn symlink_type(path: &Path) -> io::Result<SymlinkType> {
59 // Derived from File::file_attr, FileAttr::file_type, File::reparse_point, FileType::new,
60 // FileType::is_symlink and FileType::is_symlink_dir (all from libstd/sys/windows/fs.rs).
61 fn symlink_type(handle: HANDLE) -> io::Result<SymlinkType> {
62 unsafe {
63 let mut info: c::BY_HANDLE_FILE_INFORMATION = mem::zeroed();
64 if c::GetFileInformationByHandle(handle, &mut info) == 0 {
65 return Err(io::Error::last_os_error());
66 }
67 if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
68 let mut space = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
69 let mut bytes = 0;
70 if c::DeviceIoControl(handle,
71 c::FSCTL_GET_REPARSE_POINT,
72 ptr::null_mut(),
73 0,
74 space.as_mut_ptr() as *mut _,
75 space.len() as c::DWORD,
76 &mut bytes,
77 ptr::null_mut()) != 0 {
78 let buf = &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER);
79 return Ok(match (info.dwFileAttributes & c::FILE_ATTRIBUTE_DIRECTORY != 0,
80 info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0,
81 buf.ReparseTag) {
82 (_, false, _) => SymlinkType::Not,
83 (false, true, c::IO_REPARSE_TAG_SYMLINK) => SymlinkType::File,
84 (true, true, c::IO_REPARSE_TAG_SYMLINK) => SymlinkType::Dir,
85 (true, true, c::IO_REPARSE_TAG_MOUNT_POINT) => SymlinkType::Dir,
86 (_, true, _) => SymlinkType::Not,
87 });
88
89 }
90 }
91 Ok(SymlinkType::Not)
92 }
93 }
94
95 let path = to_u16s(path)?;
96 let handle = unsafe {
97 c::CreateFileW(path.as_ptr(),
98 0,
99 c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,
100 ptr::null_mut(),
101 c::OPEN_EXISTING,
102 c::FILE_FLAG_BACKUP_SEMANTICS,
103 ptr::null_mut())
104 };
105 if handle == c::INVALID_HANDLE_VALUE {
106 Err(Error::last_os_error())
107 } else {
108 let out = symlink_type(handle);
109 unsafe { let _ = c::CloseHandle(handle); }
110 out
111 }
112 }