mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-10 19:16:51 +00:00
Auto merge of #26929 - alexcrichton:windows-dir-junction, r=brson
Previously on Windows a directory junction would return false from `is_dir`, causing various odd behavior, specifically calls to `create_dir_all` might fail when they would otherwise continue to succeed. Closes #26716
This commit is contained in:
commit
64db3aac0f
@ -53,9 +53,13 @@ pub const WSA_FLAG_NO_HANDLE_INHERIT: libc::DWORD = 0x80;
|
||||
pub const ERROR_NO_MORE_FILES: libc::DWORD = 18;
|
||||
pub const TOKEN_READ: libc::DWORD = 0x20008;
|
||||
pub const FILE_FLAG_OPEN_REPARSE_POINT: libc::DWORD = 0x00200000;
|
||||
pub const FILE_FLAG_BACKUP_SEMANTICS: libc::DWORD = 0x02000000;
|
||||
pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
|
||||
pub const FSCTL_GET_REPARSE_POINT: libc::DWORD = 0x900a8;
|
||||
pub const IO_REPARSE_TAG_SYMLINK: libc::DWORD = 0xa000000c;
|
||||
pub const IO_REPARSE_TAG_MOUNT_POINT: libc::DWORD = 0xa0000003;
|
||||
pub const FSCTL_SET_REPARSE_POINT: libc::DWORD = 0x900a4;
|
||||
pub const FSCTL_DELETE_REPARSE_POINT: libc::DWORD = 0x900ac;
|
||||
|
||||
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: libc::DWORD = 0x1;
|
||||
|
||||
@ -71,6 +75,9 @@ pub const PROGRESS_CANCEL: libc::DWORD = 1;
|
||||
pub const PROGRESS_STOP: libc::DWORD = 2;
|
||||
pub const PROGRESS_QUIET: libc::DWORD = 3;
|
||||
|
||||
pub const TOKEN_ADJUST_PRIVILEGES: libc::DWORD = 0x0020;
|
||||
pub const SE_PRIVILEGE_ENABLED: libc::DWORD = 2;
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub struct WSADATA {
|
||||
@ -287,6 +294,40 @@ pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = CONDITION_VARIABLE {
|
||||
};
|
||||
pub const SRWLOCK_INIT: SRWLOCK = SRWLOCK { ptr: 0 as *mut _ };
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LUID {
|
||||
pub LowPart: libc::DWORD,
|
||||
pub HighPart: libc::c_long,
|
||||
}
|
||||
|
||||
pub type PLUID = *mut LUID;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct TOKEN_PRIVILEGES {
|
||||
pub PrivilegeCount: libc::DWORD,
|
||||
pub Privileges: [LUID_AND_ATTRIBUTES; 1],
|
||||
}
|
||||
|
||||
pub type PTOKEN_PRIVILEGES = *mut TOKEN_PRIVILEGES;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LUID_AND_ATTRIBUTES {
|
||||
pub Luid: LUID,
|
||||
pub Attributes: libc::DWORD,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct REPARSE_MOUNTPOINT_DATA_BUFFER {
|
||||
pub ReparseTag: libc::DWORD,
|
||||
pub ReparseDataLength: libc::DWORD,
|
||||
pub Reserved: libc::WORD,
|
||||
pub ReparseTargetLength: libc::WORD,
|
||||
pub ReparseTargetMaximumLength: libc::WORD,
|
||||
pub Reserved1: libc::WORD,
|
||||
pub ReparseTarget: libc::WCHAR,
|
||||
}
|
||||
|
||||
|
||||
#[link(name = "ws2_32")]
|
||||
#[link(name = "userenv")]
|
||||
extern "system" {
|
||||
@ -437,6 +478,15 @@ extern "system" {
|
||||
lpData: libc::LPVOID,
|
||||
pbCancel: LPBOOL,
|
||||
dwCopyFlags: libc::DWORD) -> libc::BOOL;
|
||||
pub fn LookupPrivilegeValueW(lpSystemName: libc::LPCWSTR,
|
||||
lpName: libc::LPCWSTR,
|
||||
lpLuid: PLUID) -> libc::BOOL;
|
||||
pub fn AdjustTokenPrivileges(TokenHandle: libc::HANDLE,
|
||||
DisableAllPrivileges: libc::BOOL,
|
||||
NewState: PTOKEN_PRIVILEGES,
|
||||
BufferLength: libc::DWORD,
|
||||
PreviousState: PTOKEN_PRIVILEGES,
|
||||
ReturnLength: *mut libc::DWORD) -> libc::BOOL;
|
||||
}
|
||||
|
||||
// Functions that aren't available on Windows XP, but we still use them and just
|
||||
|
@ -30,12 +30,12 @@ pub struct File { handle: Handle }
|
||||
|
||||
pub struct FileAttr {
|
||||
data: c::WIN32_FILE_ATTRIBUTE_DATA,
|
||||
is_symlink: bool,
|
||||
reparse_tag: libc::DWORD,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum FileType {
|
||||
Dir, File, Symlink, ReparsePoint
|
||||
Dir, File, Symlink, ReparsePoint, MountPoint,
|
||||
}
|
||||
|
||||
pub struct ReadDir {
|
||||
@ -133,7 +133,7 @@ impl DirEntry {
|
||||
|
||||
pub fn file_type(&self) -> io::Result<FileType> {
|
||||
Ok(FileType::new(self.data.dwFileAttributes,
|
||||
self.data.dwReserved0 == c::IO_REPARSE_TAG_SYMLINK))
|
||||
/* reparse_tag = */ self.data.dwReserved0))
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> io::Result<FileAttr> {
|
||||
@ -146,7 +146,7 @@ impl DirEntry {
|
||||
nFileSizeHigh: self.data.nFileSizeHigh,
|
||||
nFileSizeLow: self.data.nFileSizeLow,
|
||||
},
|
||||
is_symlink: self.data.dwReserved0 == c::IO_REPARSE_TAG_SYMLINK,
|
||||
reparse_tag: self.data.dwReserved0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -218,10 +218,12 @@ impl OpenOptions {
|
||||
}
|
||||
|
||||
impl File {
|
||||
fn open_reparse_point(path: &Path) -> io::Result<File> {
|
||||
fn open_reparse_point(path: &Path, write: bool) -> io::Result<File> {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.read(true);
|
||||
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
opts.read(!write);
|
||||
opts.write(write);
|
||||
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT |
|
||||
c::FILE_FLAG_BACKUP_SEMANTICS);
|
||||
File::open(path, &opts)
|
||||
}
|
||||
|
||||
@ -278,10 +280,13 @@ impl File {
|
||||
nFileSizeHigh: info.nFileSizeHigh,
|
||||
nFileSizeLow: info.nFileSizeLow,
|
||||
},
|
||||
is_symlink: false,
|
||||
reparse_tag: 0,
|
||||
};
|
||||
if attr.is_reparse_point() {
|
||||
attr.is_symlink = self.is_symlink();
|
||||
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
if let Ok((_, buf)) = self.reparse_point(&mut b) {
|
||||
attr.reparse_tag = buf.ReparseTag;
|
||||
}
|
||||
}
|
||||
Ok(attr)
|
||||
}
|
||||
@ -314,15 +319,11 @@ impl File {
|
||||
|
||||
pub fn handle(&self) -> &Handle { &self.handle }
|
||||
|
||||
fn is_symlink(&self) -> bool {
|
||||
self.readlink().is_ok()
|
||||
}
|
||||
|
||||
fn readlink(&self) -> io::Result<PathBuf> {
|
||||
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
let mut bytes = 0;
|
||||
|
||||
fn reparse_point<'a>(&self,
|
||||
space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE])
|
||||
-> io::Result<(libc::DWORD, &'a c::REPARSE_DATA_BUFFER)> {
|
||||
unsafe {
|
||||
let mut bytes = 0;
|
||||
try!(cvt({
|
||||
c::DeviceIoControl(self.handle.raw(),
|
||||
c::FSCTL_GET_REPARSE_POINT,
|
||||
@ -333,12 +334,20 @@ impl File {
|
||||
&mut bytes,
|
||||
0 as *mut _)
|
||||
}));
|
||||
let buf: *const c::REPARSE_DATA_BUFFER = space.as_ptr() as *const _;
|
||||
if (*buf).ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
|
||||
}
|
||||
Ok((bytes, &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER)))
|
||||
}
|
||||
}
|
||||
|
||||
fn readlink(&self) -> io::Result<PathBuf> {
|
||||
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
let (_bytes, buf) = try!(self.reparse_point(&mut space));
|
||||
if buf.ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
|
||||
&(*buf).rest as *const _ as *const _;
|
||||
&buf.rest as *const _ as *const _;
|
||||
let path_buffer = &(*info).PathBuffer as *const _ as *const u16;
|
||||
let subst_off = (*info).SubstituteNameOffset / 2;
|
||||
let subst_ptr = path_buffer.offset(subst_off as isize);
|
||||
@ -383,7 +392,7 @@ impl FileAttr {
|
||||
pub fn attrs(&self) -> u32 { self.data.dwFileAttributes as u32 }
|
||||
|
||||
pub fn file_type(&self) -> FileType {
|
||||
FileType::new(self.data.dwFileAttributes, self.is_symlink)
|
||||
FileType::new(self.data.dwFileAttributes, self.reparse_tag)
|
||||
}
|
||||
|
||||
pub fn created(&self) -> u64 { self.to_u64(&self.data.ftCreationTime) }
|
||||
@ -414,12 +423,12 @@ impl FilePermissions {
|
||||
}
|
||||
|
||||
impl FileType {
|
||||
fn new(attrs: libc::DWORD, is_symlink: bool) -> FileType {
|
||||
fn new(attrs: libc::DWORD, reparse_tag: libc::DWORD) -> FileType {
|
||||
if attrs & libc::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||
if is_symlink {
|
||||
FileType::Symlink
|
||||
} else {
|
||||
FileType::ReparsePoint
|
||||
match reparse_tag {
|
||||
c::IO_REPARSE_TAG_SYMLINK => FileType::Symlink,
|
||||
c::IO_REPARSE_TAG_MOUNT_POINT => FileType::MountPoint,
|
||||
_ => FileType::ReparsePoint,
|
||||
}
|
||||
} else if attrs & c::FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
FileType::Dir
|
||||
@ -430,7 +439,9 @@ impl FileType {
|
||||
|
||||
pub fn is_dir(&self) -> bool { *self == FileType::Dir }
|
||||
pub fn is_file(&self) -> bool { *self == FileType::File }
|
||||
pub fn is_symlink(&self) -> bool { *self == FileType::Symlink }
|
||||
pub fn is_symlink(&self) -> bool {
|
||||
*self == FileType::Symlink || *self == FileType::MountPoint
|
||||
}
|
||||
}
|
||||
|
||||
impl DirBuilder {
|
||||
@ -488,7 +499,7 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
|
||||
}
|
||||
|
||||
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
|
||||
let file = try!(File::open_reparse_point(p));
|
||||
let file = try!(File::open_reparse_point(p, false));
|
||||
file.readlink()
|
||||
}
|
||||
|
||||
@ -517,8 +528,15 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
|
||||
pub fn stat(p: &Path) -> io::Result<FileAttr> {
|
||||
let attr = try!(lstat(p));
|
||||
if attr.data.dwFileAttributes & libc::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||
let opts = OpenOptions::new();
|
||||
|
||||
// If this is a reparse point, then we need to reopen the file to get the
|
||||
// actual destination. We also pass the FILE_FLAG_BACKUP_SEMANTICS flag to
|
||||
// ensure that we can open directories (this path may be a directory
|
||||
// junction). Once the file is opened we ask the opened handle what its
|
||||
// metadata information is.
|
||||
if attr.is_reparse_point() {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
|
||||
let file = try!(File::open(p, &opts));
|
||||
file.file_attr()
|
||||
} else {
|
||||
@ -534,9 +552,10 @@ pub fn lstat(p: &Path) -> io::Result<FileAttr> {
|
||||
c::GetFileExInfoStandard,
|
||||
&mut attr.data as *mut _ as *mut _)));
|
||||
if attr.is_reparse_point() {
|
||||
attr.is_symlink = File::open_reparse_point(p).map(|f| {
|
||||
f.is_symlink()
|
||||
}).unwrap_or(false);
|
||||
attr.reparse_tag = File::open_reparse_point(p, false).and_then(|f| {
|
||||
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
f.reparse_point(&mut b).map(|(_, b)| b.ReparseTag)
|
||||
}).unwrap_or(0);
|
||||
}
|
||||
Ok(attr)
|
||||
}
|
||||
@ -600,3 +619,124 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
|
||||
}));
|
||||
Ok(size as u64)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_junctions_are_directories() {
|
||||
use ffi::OsStr;
|
||||
use env;
|
||||
use rand::{self, StdRng, Rng};
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with: {}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
let d = DirBuilder::new();
|
||||
let p = env::temp_dir();
|
||||
let mut r = rand::thread_rng();
|
||||
let ret = p.join(&format!("rust-{}", r.next_u32()));
|
||||
let foo = ret.join("foo");
|
||||
let bar = ret.join("bar");
|
||||
t!(d.mkdir(&ret));
|
||||
t!(d.mkdir(&foo));
|
||||
t!(d.mkdir(&bar));
|
||||
|
||||
t!(create_junction(&bar, &foo));
|
||||
let metadata = stat(&bar);
|
||||
t!(delete_junction(&bar));
|
||||
|
||||
t!(rmdir(&foo));
|
||||
t!(rmdir(&bar));
|
||||
t!(rmdir(&ret));
|
||||
|
||||
let metadata = t!(metadata);
|
||||
assert!(metadata.file_type().is_dir());
|
||||
|
||||
// Creating a directory junction on windows involves dealing with reparse
|
||||
// points and the DeviceIoControl function, and this code is a skeleton of
|
||||
// what can be found here:
|
||||
//
|
||||
// http://www.flexhex.com/docs/articles/hard-links.phtml
|
||||
fn create_junction(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
let f = try!(opendir(src, true));
|
||||
let h = f.handle().raw();
|
||||
|
||||
unsafe {
|
||||
let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
let mut db = data.as_mut_ptr()
|
||||
as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER;
|
||||
let mut buf = &mut (*db).ReparseTarget as *mut _;
|
||||
let mut i = 0;
|
||||
let v = br"\??\";
|
||||
let v = v.iter().map(|x| *x as u16);
|
||||
for c in v.chain(dst.as_os_str().encode_wide()) {
|
||||
*buf.offset(i) = c;
|
||||
i += 1;
|
||||
}
|
||||
*buf.offset(i) = 0;
|
||||
i += 1;
|
||||
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
|
||||
(*db).ReparseTargetMaximumLength = (i * 2) as libc::WORD;
|
||||
(*db).ReparseTargetLength = ((i - 1) * 2) as libc::WORD;
|
||||
(*db).ReparseDataLength =
|
||||
(*db).ReparseTargetLength as libc::DWORD + 12;
|
||||
|
||||
let mut ret = 0;
|
||||
cvt(c::DeviceIoControl(h as *mut _,
|
||||
c::FSCTL_SET_REPARSE_POINT,
|
||||
data.as_ptr() as *mut _,
|
||||
(*db).ReparseDataLength + 8,
|
||||
0 as *mut _, 0,
|
||||
&mut ret,
|
||||
0 as *mut _)).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
fn opendir(p: &Path, write: bool) -> io::Result<File> {
|
||||
unsafe {
|
||||
let mut token = 0 as *mut _;
|
||||
let mut tp: c::TOKEN_PRIVILEGES = mem::zeroed();
|
||||
try!(cvt(c::OpenProcessToken(c::GetCurrentProcess(),
|
||||
c::TOKEN_ADJUST_PRIVILEGES,
|
||||
&mut token)));
|
||||
let name: &OsStr = if write {
|
||||
"SeRestorePrivilege".as_ref()
|
||||
} else {
|
||||
"SeBackupPrivilege".as_ref()
|
||||
};
|
||||
let name = name.encode_wide().chain(Some(0)).collect::<Vec<_>>();
|
||||
try!(cvt(c::LookupPrivilegeValueW(0 as *const _,
|
||||
name.as_ptr(),
|
||||
&mut tp.Privileges[0].Luid)));
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Attributes = c::SE_PRIVILEGE_ENABLED;
|
||||
let size = mem::size_of::<c::TOKEN_PRIVILEGES>() as libc::DWORD;
|
||||
try!(cvt(c::AdjustTokenPrivileges(token, libc::FALSE, &mut tp, size,
|
||||
0 as *mut _, 0 as *mut _)));
|
||||
try!(cvt(libc::CloseHandle(token)));
|
||||
|
||||
File::open_reparse_point(p, write)
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_junction(p: &Path) -> io::Result<()> {
|
||||
unsafe {
|
||||
let f = try!(opendir(p, true));
|
||||
let h = f.handle().raw();
|
||||
let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
let mut db = data.as_mut_ptr()
|
||||
as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER;
|
||||
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
|
||||
let mut bytes = 0;
|
||||
cvt(c::DeviceIoControl(h as *mut _,
|
||||
c::FSCTL_DELETE_REPARSE_POINT,
|
||||
data.as_ptr() as *mut _,
|
||||
(*db).ReparseDataLength + 8,
|
||||
0 as *mut _, 0,
|
||||
&mut bytes,
|
||||
0 as *mut _)).map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user