@@ -11,8 +11,6 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
1111use std:: ffi:: OsString ;
1212use std:: io:: { Write , stdout} ;
1313use std:: path:: { Path , PathBuf } ;
14- #[ cfg( all( unix, target_os = "linux" ) ) ]
15- use uucore:: error:: FromIo ;
1614use uucore:: error:: { UResult , USimpleError } ;
1715use uucore:: translate;
1816
@@ -37,8 +35,8 @@ pub struct Config<'a> {
3735 /// Create parent directories as needed.
3836 pub recursive : bool ,
3937
40- /// File permissions (octal).
41- pub mode : u32 ,
38+ /// File permissions (octal) if provided via -m
39+ pub mode : Option < u32 > ,
4240
4341 /// Print message for each created directory.
4442 pub verbose : bool ,
@@ -55,18 +53,18 @@ pub struct Config<'a> {
5553 clippy:: unnecessary_wraps,
5654 reason = "fn sig must match on all platforms"
5755) ]
58- fn get_mode ( _matches : & ArgMatches ) -> Result < u32 , String > {
59- Ok ( DEFAULT_PERM )
56+ fn get_mode ( _matches : & ArgMatches ) -> Result < Option < u32 > , String > {
57+ Ok ( None )
6058}
6159
6260#[ cfg( not( windows) ) ]
63- fn get_mode ( matches : & ArgMatches ) -> Result < u32 , String > {
61+ fn get_mode ( matches : & ArgMatches ) -> Result < Option < u32 > , String > {
6462 // Not tested on Windows
6563 if let Some ( m) = matches. get_one :: < String > ( options:: MODE ) {
66- mode:: parse_chmod ( DEFAULT_PERM , m, true , mode:: get_umask ( ) )
64+ mode:: parse_chmod ( DEFAULT_PERM , m, true , mode:: get_umask ( ) ) . map ( Some )
6765 } else {
68- // If no mode argument is specified return the mode derived from umask
69- Ok ( !mode :: get_umask ( ) & DEFAULT_PERM )
66+ // If no mode argument, let the kernel apply umask and ACLs naturally.
67+ Ok ( None )
7068 }
7169}
7270
@@ -196,17 +194,6 @@ pub fn mkdir(path: &Path, config: &Config) -> UResult<()> {
196194 create_dir ( path, false , config)
197195}
198196
199- /// Only needed on Linux to add ACL permission bits after directory creation.
200- #[ cfg( all( unix, target_os = "linux" ) ) ]
201- fn chmod ( path : & Path , mode : u32 ) -> UResult < ( ) > {
202- use std:: fs:: { Permissions , set_permissions} ;
203- use std:: os:: unix:: fs:: PermissionsExt ;
204- let mode = Permissions :: from_mode ( mode) ;
205- set_permissions ( path, mode) . map_err_context (
206- || translate ! ( "mkdir-error-cannot-set-permissions" , "path" => path. quote( ) ) ,
207- )
208- }
209-
210197// Create a directory at the given path.
211198// Uses iterative approach instead of recursion to avoid stack overflow with deep nesting.
212199fn create_dir ( path : & Path , is_parent : bool , config : & Config ) -> UResult < ( ) > {
@@ -272,43 +259,59 @@ impl Drop for UmaskGuard {
272259
273260/// Create a directory with the exact mode specified, bypassing umask.
274261///
275- /// GNU mkdir temporarily sets umask to 0 before calling mkdir(2), ensuring the
262+ /// GNU mkdir temporarily sets umask to shaped mask before calling mkdir(2), ensuring the
276263/// directory is created atomically with the correct permissions. This avoids a
277264/// race condition where the directory briefly exists with umask-based permissions.
278265#[ cfg( unix) ]
279- fn create_dir_with_mode ( path : & Path , mode : u32 ) -> std:: io:: Result < ( ) > {
266+ fn create_dir_with_mode (
267+ path : & Path ,
268+ mode : u32 ,
269+ shaped_umask : rustix:: fs:: Mode ,
270+ ) -> std:: io:: Result < ( ) > {
280271 use std:: os:: unix:: fs:: DirBuilderExt ;
281272
282- // Temporarily set umask to 0 so the directory is created with the exact mode.
283- // The guard restores the original umask on drop, even if we panic.
284- let _guard = UmaskGuard :: set ( rustix:: fs:: Mode :: empty ( ) ) ;
273+ let _guard = UmaskGuard :: set ( shaped_umask) ;
285274
286275 std:: fs:: DirBuilder :: new ( ) . mode ( mode) . create ( path)
287276}
288277
289278#[ cfg( not( unix) ) ]
290- fn create_dir_with_mode ( path : & Path , _mode : u32 ) -> std:: io:: Result < ( ) > {
279+ fn create_dir_with_mode ( path : & Path , _mode : u32 , _shaped_umask : u32 ) -> std:: io:: Result < ( ) > {
291280 std:: fs:: create_dir ( path)
292281}
293282
294283// Helper function to create a single directory with appropriate permissions
295284// `is_parent` argument is not used on windows
296285#[ allow( unused_variables) ]
297286fn create_single_dir ( path : & Path , is_parent : bool , config : & Config ) -> UResult < ( ) > {
298- let path_exists = path. exists ( ) ;
299-
300- // Calculate the mode to use for directory creation
301287 #[ cfg( unix) ]
302- let create_mode = if is_parent {
303- // For parent directories with -p, use umask-derived mode with u+wx
304- ( !mode:: get_umask ( ) & 0o777 ) | 0o300
305- } else {
306- config. mode
288+ let ( mkdir_mode, shaped_umask) = {
289+ let umask = mode:: get_umask ( ) ;
290+ let umask_bits = rustix:: fs:: Mode :: from_bits_truncate ( umask) ;
291+ if is_parent {
292+ // Parent directories are never affected by -m (matches GNU behavior).
293+ // We pass 0o777 as the mode and shape the umask so it cannot block
294+ // owner write or execute (u+wx), ensuring the owner can traverse and
295+ // write into the parent to create children. All other umask bits are
296+ // preserved so the kernel applies them — and any default ACL on the
297+ // grandparent — through the normal mkdir(2) path.
298+ (
299+ DEFAULT_PERM ,
300+ umask_bits & !rustix:: fs:: Mode :: from_bits_truncate ( 0o300 ) ,
301+ )
302+ } else {
303+ match config. mode {
304+ // Explicit -m: shape umask so it cannot block explicitly requested bits.
305+ Some ( m) => ( m, umask_bits & !rustix:: fs:: Mode :: from_bits_truncate ( m) ) ,
306+ // No -m: leave umask fully intact; kernel applies umask + ACL naturally.
307+ None => ( DEFAULT_PERM , umask_bits) ,
308+ }
309+ }
307310 } ;
308311 #[ cfg( not( unix) ) ]
309- let create_mode = config. mode ;
312+ let ( mkdir_mode , shaped_umask ) = ( config. mode . unwrap_or ( DEFAULT_PERM ) , 0u32 ) ;
310313
311- match create_dir_with_mode ( path, create_mode ) {
314+ match create_dir_with_mode ( path, mkdir_mode , shaped_umask ) {
312315 Ok ( ( ) ) => {
313316 if config. verbose {
314317 writeln ! (
@@ -318,18 +321,6 @@ fn create_single_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<(
318321 ) ?;
319322 }
320323
321- // On Linux, we may need to add ACL permission bits via chmod.
322- // On other Unix systems, the directory was already created with the correct mode.
323- #[ cfg( all( unix, target_os = "linux" ) ) ]
324- if !path_exists {
325- // TODO: Make this macos and freebsd compatible by creating a function to get permission bits from
326- // acl in extended attributes
327- let acl_perm_bits = uucore:: fsxattr:: get_acl_perm_bits_from_xattr ( path) ;
328- if acl_perm_bits != 0 {
329- chmod ( path, create_mode | acl_perm_bits) ?;
330- }
331- }
332-
333324 // Apply SELinux context if requested
334325 #[ cfg( feature = "selinux" ) ]
335326 if config. set_security_context && uucore:: selinux:: is_selinux_enabled ( ) {
0 commit comments