Stop transmuting std internals (copy them instead)
authorChris Morgan <me@chrismorgan.info>
committerChris Morgan <me@chrismorgan.info>
This makes things a fair bit more messy, but it does mean that it won’t
suddenly blow up. It’s more maintenance, though. You win some, you lose
some.
src/lib.rs
src/windows/c.rs [new file with mode: 0644]
src/windows/mod.rs [new file with mode: 0644]

index 790cb68a20ad4137f5f5db6f36cc820fbb5c0231..a03d15b303aa5b8a203a246a22cc6ff942bac272 100644 (file)
@@ -12,53 +12,8 @@ use std::io;
 use std::path::Path;
 
 #[cfg(windows)]
-mod internal {
-    pub use std::os::windows::fs::{symlink_file, symlink_dir};
-    pub use std::fs::remove_dir as remove_symlink_dir;
-    use std::fs;
-    use std::io;
-    use std::mem;
-    use std::path::Path;
-
-    #[inline]
-    pub fn symlink_auto<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
-        if fs::metadata(src.as_ref())?.is_dir() {
-            symlink_dir(src.as_ref(), dst.as_ref())
-        } else {
-            symlink_file(src.as_ref(), dst.as_ref())
-        }
-    }
-
-    // Copied from the Rust standard library, src/libstd/sys/windows/fs.rs, because it’s an
-    // implementation detail that isn’t currently exposed in the public interface; I decided to do
-    // it this way rather than depending on the Debug implementation (which likewise could change).
-    #[derive(PartialEq)]
-    enum FileType {
-        Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint,
-    }
-
-    impl FileType {
-        pub fn is_symlink_dir(&self) -> bool {
-            *self == FileType::SymlinkDir || *self == FileType::MountPoint
-        }
-    }
-
-    #[inline]
-    pub fn remove_symlink_auto<P: AsRef<Path>>(path: P) -> io::Result<()> {
-        // We need to know whether it’s wrapping a SymlinkFile or SymlinkDir, but in the interests
-        // of consistency this crucial information is concealed. This is why unsafe transmutation
-        // is necessary: to determine whether it’s a symlink file or a symlink dir.
-        let file_type = fs::metadata(path.as_ref())?.file_type();
-        let fs_imp_file_type = unsafe { mem::transmute::<fs::FileType, FileType>(file_type) };
-        if fs_imp_file_type.is_symlink_dir() {
-            fs::remove_dir(path)
-        } else if file_type.is_symlink() {
-            fs::remove_file(path)
-        } else {
-            Err(io::Error::new(io::ErrorKind::InvalidInput, "path is not a symlink"))
-        }
-    }
-}
+#[path = "windows/mod.rs"]
+mod internal;
 
 #[cfg(any(target_os = "redox", unix))]
 mod internal {
diff --git a/src/windows/c.rs b/src/windows/c.rs
new file mode 100644 (file)
index 0000000..209f5a2
--- /dev/null
@@ -0,0 +1,105 @@
+// These are copied from libstd/sys/windows/c.rs out of necessity.
+// Note that I *can’t* use the winapi crate only, apparently, as it curiously lacks
+// REPARSE_DATA_BUFFER and MAXIMUM_REPARSE_DATA_BUFFER_SIZE (at 0.2.8, anyway). So I just threw in
+// the towel and decided not to use kernel32-sys and winapi at all.
+
+#![allow(non_snake_case, non_camel_case_types)]
+
+use std::os::raw::{c_int, c_uint, c_ushort, c_ulong, c_void};
+use std::os::windows::raw::HANDLE;
+
+pub type WCHAR = u16;
+pub type DWORD = c_ulong;
+pub type BOOL = c_int;
+pub type LPVOID = *mut c_void;
+pub type LPDWORD = *mut DWORD;
+pub type LPCWSTR = *const WCHAR;
+pub type LPOVERLAPPED = *mut OVERLAPPED;
+pub type LPBY_HANDLE_FILE_INFORMATION = *mut BY_HANDLE_FILE_INFORMATION;
+pub type LPSECURITY_ATTRIBUTES = *mut SECURITY_ATTRIBUTES;
+
+pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10;
+pub const FILE_ATTRIBUTE_REPARSE_POINT: DWORD = 0x400;
+
+pub const IO_REPARSE_TAG_SYMLINK: DWORD = 0xa000000c;
+pub const IO_REPARSE_TAG_MOUNT_POINT: DWORD = 0xa0000003;
+
+pub const FILE_SHARE_DELETE: DWORD = 0x4;
+pub const FILE_SHARE_READ: DWORD = 0x1;
+pub const FILE_SHARE_WRITE: DWORD = 0x2;
+
+pub const OPEN_EXISTING: DWORD = 3;
+pub const FILE_FLAG_BACKUP_SEMANTICS: DWORD = 0x02000000;
+
+pub const INVALID_HANDLE_VALUE: HANDLE = !0 as HANDLE;
+
+pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
+pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
+
+#[repr(C)]
+pub struct BY_HANDLE_FILE_INFORMATION {
+    pub dwFileAttributes: DWORD,
+    pub ftCreationTime: FILETIME,
+    pub ftLastAccessTime: FILETIME,
+    pub ftLastWriteTime: FILETIME,
+    pub dwVolumeSerialNumber: DWORD,
+    pub nFileSizeHigh: DWORD,
+    pub nFileSizeLow: DWORD,
+    pub nNumberOfLinks: DWORD,
+    pub nFileIndexHigh: DWORD,
+    pub nFileIndexLow: DWORD,
+}
+
+#[repr(C)]
+pub struct REPARSE_DATA_BUFFER {
+    pub ReparseTag: c_uint,
+    pub ReparseDataLength: c_ushort,
+    pub Reserved: c_ushort,
+    pub rest: (),
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct FILETIME {
+    pub dwLowDateTime: DWORD,
+    pub dwHighDateTime: DWORD,
+}
+
+#[repr(C)]
+pub struct OVERLAPPED {
+    pub Internal: *mut c_ulong,
+    pub InternalHigh: *mut c_ulong,
+    pub Offset: DWORD,
+    pub OffsetHigh: DWORD,
+    pub hEvent: HANDLE,
+}
+
+#[repr(C)]
+pub struct SECURITY_ATTRIBUTES {
+    pub nLength: DWORD,
+    pub lpSecurityDescriptor: LPVOID,
+    pub bInheritHandle: BOOL,
+}
+
+extern "system" {
+    pub fn CreateFileW(lpFileName: LPCWSTR,
+                       dwDesiredAccess: DWORD,
+                       dwShareMode: DWORD,
+                       lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
+                       dwCreationDisposition: DWORD,
+                       dwFlagsAndAttributes: DWORD,
+                       hTemplateFile: HANDLE)
+                       -> HANDLE;
+    pub fn GetFileInformationByHandle(hFile: HANDLE,
+                            lpFileInformation: LPBY_HANDLE_FILE_INFORMATION)
+                            -> BOOL;
+    pub fn DeviceIoControl(hDevice: HANDLE,
+                           dwIoControlCode: DWORD,
+                           lpInBuffer: LPVOID,
+                           nInBufferSize: DWORD,
+                           lpOutBuffer: LPVOID,
+                           nOutBufferSize: DWORD,
+                           lpBytesReturned: LPDWORD,
+                           lpOverlapped: LPOVERLAPPED) -> BOOL;
+    pub fn CloseHandle(hObject: HANDLE) -> BOOL;
+}
diff --git a/src/windows/mod.rs b/src/windows/mod.rs
new file mode 100644 (file)
index 0000000..f3304d3
--- /dev/null
@@ -0,0 +1,112 @@
+pub use std::os::windows::fs::{symlink_file, symlink_dir};
+pub use std::fs::remove_dir as remove_symlink_dir;
+use std::fs;
+use std::io::{self, Error};
+use std::mem;
+use std::ffi::OsStr;
+use std::os::windows::ffi::OsStrExt;
+use std::os::windows::raw::HANDLE;
+use std::path::Path;
+use std::ptr;
+
+mod c;
+
+#[inline]
+pub fn symlink_auto<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
+    if fs::metadata(src.as_ref())?.is_dir() {
+        symlink_dir(src.as_ref(), dst.as_ref())
+    } else {
+        symlink_file(src.as_ref(), dst.as_ref())
+    }
+}
+
+#[inline]
+pub fn remove_symlink_auto<P: AsRef<Path>>(path: P) -> io::Result<()> {
+    // Ideally we’d be able to do fs::metadata(path.as_ref())?.file_type().{is_symlink_dir,
+    // is_symlink}() or similar, but the standard library doesn’t expose that; really, we care
+    // about whether the internal FileType object is a SymlinkFile or a SymlinkDir, but that’s not
+    // exposed in any way, so ☹. Instead, we copy all that mess of code and call the Windows API
+    // directly ourselves. Icky, isn’t it? (The alternative is copying the struct and transmuting;
+    // that’s even more icky, though quite a bit shorter.)
+    match symlink_type(path.as_ref())? {
+        SymlinkType::Dir => fs::remove_dir(path),
+        SymlinkType::File => fs::remove_file(path),
+        SymlinkType::Not => Err(io::Error::new(io::ErrorKind::InvalidInput,
+                                               "path is not a symlink")),
+    }
+}
+
+pub enum SymlinkType {
+    Not,
+    File,
+    Dir,
+}
+
+// Taken from rust/src/libstd/sys/windows/mod.rs
+fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
+    let mut encoded: Vec<u16> = s.as_ref().encode_wide().collect();
+    if encoded.iter().any(|&u| u == 0) {
+        return Err(io::Error::new(io::ErrorKind::InvalidInput,
+                                  "strings passed to the Windows API cannot contain NULs"));
+    }
+    encoded.push(0);
+    Ok(encoded)
+}
+
+// Drawn from rust/src/libstd/sys/windows/fs.rs; derived from stat(path) and File::open(path,
+// opts).file_attr().file_type().{is_symlink, is_symlink_dir}().
+pub fn symlink_type(path: &Path) -> io::Result<SymlinkType> {
+    // Derived from File::file_attr, FileAttr::file_type, File::reparse_point, FileType::new,
+    // FileType::is_symlink and FileType::is_symlink_dir (all from libstd/sys/windows/fs.rs).
+    fn symlink_type(handle: HANDLE) -> io::Result<SymlinkType> {
+        unsafe {
+            let mut info: c::BY_HANDLE_FILE_INFORMATION = mem::zeroed();
+            if c::GetFileInformationByHandle(handle, &mut info) == 0 {
+                return Err(io::Error::last_os_error());
+            }
+            if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+                let mut space = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+                let mut bytes = 0;
+                if c::DeviceIoControl(handle,
+                                    c::FSCTL_GET_REPARSE_POINT,
+                                    ptr::null_mut(),
+                                    0,
+                                    space.as_mut_ptr() as *mut _,
+                                    space.len() as c::DWORD,
+                                    &mut bytes,
+                                    ptr::null_mut()) != 0 {
+                    let buf = &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER);
+                    return Ok(match (info.dwFileAttributes & c::FILE_ATTRIBUTE_DIRECTORY != 0,
+                                    info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0,
+                                    buf.ReparseTag) {
+                        (_, false, _) => SymlinkType::Not,
+                        (false, true, c::IO_REPARSE_TAG_SYMLINK) => SymlinkType::File,
+                        (true, true, c::IO_REPARSE_TAG_SYMLINK) => SymlinkType::Dir,
+                        (true, true, c::IO_REPARSE_TAG_MOUNT_POINT) => SymlinkType::Dir,
+                        (_, true, _) => SymlinkType::Not,
+                    });
+
+                }
+            }
+            Ok(SymlinkType::Not)
+        }
+    }
+
+    let path = to_u16s(path)?;
+    let handle = unsafe {
+        c::CreateFileW(path.as_ptr(),
+                       0,
+                       c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,
+                       ptr::null_mut(),
+                       c::OPEN_EXISTING,
+                       c::FILE_FLAG_BACKUP_SEMANTICS,
+                       ptr::null_mut())
+    };
+    if handle == c::INVALID_HANDLE_VALUE {
+        Err(Error::last_os_error())
+    } else {
+        let out = symlink_type(handle);
+        unsafe { let _ = c::CloseHandle(handle); }
+        out
+    }
+}