Skip to content

Commit 64db3aa

Browse files
committed
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
2 parents 98dcd5e + 72750c7 commit 64db3aa

File tree

2 files changed

+225
-35
lines changed

2 files changed

+225
-35
lines changed

src/libstd/sys/windows/c.rs

+50
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ pub const WSA_FLAG_NO_HANDLE_INHERIT: libc::DWORD = 0x80;
5353
pub const ERROR_NO_MORE_FILES: libc::DWORD = 18;
5454
pub const TOKEN_READ: libc::DWORD = 0x20008;
5555
pub const FILE_FLAG_OPEN_REPARSE_POINT: libc::DWORD = 0x00200000;
56+
pub const FILE_FLAG_BACKUP_SEMANTICS: libc::DWORD = 0x02000000;
5657
pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
5758
pub const FSCTL_GET_REPARSE_POINT: libc::DWORD = 0x900a8;
5859
pub const IO_REPARSE_TAG_SYMLINK: libc::DWORD = 0xa000000c;
60+
pub const IO_REPARSE_TAG_MOUNT_POINT: libc::DWORD = 0xa0000003;
61+
pub const FSCTL_SET_REPARSE_POINT: libc::DWORD = 0x900a4;
62+
pub const FSCTL_DELETE_REPARSE_POINT: libc::DWORD = 0x900ac;
5963

6064
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: libc::DWORD = 0x1;
6165

@@ -71,6 +75,9 @@ pub const PROGRESS_CANCEL: libc::DWORD = 1;
7175
pub const PROGRESS_STOP: libc::DWORD = 2;
7276
pub const PROGRESS_QUIET: libc::DWORD = 3;
7377

78+
pub const TOKEN_ADJUST_PRIVILEGES: libc::DWORD = 0x0020;
79+
pub const SE_PRIVILEGE_ENABLED: libc::DWORD = 2;
80+
7481
#[repr(C)]
7582
#[cfg(target_arch = "x86")]
7683
pub struct WSADATA {
@@ -287,6 +294,40 @@ pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = CONDITION_VARIABLE {
287294
};
288295
pub const SRWLOCK_INIT: SRWLOCK = SRWLOCK { ptr: 0 as *mut _ };
289296

297+
#[repr(C)]
298+
pub struct LUID {
299+
pub LowPart: libc::DWORD,
300+
pub HighPart: libc::c_long,
301+
}
302+
303+
pub type PLUID = *mut LUID;
304+
305+
#[repr(C)]
306+
pub struct TOKEN_PRIVILEGES {
307+
pub PrivilegeCount: libc::DWORD,
308+
pub Privileges: [LUID_AND_ATTRIBUTES; 1],
309+
}
310+
311+
pub type PTOKEN_PRIVILEGES = *mut TOKEN_PRIVILEGES;
312+
313+
#[repr(C)]
314+
pub struct LUID_AND_ATTRIBUTES {
315+
pub Luid: LUID,
316+
pub Attributes: libc::DWORD,
317+
}
318+
319+
#[repr(C)]
320+
pub struct REPARSE_MOUNTPOINT_DATA_BUFFER {
321+
pub ReparseTag: libc::DWORD,
322+
pub ReparseDataLength: libc::DWORD,
323+
pub Reserved: libc::WORD,
324+
pub ReparseTargetLength: libc::WORD,
325+
pub ReparseTargetMaximumLength: libc::WORD,
326+
pub Reserved1: libc::WORD,
327+
pub ReparseTarget: libc::WCHAR,
328+
}
329+
330+
290331
#[link(name = "ws2_32")]
291332
#[link(name = "userenv")]
292333
extern "system" {
@@ -437,6 +478,15 @@ extern "system" {
437478
lpData: libc::LPVOID,
438479
pbCancel: LPBOOL,
439480
dwCopyFlags: libc::DWORD) -> libc::BOOL;
481+
pub fn LookupPrivilegeValueW(lpSystemName: libc::LPCWSTR,
482+
lpName: libc::LPCWSTR,
483+
lpLuid: PLUID) -> libc::BOOL;
484+
pub fn AdjustTokenPrivileges(TokenHandle: libc::HANDLE,
485+
DisableAllPrivileges: libc::BOOL,
486+
NewState: PTOKEN_PRIVILEGES,
487+
BufferLength: libc::DWORD,
488+
PreviousState: PTOKEN_PRIVILEGES,
489+
ReturnLength: *mut libc::DWORD) -> libc::BOOL;
440490
}
441491

442492
// Functions that aren't available on Windows XP, but we still use them and just

src/libstd/sys/windows/fs.rs

+175-35
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ pub struct File { handle: Handle }
3030

3131
pub struct FileAttr {
3232
data: c::WIN32_FILE_ATTRIBUTE_DATA,
33-
is_symlink: bool,
33+
reparse_tag: libc::DWORD,
3434
}
3535

3636
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
3737
pub enum FileType {
38-
Dir, File, Symlink, ReparsePoint
38+
Dir, File, Symlink, ReparsePoint, MountPoint,
3939
}
4040

4141
pub struct ReadDir {
@@ -133,7 +133,7 @@ impl DirEntry {
133133

134134
pub fn file_type(&self) -> io::Result<FileType> {
135135
Ok(FileType::new(self.data.dwFileAttributes,
136-
self.data.dwReserved0 == c::IO_REPARSE_TAG_SYMLINK))
136+
/* reparse_tag = */ self.data.dwReserved0))
137137
}
138138

139139
pub fn metadata(&self) -> io::Result<FileAttr> {
@@ -146,7 +146,7 @@ impl DirEntry {
146146
nFileSizeHigh: self.data.nFileSizeHigh,
147147
nFileSizeLow: self.data.nFileSizeLow,
148148
},
149-
is_symlink: self.data.dwReserved0 == c::IO_REPARSE_TAG_SYMLINK,
149+
reparse_tag: self.data.dwReserved0,
150150
})
151151
}
152152
}
@@ -218,10 +218,12 @@ impl OpenOptions {
218218
}
219219

220220
impl File {
221-
fn open_reparse_point(path: &Path) -> io::Result<File> {
221+
fn open_reparse_point(path: &Path, write: bool) -> io::Result<File> {
222222
let mut opts = OpenOptions::new();
223-
opts.read(true);
224-
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT);
223+
opts.read(!write);
224+
opts.write(write);
225+
opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT |
226+
c::FILE_FLAG_BACKUP_SEMANTICS);
225227
File::open(path, &opts)
226228
}
227229

@@ -278,10 +280,13 @@ impl File {
278280
nFileSizeHigh: info.nFileSizeHigh,
279281
nFileSizeLow: info.nFileSizeLow,
280282
},
281-
is_symlink: false,
283+
reparse_tag: 0,
282284
};
283285
if attr.is_reparse_point() {
284-
attr.is_symlink = self.is_symlink();
286+
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
287+
if let Ok((_, buf)) = self.reparse_point(&mut b) {
288+
attr.reparse_tag = buf.ReparseTag;
289+
}
285290
}
286291
Ok(attr)
287292
}
@@ -314,15 +319,11 @@ impl File {
314319

315320
pub fn handle(&self) -> &Handle { &self.handle }
316321

317-
fn is_symlink(&self) -> bool {
318-
self.readlink().is_ok()
319-
}
320-
321-
fn readlink(&self) -> io::Result<PathBuf> {
322-
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
323-
let mut bytes = 0;
324-
322+
fn reparse_point<'a>(&self,
323+
space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE])
324+
-> io::Result<(libc::DWORD, &'a c::REPARSE_DATA_BUFFER)> {
325325
unsafe {
326+
let mut bytes = 0;
326327
try!(cvt({
327328
c::DeviceIoControl(self.handle.raw(),
328329
c::FSCTL_GET_REPARSE_POINT,
@@ -333,12 +334,20 @@ impl File {
333334
&mut bytes,
334335
0 as *mut _)
335336
}));
336-
let buf: *const c::REPARSE_DATA_BUFFER = space.as_ptr() as *const _;
337-
if (*buf).ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
338-
return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
339-
}
337+
Ok((bytes, &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER)))
338+
}
339+
}
340+
341+
fn readlink(&self) -> io::Result<PathBuf> {
342+
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
343+
let (_bytes, buf) = try!(self.reparse_point(&mut space));
344+
if buf.ReparseTag != c::IO_REPARSE_TAG_SYMLINK {
345+
return Err(io::Error::new(io::ErrorKind::Other, "not a symlink"))
346+
}
347+
348+
unsafe {
340349
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
341-
&(*buf).rest as *const _ as *const _;
350+
&buf.rest as *const _ as *const _;
342351
let path_buffer = &(*info).PathBuffer as *const _ as *const u16;
343352
let subst_off = (*info).SubstituteNameOffset / 2;
344353
let subst_ptr = path_buffer.offset(subst_off as isize);
@@ -383,7 +392,7 @@ impl FileAttr {
383392
pub fn attrs(&self) -> u32 { self.data.dwFileAttributes as u32 }
384393

385394
pub fn file_type(&self) -> FileType {
386-
FileType::new(self.data.dwFileAttributes, self.is_symlink)
395+
FileType::new(self.data.dwFileAttributes, self.reparse_tag)
387396
}
388397

389398
pub fn created(&self) -> u64 { self.to_u64(&self.data.ftCreationTime) }
@@ -414,12 +423,12 @@ impl FilePermissions {
414423
}
415424

416425
impl FileType {
417-
fn new(attrs: libc::DWORD, is_symlink: bool) -> FileType {
426+
fn new(attrs: libc::DWORD, reparse_tag: libc::DWORD) -> FileType {
418427
if attrs & libc::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
419-
if is_symlink {
420-
FileType::Symlink
421-
} else {
422-
FileType::ReparsePoint
428+
match reparse_tag {
429+
c::IO_REPARSE_TAG_SYMLINK => FileType::Symlink,
430+
c::IO_REPARSE_TAG_MOUNT_POINT => FileType::MountPoint,
431+
_ => FileType::ReparsePoint,
423432
}
424433
} else if attrs & c::FILE_ATTRIBUTE_DIRECTORY != 0 {
425434
FileType::Dir
@@ -430,7 +439,9 @@ impl FileType {
430439

431440
pub fn is_dir(&self) -> bool { *self == FileType::Dir }
432441
pub fn is_file(&self) -> bool { *self == FileType::File }
433-
pub fn is_symlink(&self) -> bool { *self == FileType::Symlink }
442+
pub fn is_symlink(&self) -> bool {
443+
*self == FileType::Symlink || *self == FileType::MountPoint
444+
}
434445
}
435446

436447
impl DirBuilder {
@@ -488,7 +499,7 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
488499
}
489500

490501
pub fn readlink(p: &Path) -> io::Result<PathBuf> {
491-
let file = try!(File::open_reparse_point(p));
502+
let file = try!(File::open_reparse_point(p, false));
492503
file.readlink()
493504
}
494505

@@ -517,8 +528,15 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
517528

518529
pub fn stat(p: &Path) -> io::Result<FileAttr> {
519530
let attr = try!(lstat(p));
520-
if attr.data.dwFileAttributes & libc::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
521-
let opts = OpenOptions::new();
531+
532+
// If this is a reparse point, then we need to reopen the file to get the
533+
// actual destination. We also pass the FILE_FLAG_BACKUP_SEMANTICS flag to
534+
// ensure that we can open directories (this path may be a directory
535+
// junction). Once the file is opened we ask the opened handle what its
536+
// metadata information is.
537+
if attr.is_reparse_point() {
538+
let mut opts = OpenOptions::new();
539+
opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
522540
let file = try!(File::open(p, &opts));
523541
file.file_attr()
524542
} else {
@@ -534,9 +552,10 @@ pub fn lstat(p: &Path) -> io::Result<FileAttr> {
534552
c::GetFileExInfoStandard,
535553
&mut attr.data as *mut _ as *mut _)));
536554
if attr.is_reparse_point() {
537-
attr.is_symlink = File::open_reparse_point(p).map(|f| {
538-
f.is_symlink()
539-
}).unwrap_or(false);
555+
attr.reparse_tag = File::open_reparse_point(p, false).and_then(|f| {
556+
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
557+
f.reparse_point(&mut b).map(|(_, b)| b.ReparseTag)
558+
}).unwrap_or(0);
540559
}
541560
Ok(attr)
542561
}
@@ -600,3 +619,124 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
600619
}));
601620
Ok(size as u64)
602621
}
622+
623+
#[test]
624+
fn directory_junctions_are_directories() {
625+
use ffi::OsStr;
626+
use env;
627+
use rand::{self, StdRng, Rng};
628+
629+
macro_rules! t {
630+
($e:expr) => (match $e {
631+
Ok(e) => e,
632+
Err(e) => panic!("{} failed with: {}", stringify!($e), e),
633+
})
634+
}
635+
636+
let d = DirBuilder::new();
637+
let p = env::temp_dir();
638+
let mut r = rand::thread_rng();
639+
let ret = p.join(&format!("rust-{}", r.next_u32()));
640+
let foo = ret.join("foo");
641+
let bar = ret.join("bar");
642+
t!(d.mkdir(&ret));
643+
t!(d.mkdir(&foo));
644+
t!(d.mkdir(&bar));
645+
646+
t!(create_junction(&bar, &foo));
647+
let metadata = stat(&bar);
648+
t!(delete_junction(&bar));
649+
650+
t!(rmdir(&foo));
651+
t!(rmdir(&bar));
652+
t!(rmdir(&ret));
653+
654+
let metadata = t!(metadata);
655+
assert!(metadata.file_type().is_dir());
656+
657+
// Creating a directory junction on windows involves dealing with reparse
658+
// points and the DeviceIoControl function, and this code is a skeleton of
659+
// what can be found here:
660+
//
661+
// http://www.flexhex.com/docs/articles/hard-links.phtml
662+
fn create_junction(src: &Path, dst: &Path) -> io::Result<()> {
663+
let f = try!(opendir(src, true));
664+
let h = f.handle().raw();
665+
666+
unsafe {
667+
let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
668+
let mut db = data.as_mut_ptr()
669+
as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER;
670+
let mut buf = &mut (*db).ReparseTarget as *mut _;
671+
let mut i = 0;
672+
let v = br"\??\";
673+
let v = v.iter().map(|x| *x as u16);
674+
for c in v.chain(dst.as_os_str().encode_wide()) {
675+
*buf.offset(i) = c;
676+
i += 1;
677+
}
678+
*buf.offset(i) = 0;
679+
i += 1;
680+
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
681+
(*db).ReparseTargetMaximumLength = (i * 2) as libc::WORD;
682+
(*db).ReparseTargetLength = ((i - 1) * 2) as libc::WORD;
683+
(*db).ReparseDataLength =
684+
(*db).ReparseTargetLength as libc::DWORD + 12;
685+
686+
let mut ret = 0;
687+
cvt(c::DeviceIoControl(h as *mut _,
688+
c::FSCTL_SET_REPARSE_POINT,
689+
data.as_ptr() as *mut _,
690+
(*db).ReparseDataLength + 8,
691+
0 as *mut _, 0,
692+
&mut ret,
693+
0 as *mut _)).map(|_| ())
694+
}
695+
}
696+
697+
fn opendir(p: &Path, write: bool) -> io::Result<File> {
698+
unsafe {
699+
let mut token = 0 as *mut _;
700+
let mut tp: c::TOKEN_PRIVILEGES = mem::zeroed();
701+
try!(cvt(c::OpenProcessToken(c::GetCurrentProcess(),
702+
c::TOKEN_ADJUST_PRIVILEGES,
703+
&mut token)));
704+
let name: &OsStr = if write {
705+
"SeRestorePrivilege".as_ref()
706+
} else {
707+
"SeBackupPrivilege".as_ref()
708+
};
709+
let name = name.encode_wide().chain(Some(0)).collect::<Vec<_>>();
710+
try!(cvt(c::LookupPrivilegeValueW(0 as *const _,
711+
name.as_ptr(),
712+
&mut tp.Privileges[0].Luid)));
713+
tp.PrivilegeCount = 1;
714+
tp.Privileges[0].Attributes = c::SE_PRIVILEGE_ENABLED;
715+
let size = mem::size_of::<c::TOKEN_PRIVILEGES>() as libc::DWORD;
716+
try!(cvt(c::AdjustTokenPrivileges(token, libc::FALSE, &mut tp, size,
717+
0 as *mut _, 0 as *mut _)));
718+
try!(cvt(libc::CloseHandle(token)));
719+
720+
File::open_reparse_point(p, write)
721+
}
722+
}
723+
724+
fn delete_junction(p: &Path) -> io::Result<()> {
725+
unsafe {
726+
let f = try!(opendir(p, true));
727+
let h = f.handle().raw();
728+
let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
729+
let mut db = data.as_mut_ptr()
730+
as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER;
731+
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
732+
let mut bytes = 0;
733+
cvt(c::DeviceIoControl(h as *mut _,
734+
c::FSCTL_DELETE_REPARSE_POINT,
735+
data.as_ptr() as *mut _,
736+
(*db).ReparseDataLength + 8,
737+
0 as *mut _, 0,
738+
&mut bytes,
739+
0 as *mut _)).map(|_| ())
740+
}
741+
}
742+
}

0 commit comments

Comments
 (0)