@@ -30,12 +30,12 @@ pub struct File { handle: Handle }
30
30
31
31
pub struct FileAttr {
32
32
data : c:: WIN32_FILE_ATTRIBUTE_DATA ,
33
- is_symlink : bool ,
33
+ reparse_tag : libc :: DWORD ,
34
34
}
35
35
36
36
#[ derive( Copy , Clone , PartialEq , Eq , Hash ) ]
37
37
pub enum FileType {
38
- Dir , File , Symlink , ReparsePoint
38
+ Dir , File , Symlink , ReparsePoint , MountPoint ,
39
39
}
40
40
41
41
pub struct ReadDir {
@@ -133,7 +133,7 @@ impl DirEntry {
133
133
134
134
pub fn file_type ( & self ) -> io:: Result < FileType > {
135
135
Ok ( FileType :: new ( self . data . dwFileAttributes ,
136
- self . data . dwReserved0 == c :: IO_REPARSE_TAG_SYMLINK ) )
136
+ /* reparse_tag = */ self . data . dwReserved0 ) )
137
137
}
138
138
139
139
pub fn metadata ( & self ) -> io:: Result < FileAttr > {
@@ -146,7 +146,7 @@ impl DirEntry {
146
146
nFileSizeHigh : self . data . nFileSizeHigh ,
147
147
nFileSizeLow : self . data . nFileSizeLow ,
148
148
} ,
149
- is_symlink : self . data . dwReserved0 == c :: IO_REPARSE_TAG_SYMLINK ,
149
+ reparse_tag : self . data . dwReserved0 ,
150
150
} )
151
151
}
152
152
}
@@ -218,10 +218,12 @@ impl OpenOptions {
218
218
}
219
219
220
220
impl File {
221
- fn open_reparse_point ( path : & Path ) -> io:: Result < File > {
221
+ fn open_reparse_point ( path : & Path , write : bool ) -> io:: Result < File > {
222
222
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 ) ;
225
227
File :: open ( path, & opts)
226
228
}
227
229
@@ -278,10 +280,13 @@ impl File {
278
280
nFileSizeHigh : info. nFileSizeHigh ,
279
281
nFileSizeLow : info. nFileSizeLow ,
280
282
} ,
281
- is_symlink : false ,
283
+ reparse_tag : 0 ,
282
284
} ;
283
285
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
+ }
285
290
}
286
291
Ok ( attr)
287
292
}
@@ -314,15 +319,11 @@ impl File {
314
319
315
320
pub fn handle ( & self ) -> & Handle { & self . handle }
316
321
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 ) > {
325
325
unsafe {
326
+ let mut bytes = 0 ;
326
327
try!( cvt ( {
327
328
c:: DeviceIoControl ( self . handle . raw ( ) ,
328
329
c:: FSCTL_GET_REPARSE_POINT ,
@@ -333,12 +334,20 @@ impl File {
333
334
& mut bytes,
334
335
0 as * mut _ )
335
336
} ) ) ;
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 {
340
349
let info: * const c:: SYMBOLIC_LINK_REPARSE_BUFFER =
341
- & ( * buf) . rest as * const _ as * const _ ;
350
+ & buf. rest as * const _ as * const _ ;
342
351
let path_buffer = & ( * info) . PathBuffer as * const _ as * const u16 ;
343
352
let subst_off = ( * info) . SubstituteNameOffset / 2 ;
344
353
let subst_ptr = path_buffer. offset ( subst_off as isize ) ;
@@ -383,7 +392,7 @@ impl FileAttr {
383
392
pub fn attrs ( & self ) -> u32 { self . data . dwFileAttributes as u32 }
384
393
385
394
pub fn file_type ( & self ) -> FileType {
386
- FileType :: new ( self . data . dwFileAttributes , self . is_symlink )
395
+ FileType :: new ( self . data . dwFileAttributes , self . reparse_tag )
387
396
}
388
397
389
398
pub fn created ( & self ) -> u64 { self . to_u64 ( & self . data . ftCreationTime ) }
@@ -414,12 +423,12 @@ impl FilePermissions {
414
423
}
415
424
416
425
impl FileType {
417
- fn new ( attrs : libc:: DWORD , is_symlink : bool ) -> FileType {
426
+ fn new ( attrs : libc:: DWORD , reparse_tag : libc :: DWORD ) -> FileType {
418
427
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 ,
423
432
}
424
433
} else if attrs & c:: FILE_ATTRIBUTE_DIRECTORY != 0 {
425
434
FileType :: Dir
@@ -430,7 +439,9 @@ impl FileType {
430
439
431
440
pub fn is_dir ( & self ) -> bool { * self == FileType :: Dir }
432
441
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
+ }
434
445
}
435
446
436
447
impl DirBuilder {
@@ -488,7 +499,7 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
488
499
}
489
500
490
501
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 ) ) ;
492
503
file. readlink ( )
493
504
}
494
505
@@ -517,8 +528,15 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
517
528
518
529
pub fn stat ( p : & Path ) -> io:: Result < FileAttr > {
519
530
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 ) ;
522
540
let file = try!( File :: open ( p, & opts) ) ;
523
541
file. file_attr ( )
524
542
} else {
@@ -534,9 +552,10 @@ pub fn lstat(p: &Path) -> io::Result<FileAttr> {
534
552
c:: GetFileExInfoStandard ,
535
553
& mut attr. data as * mut _ as * mut _ ) ) ) ;
536
554
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 ) ;
540
559
}
541
560
Ok ( attr)
542
561
}
@@ -600,3 +619,124 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
600
619
} ) ) ;
601
620
Ok ( size as u64 )
602
621
}
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