Skip to content

std: Consider directory junctions as directories #26929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(|_| ())
}
}
}