1- mod c;
2- mod cast;
3- mod helpers;
1+ pub ( crate ) mod c;
2+ pub ( crate ) mod cast;
3+ pub ( crate ) mod helpers;
44
55use std:: ffi:: OsString ;
66use std:: mem:: size_of;
@@ -20,14 +20,13 @@ const NT_PREFIX: [u16; 4] = helpers::utf16s(br"\??\");
2020/// Ref: <https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry>
2121const VERBATIM_PREFIX : [ u16 ; 4 ] = helpers:: utf16s ( br"\\?\" ) ;
2222
23- const WCHAR_SIZE : u16 = size_of :: < u16 > ( ) as _ ;
23+ pub ( crate ) const WCHAR_SIZE : u16 = size_of :: < u16 > ( ) as _ ;
2424
2525pub fn create ( target : & Path , junction : & Path ) -> io:: Result < ( ) > {
2626 const UNICODE_NULL_SIZE : u16 = WCHAR_SIZE ;
27- const MAX_AVAILABLE_PATH_BUFFER : u16 = c:: MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16
27+ const MAX_PATH_BUFFER : u16 = c:: MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16
2828 - c:: REPARSE_DATA_BUFFER_HEADER_SIZE
29- - c:: MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE
30- - 2 * UNICODE_NULL_SIZE ;
29+ - c:: MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE ;
3130
3231 // We're using low-level APIs to create the junction, and these are more picky about paths.
3332 // For example, forward slashes cannot be used as a path separator, so we should try to
@@ -37,19 +36,29 @@ pub fn create(target: &Path, junction: &Path) -> io::Result<()> {
3736 let target = target. strip_prefix ( VERBATIM_PREFIX . as_slice ( ) ) . unwrap_or ( & target) ;
3837 fs:: create_dir ( junction) ?;
3938 let file = helpers:: open_reparse_point ( junction, true ) ?;
40- let target_len_in_bytes = {
41- // "\??\" + target
39+
40+ // SubstituteName = "\??\" + target (NT path)
41+ let substitute_len_in_bytes = {
4242 let len = NT_PREFIX . len ( ) . saturating_add ( target. len ( ) ) ;
4343 let min_len = cmp:: min ( len, u16:: MAX as usize ) as u16 ;
44- // Len without `UNICODE_NULL` at the end
45- let target_len_in_bytes = min_len . saturating_mul ( WCHAR_SIZE ) ;
46- // Check for buffer overflow.
47- if target_len_in_bytes > MAX_AVAILABLE_PATH_BUFFER {
48- return Err ( io :: Error :: new ( io :: ErrorKind :: InvalidInput , "`target` is too long" ) ) ;
49- }
50- target_len_in_bytes
44+ min_len . saturating_mul ( WCHAR_SIZE )
45+ } ;
46+
47+ // PrintName = target (Win32 path, without the \??\ prefix)
48+ let print_name_len_in_bytes = {
49+ let min_len = cmp :: min ( target . len ( ) , u16 :: MAX as usize ) as u16 ;
50+ min_len . saturating_mul ( WCHAR_SIZE )
5151 } ;
5252
53+ // Check for buffer overflow: both names + their null terminators must fit
54+ let total_path_buffer = substitute_len_in_bytes
55+ . saturating_add ( UNICODE_NULL_SIZE )
56+ . saturating_add ( print_name_len_in_bytes)
57+ . saturating_add ( UNICODE_NULL_SIZE ) ;
58+ if total_path_buffer > MAX_PATH_BUFFER {
59+ return Err ( io:: Error :: new ( io:: ErrorKind :: InvalidInput , "`target` is too long" ) ) ;
60+ }
61+
5362 // Redefine the above char array into a ReparseDataBuffer we can work with
5463 let mut data = BytesAsReparseDataBuffer :: new ( ) ;
5564 let rdb = data. as_mut_ptr ( ) ;
@@ -58,24 +67,39 @@ pub fn create(target: &Path, junction: &Path) -> io::Result<()> {
5867 addr_of_mut ! ( ( * rdb) . ReparseTag ) . write ( c:: IO_REPARSE_TAG_MOUNT_POINT ) ;
5968 addr_of_mut ! ( ( * rdb) . Reserved ) . write ( 0 ) ;
6069
61- // We write target at offset 0 of PathBuffer
70+ // SubstituteName starts at offset 0 in PathBuffer
6271 addr_of_mut ! ( ( * rdb) . ReparseBuffer . SubstituteNameOffset ) . write ( 0 ) ;
63- addr_of_mut ! ( ( * rdb) . ReparseBuffer . SubstituteNameLength ) . write ( target_len_in_bytes ) ;
72+ addr_of_mut ! ( ( * rdb) . ReparseBuffer . SubstituteNameLength ) . write ( substitute_len_in_bytes ) ;
6473
65- // We do not use PrintName. However let's set its offset correctly right after SubstituteName
66- addr_of_mut ! ( ( * rdb) . ReparseBuffer . PrintNameOffset ) . write ( target_len_in_bytes + UNICODE_NULL_SIZE ) ;
67- addr_of_mut ! ( ( * rdb) . ReparseBuffer . PrintNameLength ) . write ( 0 ) ;
74+ // PrintName starts right after SubstituteName + its null terminator
75+ addr_of_mut ! ( ( * rdb) . ReparseBuffer . PrintNameOffset ) . write ( substitute_len_in_bytes + UNICODE_NULL_SIZE ) ;
76+ addr_of_mut ! ( ( * rdb) . ReparseBuffer . PrintNameLength ) . write ( print_name_len_in_bytes ) ;
6877
6978 let mut path_buffer_ptr: * mut u16 = addr_of_mut ! ( ( * rdb) . ReparseBuffer . PathBuffer ) . cast ( ) ;
70- // Safe because we checked `MAX_AVAILABLE_PATH_BUFFER`
79+
80+ // Write SubstituteName: "\??\" + target
7181 copy_nonoverlapping ( NT_PREFIX . as_ptr ( ) , path_buffer_ptr, NT_PREFIX . len ( ) ) ;
72- // TODO: Do we need to write the NULL-terminator byte?
73- // It looks like libuv does that.
7482 path_buffer_ptr = path_buffer_ptr. add ( NT_PREFIX . len ( ) ) ;
7583 copy_nonoverlapping ( target. as_ptr ( ) , path_buffer_ptr, target. len ( ) ) ;
84+ path_buffer_ptr = path_buffer_ptr. add ( target. len ( ) ) ;
85+
86+ // Null terminator after SubstituteName
87+ path_buffer_ptr. write ( 0 ) ;
88+ path_buffer_ptr = path_buffer_ptr. add ( 1 ) ;
89+
90+ // Write PrintName: target (Win32 path without \??\ prefix)
91+ copy_nonoverlapping ( target. as_ptr ( ) , path_buffer_ptr, target. len ( ) ) ;
92+ path_buffer_ptr = path_buffer_ptr. add ( target. len ( ) ) ;
93+
94+ // Null terminator after PrintName
95+ path_buffer_ptr. write ( 0 ) ;
7696
7797 // Set the total size of the data buffer
78- let size = target_len_in_bytes. wrapping_add ( c:: MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + 2 * UNICODE_NULL_SIZE ) ;
98+ let size = c:: MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE
99+ + substitute_len_in_bytes
100+ + UNICODE_NULL_SIZE
101+ + print_name_len_in_bytes
102+ + UNICODE_NULL_SIZE ;
79103 addr_of_mut ! ( ( * rdb) . ReparseDataLength ) . write ( size) ;
80104 size. wrapping_add ( c:: REPARSE_DATA_BUFFER_HEADER_SIZE )
81105 } ;
0 commit comments