@@ -30,12 +30,12 @@ pub struct File { handle: Handle }
3030
3131pub 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 ) ]
3737pub enum FileType {
38- Dir , File , Symlink , ReparsePoint
38+ Dir , File , Symlink , ReparsePoint , MountPoint ,
3939}
4040
4141pub 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
220220impl 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
416425impl 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
436447impl DirBuilder {
@@ -488,7 +499,7 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
488499}
489500
490501pub 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
518529pub 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