From: Chris Morgan Date: Thu, 26 Jan 2017 12:15:14 +0000 (+0530) Subject: Stop transmuting std internals (copy them instead) X-Git-Url: https://git.chrismorgan.info/symlink/commitdiff_plain/f9fef0c05d2594bcfa763810046a473403d1cb53 Stop transmuting std internals (copy them instead) 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. --- diff --git a/src/lib.rs b/src/lib.rs index 790cb68..a03d15b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, Q: AsRef>(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>(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::(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 index 0000000..209f5a2 --- /dev/null +++ b/src/windows/c.rs @@ -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 index 0000000..f3304d3 --- /dev/null +++ b/src/windows/mod.rs @@ -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, Q: AsRef>(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>(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: S) -> io::Result> { + let mut encoded: Vec = 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 { + // 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 { + 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 + } +}