diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 012c14264..8a1e142df 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -13,7 +13,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.43.1" ## v1.43.0 + RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0 RUST_COV_SRV: "2021-05-06" ## (~v1.52.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel on: [push, pull_request] @@ -249,6 +249,8 @@ jobs: # { os, target, cargo-options, features, use-cross, toolchain } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only # - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only - { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } diff --git a/Cargo.lock b/Cargo.lock index 9245cb6df..912955deb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,29 @@ dependencies = [ "compare", ] +[[package]] +name = "bindgen" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger 0.8.4", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote 1.0.9", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -94,6 +117,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -144,6 +179,15 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +[[package]] +name = "cexpr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -169,6 +213,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "clang-sys" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +dependencies = [ + "glob 0.3.0", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.33.3" @@ -232,6 +287,7 @@ dependencies = [ "rand 0.7.3", "regex", "rlimit", + "selinux", "sha1", "tempfile", "textwrap", @@ -582,6 +638,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -624,6 +693,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "generic-array" version = "0.8.4" @@ -731,6 +806,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "if_rust_version" version = "1.0.0" @@ -786,12 +867,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + [[package]] name = "locale" version = "0.2.2" @@ -923,6 +1020,18 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "memchr 2.4.0", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1088,6 +1197,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pkg-config" version = "0.3.19" @@ -1179,7 +1294,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ - "env_logger", + "env_logger 0.7.1", "log", "rand 0.7.3", "rand_core 0.5.1", @@ -1200,6 +1315,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.5.6" @@ -1368,6 +1489,12 @@ dependencies = [ "redox_syscall 0.2.9", ] +[[package]] +name = "reference-counted-singleton" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e03aec30be874d0786379a6ee483c653c58dd6863ad4ed873e697ed968f9358" + [[package]] name = "regex" version = "1.5.4" @@ -1422,6 +1549,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "same-file" version = "1.0.6" @@ -1437,6 +1570,32 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "selinux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd525eeb189eb26c8471463186bba87644e3d8a9c7ae392adaf9ec45ede574bc" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "reference-counted-singleton", + "selinux-sys", + "thiserror", +] + +[[package]] +name = "selinux-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d842d177120716580c4c6cb56dfe3c5f3a3e3dcec635091f1b2034b6c0be4c6" +dependencies = [ + "bindgen", + "cc", + "dunce", + "walkdir", +] + [[package]] name = "sha1" version = "0.6.0" @@ -1468,6 +1627,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + [[package]] name = "signal-hook" version = "0.3.9" @@ -1565,6 +1730,12 @@ dependencies = [ "unicode-xid 0.2.2", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.2.0" @@ -1598,6 +1769,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "termion" version = "1.5.6" @@ -2075,6 +2255,7 @@ name = "uu_id" version = "0.0.7" dependencies = [ "clap", + "selinux", "uucore", "uucore_procs", ] @@ -2837,6 +3018,15 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "wild" version = "2.0.4" @@ -2889,6 +3079,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "xattr" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 46931b205..04bfef500 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ # coreutils (uutils) # * see the repository LICENSE, README, and CONTRIBUTING files for more information +# spell-checker:ignore (libs) libselinux + [package] name = "coreutils" version = "0.0.7" @@ -138,6 +140,11 @@ feat_os_unix_musl = [ # "feat_require_unix", ] +# "feat_selinux" == set of utilities providing support for SELinux Security Context if enabled with `--features feat_selinux`. +# NOTE: +# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. +# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. +feat_selinux = ["id/selinux", "selinux"] ## feature sets with requirements (restricting cross-platform availability) # # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities @@ -229,6 +236,7 @@ clap = { version = "2.33", features = ["wrap_help"] } lazy_static = { version="1.3" } textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review uucore = { version=">=0.0.9", package="uucore", path="src/uucore" } +selinux = { version="0.1.3", optional = true } # * uutils uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" } # diff --git a/build.rs b/build.rs index e9fe129eb..4fbb27cce 100644 --- a/build.rs +++ b/build.rs @@ -28,8 +28,8 @@ pub fn main() { if val == "1" && key.starts_with(env_feature_prefix) { let krate = key[env_feature_prefix.len()..].to_lowercase(); match krate.as_ref() { - "default" | "macos" | "unix" | "windows" => continue, // common/standard feature names - "nightly" | "test_unimplemented" => continue, // crate-local custom features + "default" | "macos" | "unix" | "windows" | "selinux" => continue, // common/standard feature names + "nightly" | "test_unimplemented" => continue, // crate-local custom features "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' s if s.starts_with(feature_prefix) => continue, // crate feature sets _ => {} // util feature name diff --git a/clippy.toml b/clippy.toml index 0a0a69a41..dbabdab50 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.43.1" +msrv = "1.47.0" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 367fb553c..518de2729 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -18,7 +18,11 @@ path = "src/id.rs" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } +selinux = { version="0.1.3", optional = true } [[bin]] name = "id" path = "src/main.rs" + +[features] +feat_selinux = ["selinux"] diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index d5acc97f3..9f92a4561 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -5,7 +5,10 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// + +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag cflag + +// README: // This was originally based on BSD's `id` // (noticeable in functionality, usage text, options text, etc.) // and synced with: @@ -25,8 +28,10 @@ // // * Help text based on BSD's `id` manpage and GNU's `id` manpage. // - -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag +// * This passes GNU's coreutils Test suite (8.32) for "tests/id/context.sh" if compiled with +// `--features feat_selinux`. It should also pass "tests/id/no-context.sh", but that depends on +// `uu_ls -Z` being implemented and therefore fails at the moment +// #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -35,6 +40,8 @@ extern crate uucore; use clap::{crate_version, App, Arg}; +#[cfg(all(target_os = "linux", feature = "selinux"))] +use selinux; use std::ffi::CStr; use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::libc; @@ -50,6 +57,11 @@ macro_rules! cstr2cow { static ABOUT: &str = "Print user and group information for each specified USER, or (when USER omitted) for the current user."; +#[cfg(not(feature = "selinux"))] +static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)"; +#[cfg(feature = "selinux")] +static CONTEXT_HELP_TEXT: &str = "print only the security context of the process"; + mod options { pub const OPT_AUDIT: &str = "audit"; // GNU's id does not have this pub const OPT_CONTEXT: &str = "context"; @@ -93,6 +105,8 @@ struct State { gsflag: bool, // --groups rflag: bool, // --real zflag: bool, // --zero + cflag: bool, // --context + selinux_supported: bool, ids: Option, // The behavior for calling GNU's `id` and calling GNU's `id $USER` is similar but different. // * The SELinux context is only displayed without a specified user. @@ -109,6 +123,7 @@ struct State { // 1000 10 968 975 // +++ exited with 0 +++ user_specified: bool, + exit_code: i32, } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -132,8 +147,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { gsflag: matches.is_present(options::OPT_GROUPS), rflag: matches.is_present(options::OPT_REAL_ID), zflag: matches.is_present(options::OPT_ZERO), + cflag: matches.is_present(options::OPT_CONTEXT), + + selinux_supported: { + #[cfg(feature = "selinux")] + { + selinux::kernel_support() != selinux::KernelSupport::Unsupported + } + #[cfg(not(feature = "selinux"))] + { + false + } + }, user_specified: !users.is_empty(), ids: None, + exit_code: 0, }; let default_format = { @@ -141,13 +169,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { !(state.uflag || state.gflag || state.gsflag) }; - if (state.nflag || state.rflag) && default_format { + if (state.nflag || state.rflag) && default_format && !state.cflag { crash!(1, "cannot print only names or real IDs in default format"); } - if (state.zflag) && default_format { + if state.zflag && default_format && !state.cflag { // NOTE: GNU test suite "id/zero.sh" needs this stderr output: crash!(1, "option --zero not permitted in default format"); } + if state.user_specified && state.cflag { + crash!(1, "cannot print security context when user specified"); + } let delimiter = { if state.zflag { @@ -163,7 +194,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { '\n' } }; - let mut exit_code = 0; + + if state.cflag { + if state.selinux_supported { + // print SElinux context and exit + #[cfg(all(target_os = "linux", feature = "selinux"))] + if let Ok(context) = selinux::SecurityContext::current(false) { + let bytes = context.as_bytes(); + print!("{}{}", String::from_utf8_lossy(bytes), line_ending); + } else { + // print error because `cflag` was explicitly requested + crash!(1, "can't get process context"); + } + return state.exit_code; + } else { + crash!(1, "--context (-Z) works only on an SELinux-enabled kernel"); + } + } for i in 0..=users.len() { let possible_pw = if !state.user_specified { @@ -173,7 +220,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(p) => Some(p), Err(_) => { show_error!("'{}': no such user", users[i]); - exit_code = 1; + state.exit_code = 1; if i + 1 >= users.len() { break; } else { @@ -187,17 +234,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(options::OPT_PASSWORD) { // BSD's `id` ignores all but the first specified user pline(possible_pw.map(|v| v.uid())); - return exit_code; + return state.exit_code; }; if matches.is_present(options::OPT_HUMAN_READABLE) { // BSD's `id` ignores all but the first specified user pretty(possible_pw); - return exit_code; + return state.exit_code; } if matches.is_present(options::OPT_AUDIT) { // BSD's `id` ignores specified users auditid(); - return exit_code; + return state.exit_code; } let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or(( @@ -217,7 +264,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if state.nflag { entries::gid2grp(gid).unwrap_or_else(|_| { show_error!("cannot find name for group ID {}", gid); - exit_code = 1; + state.exit_code = 1; gid.to_string() }) } else { @@ -232,7 +279,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if state.nflag { entries::uid2usr(uid).unwrap_or_else(|_| { show_error!("cannot find name for user ID {}", uid); - exit_code = 1; + state.exit_code = 1; uid.to_string() }) } else { @@ -257,7 +304,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if state.nflag { entries::gid2grp(id).unwrap_or_else(|_| { show_error!("cannot find name for group ID {}", id); - exit_code = 1; + state.exit_code = 1; id.to_string() }) } else { @@ -276,7 +323,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if default_format { - id_print(&state, groups); + id_print(&mut state, groups); } print!("{}", line_ending); @@ -285,7 +332,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } - exit_code + state.exit_code } pub fn uu_app() -> App<'static, 'static> { @@ -319,6 +366,7 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::OPT_GROUP) .short("g") .long(options::OPT_GROUP) + .conflicts_with(options::OPT_EFFECTIVE_USER) .help("Display only the effective group ID as a number"), ) .arg( @@ -328,6 +376,7 @@ pub fn uu_app() -> App<'static, 'static> { .conflicts_with_all(&[ options::OPT_GROUP, options::OPT_EFFECTIVE_USER, + options::OPT_CONTEXT, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT, @@ -379,7 +428,8 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::OPT_CONTEXT) .short("Z") .long(options::OPT_CONTEXT) - .help("NotImplemented: print only the security context of the process"), + .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER]) + .help(CONTEXT_HELP_TEXT), ) .arg( Arg::with_name(options::ARG_USERS) @@ -499,34 +549,80 @@ fn auditid() { println!("asid={}", auditinfo.ai_asid); } -fn id_print(state: &State, groups: Vec) { +fn id_print(state: &mut State, groups: Vec) { let uid = state.ids.as_ref().unwrap().uid; let gid = state.ids.as_ref().unwrap().gid; let euid = state.ids.as_ref().unwrap().euid; let egid = state.ids.as_ref().unwrap().egid; - print!("uid={}({})", uid, entries::uid2usr(uid).unwrap()); - print!(" gid={}({})", gid, entries::gid2grp(gid).unwrap()); + print!( + "uid={}({})", + uid, + entries::uid2usr(uid).unwrap_or_else(|_| { + show_error!("cannot find name for user ID {}", uid); + state.exit_code = 1; + uid.to_string() + }) + ); + print!( + " gid={}({})", + gid, + entries::gid2grp(gid).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", gid); + state.exit_code = 1; + gid.to_string() + }) + ); if !state.user_specified && (euid != uid) { - print!(" euid={}({})", euid, entries::uid2usr(euid).unwrap()); + print!( + " euid={}({})", + euid, + entries::uid2usr(euid).unwrap_or_else(|_| { + show_error!("cannot find name for user ID {}", euid); + state.exit_code = 1; + euid.to_string() + }) + ); } if !state.user_specified && (egid != gid) { - print!(" egid={}({})", euid, entries::gid2grp(egid).unwrap()); + print!( + " egid={}({})", + euid, + entries::gid2grp(egid).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", egid); + state.exit_code = 1; + egid.to_string() + }) + ); } print!( " groups={}", groups .iter() - .map(|&gr| format!("{}({})", gr, entries::gid2grp(gr).unwrap())) + .map(|&gr| format!( + "{}({})", + gr, + entries::gid2grp(gr).unwrap_or_else(|_| { + show_error!("cannot find name for group ID {}", gr); + state.exit_code = 1; + gr.to_string() + }) + )) .collect::>() .join(",") ); - // NOTE: (SELinux NotImplemented) placeholder: - // if !state.user_specified { - // // print SElinux context (does not depend on "-Z") - // print!(" context={}", get_selinux_contexts().join(":")); - // } + #[cfg(all(target_os = "linux", feature = "selinux"))] + if state.selinux_supported + && !state.user_specified + && std::env::var_os("POSIXLY_CORRECT").is_none() + { + // print SElinux context (does not depend on "-Z") + if let Ok(context) = selinux::SecurityContext::current(false) { + let bytes = context.as_bytes(); + print!(" context={}", String::from_utf8_lossy(bytes)); + } + } } #[cfg(not(target_os = "linux"))] diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index e62f105de..db918aa33 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -17,9 +17,9 @@ fn test_id_no_specified_user() { let exp_result = unwrap_or_return!(expected_result(&ts, &[])); let mut _exp_stdout = exp_result.stdout_str().to_string(); - #[cfg(target_os = "linux")] + #[cfg(not(feature = "feat_selinux"))] { - // NOTE: (SELinux NotImplemented) strip 'context' part from exp_stdout: + // NOTE: strip 'context' part from exp_stdout if selinux not enabled: // example: // uid=1001(runner) gid=121(docker) groups=121(docker),4(adm),101(systemd-journal) \ // context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 @@ -363,3 +363,88 @@ fn test_id_zero() { } } } + +#[test] +#[cfg(feature = "feat_selinux")] +fn test_id_context() { + use selinux::{self, KernelSupport}; + if selinux::kernel_support() == KernelSupport::Unsupported { + println!("test skipped: Kernel has no support for SElinux context",); + return; + } + let ts = TestScenario::new(util_name!()); + for c_flag in &["-Z", "--context"] { + ts.ucmd() + .args(&[c_flag]) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag])).stdout_str()); + for &z_flag in &["-z", "--zero"] { + let args = [c_flag, z_flag]; + ts.ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); + for &opt1 in &["--name", "--real"] { + // id: cannot print only names or real IDs in default format + let args = [opt1, c_flag]; + ts.ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); + let args = [opt1, c_flag, z_flag]; + ts.ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&ts, &args)).stdout_str()); + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G n/r z Z + // for now, we print clap's standard response for "conflicts_with" instead of: + // id: cannot print "only" of more than one choice + let args = [opt2, c_flag, opt1]; + let _result = ts.ucmd().args(&args).fails(); + // let exp_result = unwrap_or_return!(expected_result(&args)); + // result + // .stdout_is(exp_result.stdout_str()) + // .stderr_is(exp_result.stderr_str()) + // .code_is(exp_result.code()); + } + } + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G z Z + // for now, we print clap's standard response for "conflicts_with" instead of: + // id: cannot print "only" of more than one choice + let args = [opt2, c_flag]; + let _result = ts.ucmd().args(&args).fails(); + // let exp_result = unwrap_or_return!(expected_result(&args)); + // result + // .stdout_is(exp_result.stdout_str()) + // .stderr_is(exp_result.stderr_str()) + // .code_is(exp_result.code()); + } + } + } +} + +#[test] +#[cfg(unix)] +fn test_id_no_specified_user_posixly() { + // gnu/tests/id/no-context.sh + + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().env("POSIXLY_CORRECT", "1").run(); + assert!(!result.stdout_str().contains("context=")); + if !is_ci() { + result.success(); + } + + #[cfg(all(target_os = "linux", feature = "feat_selinux"))] + { + use selinux::{self, KernelSupport}; + if selinux::kernel_support() == KernelSupport::Unsupported { + println!("test skipped: Kernel has no support for SElinux context",); + } else { + let result = ts.ucmd().succeeds(); + assert!(result.stdout_str().contains("context=")); + } + } +}