33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // spell-checker:ignore (ToDO) ugoa cmode
6+ // spell-checker:ignore (ToDO) ugoa cmode RAII
77
88use clap:: builder:: ValueParser ;
99use clap:: parser:: ValuesRef ;
1010use clap:: { Arg , ArgAction , ArgMatches , Command } ;
1111use std:: ffi:: OsString ;
1212use std:: path:: { Path , PathBuf } ;
13- #[ cfg( not ( windows ) ) ]
13+ #[ cfg( all ( unix , target_os = "linux" ) ) ]
1414use uucore:: error:: FromIo ;
1515use uucore:: error:: { UResult , USimpleError } ;
1616use uucore:: translate;
@@ -191,7 +191,8 @@ pub fn mkdir(path: &Path, config: &Config) -> UResult<()> {
191191 create_dir ( path, false , config)
192192}
193193
194- #[ cfg( any( unix, target_os = "redox" ) ) ]
194+ /// Only needed on Linux to add ACL permission bits after directory creation.
195+ #[ cfg( all( unix, target_os = "linux" ) ) ]
195196fn chmod ( path : & Path , mode : u32 ) -> UResult < ( ) > {
196197 use std:: fs:: { Permissions , set_permissions} ;
197198 use std:: os:: unix:: fs:: PermissionsExt ;
@@ -201,12 +202,6 @@ fn chmod(path: &Path, mode: u32) -> UResult<()> {
201202 )
202203}
203204
204- #[ cfg( windows) ]
205- fn chmod ( _path : & Path , _mode : u32 ) -> UResult < ( ) > {
206- // chmod on Windows only sets the readonly flag, which isn't even honored on directories
207- Ok ( ( ) )
208- }
209-
210205// Create a directory at the given path.
211206// Uses iterative approach instead of recursion to avoid stack overflow with deep nesting.
212207fn create_dir ( path : & Path , is_parent : bool , config : & Config ) -> UResult < ( ) > {
@@ -250,13 +245,67 @@ fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
250245 create_single_dir ( path, is_parent, config)
251246}
252247
248+ /// RAII guard to restore umask on drop, ensuring cleanup even on panic.
249+ #[ cfg( unix) ]
250+ struct UmaskGuard ( uucore:: libc:: mode_t ) ;
251+
252+ #[ cfg( unix) ]
253+ impl UmaskGuard {
254+ /// Set umask to the given value and return a guard that restores the original on drop.
255+ fn set ( new_mask : uucore:: libc:: mode_t ) -> Self {
256+ let old_mask = unsafe { uucore:: libc:: umask ( new_mask) } ;
257+ Self ( old_mask)
258+ }
259+ }
260+
261+ #[ cfg( unix) ]
262+ impl Drop for UmaskGuard {
263+ fn drop ( & mut self ) {
264+ unsafe {
265+ uucore:: libc:: umask ( self . 0 ) ;
266+ }
267+ }
268+ }
269+
270+ /// Create a directory with the exact mode specified, bypassing umask.
271+ ///
272+ /// GNU mkdir temporarily sets umask to 0 before calling mkdir(2), ensuring the
273+ /// directory is created atomically with the correct permissions. This avoids a
274+ /// race condition where the directory briefly exists with umask-based permissions.
275+ #[ cfg( unix) ]
276+ fn create_dir_with_mode ( path : & Path , mode : u32 ) -> std:: io:: Result < ( ) > {
277+ use std:: os:: unix:: fs:: DirBuilderExt ;
278+
279+ // Temporarily set umask to 0 so the directory is created with the exact mode.
280+ // The guard restores the original umask on drop, even if we panic.
281+ let _guard = UmaskGuard :: set ( 0 ) ;
282+
283+ std:: fs:: DirBuilder :: new ( ) . mode ( mode) . create ( path)
284+ }
285+
286+ #[ cfg( not( unix) ) ]
287+ fn create_dir_with_mode ( path : & Path , _mode : u32 ) -> std:: io:: Result < ( ) > {
288+ std:: fs:: create_dir ( path)
289+ }
290+
253291// Helper function to create a single directory with appropriate permissions
254292// `is_parent` argument is not used on windows
255293#[ allow( unused_variables) ]
256294fn create_single_dir ( path : & Path , is_parent : bool , config : & Config ) -> UResult < ( ) > {
257295 let path_exists = path. exists ( ) ;
258296
259- match std:: fs:: create_dir ( path) {
297+ // Calculate the mode to use for directory creation
298+ #[ cfg( unix) ]
299+ let create_mode = if is_parent {
300+ // For parent directories with -p, use umask-derived mode with u+wx
301+ ( !mode:: get_umask ( ) & 0o777 ) | 0o300
302+ } else {
303+ config. mode
304+ } ;
305+ #[ cfg( not( unix) ) ]
306+ let create_mode = config. mode ;
307+
308+ match create_dir_with_mode ( path, create_mode) {
260309 Ok ( ( ) ) => {
261310 if config. verbose {
262311 println ! (
@@ -265,30 +314,17 @@ fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<(
265314 ) ;
266315 }
267316
317+ // On Linux, we may need to add ACL permission bits via chmod.
318+ // On other Unix systems, the directory was already created with the correct mode.
268319 #[ cfg( all( unix, target_os = "linux" ) ) ]
269- let new_mode = if path_exists {
270- config. mode
271- } else {
320+ if !path_exists {
272321 // TODO: Make this macos and freebsd compatible by creating a function to get permission bits from
273322 // acl in extended attributes
274323 let acl_perm_bits = uucore:: fsxattr:: get_acl_perm_bits_from_xattr ( path) ;
275-
276- if is_parent {
277- ( !mode:: get_umask ( ) & 0o777 ) | 0o300 | acl_perm_bits
278- } else {
279- config. mode | acl_perm_bits
324+ if acl_perm_bits != 0 {
325+ chmod ( path, create_mode | acl_perm_bits) ?;
280326 }
281- } ;
282- #[ cfg( all( unix, not( target_os = "linux" ) ) ) ]
283- let new_mode = if is_parent {
284- ( !mode:: get_umask ( ) & 0o777 ) | 0o300
285- } else {
286- config. mode
287- } ;
288- #[ cfg( windows) ]
289- let new_mode = config. mode ;
290-
291- chmod ( path, new_mode) ?;
327+ }
292328
293329 // Apply SELinux context if requested
294330 #[ cfg( feature = "selinux" ) ]
0 commit comments