From 7430856575e3b0078f02c2da594adc15c9ce295f Mon Sep 17 00:00:00 2001 From: Anirban Halder Date: Thu, 12 Sep 2024 01:05:25 +0530 Subject: [PATCH] ``mkdir``: added ``acl`` permissions inheritance for subdirectories (#6676) Mostly for linux for now --- src/uu/mkdir/Cargo.toml | 3 +- src/uu/mkdir/src/mkdir.rs | 62 +++++++++------ src/uucore/src/lib/features/fsxattr.rs | 100 ++++++++++++++++++++++++- tests/by-util/test_mkdir.rs | 55 ++++++++++++++ 4 files changed, 197 insertions(+), 23 deletions(-) diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 10e63a6c839..e1250afe106 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -18,7 +18,8 @@ path = "src/mkdir.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["fs", "mode"] } +uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] } + [[bin]] name = "mkdir" diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 8637010e018..2f8dfa98802 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -164,20 +164,14 @@ pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult< // std::fs::create_dir("foo/."); fails in pure Rust let path_buf = dir_strip_dot_for_creation(path); let path = path_buf.as_path(); - - if create_dir(path, recursive, verbose, false)? { - chmod(path, mode)?; - } - Ok(()) + create_dir(path, recursive, verbose, false, mode) } #[cfg(any(unix, target_os = "redox"))] fn chmod(path: &Path, mode: u32) -> UResult<()> { use std::fs::{set_permissions, Permissions}; use std::os::unix::fs::PermissionsExt; - let mode = Permissions::from_mode(mode); - set_permissions(path, mode) .map_err_context(|| format!("cannot set permissions {}", path.quote())) } @@ -191,27 +185,33 @@ fn chmod(_path: &Path, _mode: u32) -> UResult<()> { // Return true if the directory at `path` has been created by this call. // `is_parent` argument is not used on windows #[allow(unused_variables)] -fn create_dir(path: &Path, recursive: bool, verbose: bool, is_parent: bool) -> UResult { - if path.exists() && !recursive { +fn create_dir( + path: &Path, + recursive: bool, + verbose: bool, + is_parent: bool, + mode: u32, +) -> UResult<()> { + let path_exists = path.exists(); + if path_exists && !recursive { return Err(USimpleError::new( 1, format!("{}: File exists", path.display()), )); } if path == Path::new("") { - return Ok(false); + return Ok(()); } if recursive { match path.parent() { - Some(p) => { - create_dir(p, recursive, verbose, true)?; - } + Some(p) => create_dir(p, recursive, verbose, true, mode)?, None => { USimpleError::new(1, "failed to create whole tree"); } } } + match std::fs::create_dir(path) { Ok(()) => { if verbose { @@ -221,15 +221,35 @@ fn create_dir(path: &Path, recursive: bool, verbose: bool, is_parent: bool) -> U path.quote() ); } - #[cfg(not(windows))] - if is_parent { - // directories created by -p have permission bits set to '=rwx,u+wx', - // which is umask modified by 'u+wx' - chmod(path, (!mode::get_umask() & 0o0777) | 0o0300)?; - } - Ok(true) + + #[cfg(all(unix, target_os = "linux"))] + let new_mode = if path_exists { + mode + } else { + // TODO: Make this macos and freebsd compatible by creating a function to get permission bits from + // acl in extended attributes + let acl_perm_bits = uucore::fsxattr::get_acl_perm_bits_from_xattr(path); + + if is_parent { + (!mode::get_umask() & 0o777) | 0o300 | acl_perm_bits + } else { + mode | acl_perm_bits + } + }; + #[cfg(all(unix, not(target_os = "linux")))] + let new_mode = if is_parent { + (!mode::get_umask() & 0o777) | 0o300 + } else { + mode + }; + #[cfg(windows)] + let new_mode = mode; + + chmod(path, new_mode)?; + Ok(()) } - Err(_) if path.is_dir() => Ok(false), + + Err(_) if path.is_dir() => Ok(()), Err(e) => Err(e.into()), } } diff --git a/src/uucore/src/lib/features/fsxattr.rs b/src/uucore/src/lib/features/fsxattr.rs index 41dfb88389e..3fb626a3039 100644 --- a/src/uucore/src/lib/features/fsxattr.rs +++ b/src/uucore/src/lib/features/fsxattr.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore getxattr +// spell-checker:ignore getxattr posix_acl_default //! Set of functions to manage xattr on files and dirs use std::collections::HashMap; @@ -88,6 +88,58 @@ pub fn has_acl>(file: P) -> bool { } } +/// Returns the permissions bits of a file or directory which has Access Control List (ACL) entries based on its +/// extended attributes (Only works for linux) +/// +/// # Arguments +/// +/// * `source` - A reference to the path of the file. +/// +/// # Returns +/// +/// `u32` the perm bits of a file having extended attributes of type 'system.posix_acl_default' with permissions +/// otherwise returns a 0 if perm bits are 0 or the file has no extended attributes +pub fn get_acl_perm_bits_from_xattr>(source: P) -> u32 { + // TODO: Modify this to work on non linux unix systems. + + // Only default acl entries get inherited by objects under the path i.e. if child directories + // will have their permissions modified. + if let Ok(entries) = retrieve_xattrs(source) { + let mut perm: u32 = 0; + if let Some(value) = entries.get(&OsString::from("system.posix_acl_default")) { + // value is xattr byte vector + // value follows a starts with a 4 byte header, and then has posix_acl_entries, each + // posix_acl_entry is separated by a u32 sequence i.e. 0xFFFFFFFF + // + // struct posix_acl_entries { + // e_tag: u16 + // e_perm: u16 + // e_id: u32 + // } + // + // Reference: `https://github.com/torvalds/linux/blob/master/include/uapi/linux/posix_acl_xattr.h` + // + // The value of the header is 0x0002, so we skip the first four bytes of the value and + // process the rest + + let acl_entries = value + .split_at(3) + .1 + .iter() + .filter(|&x| *x != 255) + .copied() + .collect::>(); + + for entry in acl_entries.chunks_exact(4) { + // Third byte and fourth byte will be the perm bits + perm = (perm << 3) | entry[2] as u32 | entry[3] as u32; + } + return perm; + } + } + 0 +} + // FIXME: 3 tests failed on OpenBSD #[cfg(not(target_os = "openbsd"))] #[cfg(test)] @@ -138,6 +190,52 @@ mod tests { ); } + #[test] + #[cfg(target_os = "linux")] + fn test_get_perm_bits_from_xattrs() { + let temp_dir = tempdir().unwrap(); + let source_path = temp_dir.path().join("source_dir"); + + std::fs::create_dir(&source_path).unwrap(); + + let test_attr = "system.posix_acl_default"; + // posix_acl entries are in the form of + // struct posix_acl_entry{ + // tag: u16, + // perm: u16, + // id: u32, + // } + // the fields are serialized in little endian. + // The entries are preceded by a header of value of 0x0002 + // Reference: `` + // The id is undefined i.e. -1 which in u32 is 0xFFFFFFFF and tag and perm bits as given in the + // header file. + // Reference: `` + // + // + // There is a bindgen bug which generates the ACL_OTHER constant whose value is 0x20 into 32. + // which when the bug is fixed will need to be changed back to 20 from 32 in the vec 'test_value'. + // + // Reference `` + // + // The test_value vector is the header 0x0002 followed by tag and permissions for user_obj , tag + // and permissions and for group_obj and finally the tag and permissions for ACL_OTHER. Each + // entry has undefined id as mentioned above. + // + // + + let test_value = vec![ + 2, 0, 0, 0, 1, 0, 7, 0, 255, 255, 255, 255, 4, 0, 0, 0, 255, 255, 255, 255, 32, 0, 0, + 0, 255, 255, 255, 255, + ]; + + xattr::set(&source_path, test_attr, test_value.as_slice()).unwrap(); + + let perm_bits = get_acl_perm_bits_from_xattr(source_path); + + assert_eq!(0o700, perm_bits); + } + #[test] fn test_file_has_acl() { let temp_dir = tempdir().unwrap(); diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 7066af5807f..3b00ac1da4d 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -228,6 +228,61 @@ fn test_recursive_reporting() { .stdout_contains("created directory 'test_dir/../test_dir_a/../test_dir_b'"); } +#[test] +// Windows don't have acl entries +// TODO Enable and modify this for macos when xattr processing for macos is added. +// TODO Enable and modify this for freebsd when xattr processing for freebsd is enabled. +#[cfg(target_os = "linux")] +fn test_mkdir_acl() { + use std::{collections::HashMap, ffi::OsString}; + + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("a"); + + let mut map: HashMap> = HashMap::new(); + // posix_acl entries are in the form of + // struct posix_acl_entry{ + // tag: u16, + // perm: u16, + // id: u32, + // } + // the fields are serialized in little endian. + // The entries are preceded by a header of value of 0x0002 + // Reference: `` + // The id is undefined i.e. -1 which in u32 is 0xFFFFFFFF and tag and perm bits as given in the + // header file. + // Reference: `` + // + // + // There is a bindgen bug which generates the ACL_OTHER constant whose value is 0x20 into 32. + // which when the bug is fixed will need to be changed back to 20 from 32 in the vec 'xattr_val'. + // + // Reference `` + // + // The xattr_val vector is the header 0x0002 followed by tag and permissions for user_obj , tag + // and permissions and for group_obj and finally the tag and permissions for ACL_OTHER. Each + // entry has undefined id as mentioned above. + // + // + + let xattr_val: Vec = vec![ + 2, 0, 0, 0, 1, 0, 7, 0, 255, 255, 255, 255, 4, 0, 7, 0, 255, 255, 255, 255, 32, 0, 5, 0, + 255, 255, 255, 255, + ]; + + map.insert(OsString::from("system.posix_acl_default"), xattr_val); + + uucore::fsxattr::apply_xattrs(at.plus("a"), map).unwrap(); + + ucmd.arg("-p").arg("a/b").umask(0x077).succeeds(); + + let perms = at.metadata("a/b").permissions().mode(); + + // 0x770 would be user:rwx,group:rwx permissions + assert_eq!(perms, 16893); +} + #[test] fn test_mkdir_trailing_dot() { new_ucmd!().arg("-p").arg("-v").arg("test_dir").succeeds();