Skip to content

Commit

Permalink
Auto merge of #26929 - alexcrichton:windows-dir-junction, r=brson
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed Jul 11, 2015
2 parents 98dcd5e + 72750c7 commit 64db3aa
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 35 deletions.
50 changes: 50 additions & 0 deletions src/libstd/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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
Expand Down
210 changes: 175 additions & 35 deletions src/libstd/sys/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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> {
Expand All @@ -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,
})
}
}
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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(|_| ())
}
}
}

0 comments on commit 64db3aa

Please sign in to comment.