Merge pull request #253 from mdcssw/main

Implemented `lsipc`
This commit is contained in:
Sylvestre Ledru
2025-04-01 08:28:00 +02:00
committed by GitHub
16 changed files with 3149 additions and 36 deletions

View File

@@ -15,6 +15,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
shell: bash
run: case ${{ matrix.os }} in ubuntu-*) sudo apt-get update --quiet && sudo apt-get install --quiet --no-install-recommends --assume-yes libclang-dev libc6-dev libsmartcols-dev libmount-dev ;; esac;
- run: cargo check
test:
@@ -26,6 +29,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
shell: bash
run: case ${{ matrix.os }} in ubuntu-*) sudo apt-get update --quiet && sudo apt-get install --quiet --no-install-recommends --assume-yes libclang-dev libc6-dev libsmartcols-dev libmount-dev ;; esac;
- run: cargo test
fmt:
@@ -34,6 +40,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: sudo apt-get update --quiet
- run: sudo apt-get install --quiet --no-install-recommends --assume-yes libclang-dev libc6-dev libsmartcols-dev libmount-dev
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
@@ -46,6 +54,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
shell: bash
run: case ${{ matrix.os }} in ubuntu-*) sudo apt-get update --quiet && sudo apt-get install --quiet --no-install-recommends --assume-yes libclang-dev libc6-dev libsmartcols-dev libmount-dev ;; esac;
- run: rustup component add clippy
- run: cargo clippy -- -D warnings
@@ -79,6 +90,10 @@ jobs:
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
outputs CODECOV_FLAGS
- name: Install dependencies
shell: bash
run: case ${{ matrix.job.os }} in ubuntu-*) sudo apt-get update --quiet && sudo apt-get install --quiet --no-install-recommends --assume-yes libclang-dev libc6-dev libsmartcols-dev libmount-dev ;; esac;
- name: rust toolchain ~ install
uses: dtolnay/rust-toolchain@nightly
- name: Install llvm-tools-preview

216
Cargo.lock generated
View File

@@ -82,6 +82,26 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.0"
@@ -105,13 +125,22 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.16"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom 7.1.3",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -140,9 +169,9 @@ dependencies = [
[[package]]
name = "chrono-tz"
version = "0.10.1"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c6ac4f2c0bf0f44e9161aec9675e1050aa4a530663c4a9e37e108fa948bca9f"
checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3"
dependencies = [
"chrono",
"chrono-tz-build",
@@ -151,14 +180,25 @@ dependencies = [
[[package]]
name = "chrono-tz-build"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7"
checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402"
dependencies = [
"parse-zoneinfo",
"phf_codegen",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.34"
@@ -230,9 +270,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
dependencies = [
"powerfmt",
]
@@ -265,6 +305,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -329,14 +381,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
version = "0.1.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.52.0",
]
@@ -366,6 +419,15 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@@ -388,6 +450,16 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -402,9 +474,9 @@ checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "md-5"
@@ -422,6 +494,12 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.29.0"
@@ -434,6 +512,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nom"
version = "8.0.0"
@@ -493,17 +581,17 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.1"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "os_display"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6229bad892b46b0dcfaaeb18ad0d2e56400f5aaea05b768bde96e73676cf75"
checksum = "ad5fd71b79026fb918650dde6d125000a233764f1c2f1659a1c71118e33ea08f"
dependencies = [
"unicode-width 0.1.14",
"unicode-width",
]
[[package]]
@@ -522,7 +610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bffd1156cebf13f681d7769924d3edfb9d9d71ba206a8d8e8e7eb9df4f4b1e7"
dependencies = [
"chrono",
"nom",
"nom 8.0.0",
"regex",
]
@@ -589,6 +677,16 @@ dependencies = [
"yansi",
]
[[package]]
name = "prettyplease"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.94"
@@ -730,6 +828,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
@@ -768,6 +872,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.219"
@@ -813,6 +926,18 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "smartcols-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95f44699de6c620259d5a4fe55dc9e6a1a98af93f935567f82bf02fad5c2295d"
dependencies = [
"bindgen",
"cc",
"dunce",
"walkdir",
]
[[package]]
name = "smawk"
version = "0.3.2"
@@ -897,7 +1022,7 @@ dependencies = [
"smawk",
"terminal_size",
"unicode-linebreak",
"unicode-width 0.2.0",
"unicode-width",
]
[[package]]
@@ -922,9 +1047,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.40"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@@ -945,9 +1070,9 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.21"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
@@ -971,12 +1096,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
@@ -1017,6 +1136,7 @@ dependencies = [
"uu_fsfreeze",
"uu_last",
"uu_lscpu",
"uu_lsipc",
"uu_lslocks",
"uu_lsmem",
"uu_mcookie",
@@ -1105,6 +1225,17 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_lsipc"
version = "0.0.1"
dependencies = [
"clap",
"errno",
"libc",
"smartcols-sys",
"uucore",
]
[[package]]
name = "uu_lslocks"
version = "0.0.1"
@@ -1222,6 +1353,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
@@ -1314,6 +1455,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -1563,18 +1713,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -33,6 +33,7 @@ feat_common_core = [
"fsfreeze",
"last",
"lscpu",
"lsipc",
"lslocks",
"lsmem",
"mcookie",
@@ -48,24 +49,26 @@ clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.4"
clap_mangen = "0.2"
dns-lookup = "2.0.4"
libc = "0.2.152"
errno = "0.3"
libc = "0.2.171"
linux-raw-sys = { version = "0.9.0", features = ["ioctl"] }
md-5 = "0.10.6"
nix = { version = "0.29", default-features = false }
phf = "0.11.2"
phf_codegen = "0.11.2"
rand = { version = "0.9.0", features = ["small_rng"] }
rangemap = "1.5.1"
regex = "1.10.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0.122", features = ["preserve_order"] }
smartcols-sys = "0.1"
syscall-numbers = "4.0.2"
sysinfo = "0.34"
tempfile = "3.9.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "2.0"
uucore = "0.0.30"
xattr = "1.3.1"
rangemap = "1.5.1"
syscall-numbers = "4.0.2"
[dependencies]
clap = { workspace = true }
@@ -86,6 +89,7 @@ dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/
fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" }
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
lsipc = { optional = true, version = "0.0.1", package = "uu_lsipc", path = "src/uu/lsipc" }
lslocks = { optional = true, version = "0.0.1", package = "uu_lslocks", path = "src/uu/lslocks" }
lsmem = { optional = true, version = "0.0.1", package = "uu_lsmem", path = "src/uu/lsmem" }
mcookie = { optional = true, version = "0.0.1", package = "uu_mcookie", path = "src/uu/mcookie" }

18
src/uu/lsipc/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "uu_lsipc"
version = "0.0.1"
edition = "2024"
[lib]
path = "src/lsipc.rs"
[[bin]]
name = "lsipc"
path = "src/main.rs"
[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
libc = { workspace = true }
errno = { workspace = true }
smartcols-sys = { workspace = true }

View File

@@ -0,0 +1,43 @@
Generic columns:
KEY Resource key
ID Resource ID
OWNER Owner's username or UID
PERMS Permissions
CUID Creator UID
CUSER Creator user
CGID Creator GID
CGROUP Creator group
UID User ID
USER User name
GID Group ID
GROUP Group name
CTIME Time of the last change
Shared-memory columns (--shmems):
SIZE Segment size
NATTCH Number of attached processes
STATUS Status
ATTACH Attach time
DETACH Detach time
COMMAND Creator command line
CPID PID of the creator
LPID PID of last user
Message-queue columns (--queues):
USEDBYTES Bytes used
MSGS Number of messages
SEND Time of last msg sent
RECV Time of last msg received
LSPID PID of the last msg sender
LRPID PID of the last msg receiver
Semaphore columns (--semaphores):
NSEMS Number of semaphores
OTIME Time of the last operation
Summary columns (--global):
RESOURCE Resource name
DESCRIPTION Resource description
LIMIT System-wide limit
USED Currently used
USE% Currently use percentage

10
src/uu/lsipc/lsipc.md Normal file
View File

@@ -0,0 +1,10 @@
# lsipc
```
lsipc [-g|--global] [{-m|--shmems|-q|--queues|-s|--semaphores} [-c|--creator] [-t|--time]] [-e|--export|-J|--json|-l|--list|-n|--newline|-r|--raw] [-b|--bytes] [--noheadings] [--notruncate] [-o list|--output list] [-P|--numeric-perms] [--time-format type] [-y|--shell]
lsipc {-i id|--id id} {-m|--shmems|-q|--queues|-s|--semaphores} [-e|--export|-J|--json|-l|--list|-n|--newline|-r|--raw] [-b|--bytes] [-c|--creator] [--noheadings] [--notruncate] [-o list|--output list] [-P|--numeric-perms] [-t|--time] [--time-format type] [-y|--shell]
lsipc {-V|--version}
lsipc {-h|--help}
```
show information on IPC facilities currently employed in the system.

301
src/uu/lsipc/src/column.rs Normal file
View File

@@ -0,0 +1,301 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{CStr, c_uint};
use std::str::FromStr;
use smartcols_sys::{SCOLS_FL_NOEXTREMES, SCOLS_FL_RIGHT, SCOLS_FL_TRUNC};
use crate::errors::LsIpcError;
#[derive(Debug, Copy, Clone)]
pub(crate) struct ColumnInfo {
pub(crate) id: &'static CStr,
pub(crate) title: &'static str,
pub(crate) width_hint: f64,
pub(crate) flags: c_uint,
}
impl ColumnInfo {
const fn new(id: &'static CStr, title: &'static str, flags: c_uint) -> Self {
Self {
id,
title,
width_hint: 1.0,
flags,
}
}
}
pub(crate) static COLUMN_INFOS: [ColumnInfo; 34] = [
// Generic
ColumnInfo::new(c"KEY", "Key", 0),
ColumnInfo::new(c"ID", "ID", 0),
ColumnInfo::new(c"OWNER", "Owner", SCOLS_FL_RIGHT),
ColumnInfo::new(c"PERMS", "Permissions", SCOLS_FL_RIGHT),
ColumnInfo::new(c"CUID", "Creator UID", SCOLS_FL_RIGHT),
ColumnInfo::new(c"CUSER", "Creator user", 0),
ColumnInfo::new(c"CGID", "Creator GID", SCOLS_FL_RIGHT),
ColumnInfo::new(c"CGROUP", "Creator group", 0),
ColumnInfo::new(c"UID", "UID", SCOLS_FL_RIGHT),
ColumnInfo::new(c"USER", "User name", 0),
ColumnInfo::new(c"GID", "GID", SCOLS_FL_RIGHT),
ColumnInfo::new(c"GROUP", "Group name", 0),
ColumnInfo::new(c"CTIME", "Last change", SCOLS_FL_RIGHT),
// Message queues
ColumnInfo::new(c"USEDBYTES", "Bytes used", SCOLS_FL_RIGHT),
ColumnInfo::new(c"MSGS", "Messages", 0),
ColumnInfo::new(c"SEND", "Msg sent", SCOLS_FL_RIGHT),
ColumnInfo::new(c"RECV", "Msg received", SCOLS_FL_RIGHT),
ColumnInfo::new(c"LSPID", "Msg sender", SCOLS_FL_RIGHT),
ColumnInfo::new(c"LRPID", "Msg receiver", SCOLS_FL_RIGHT),
// Shared memory
ColumnInfo::new(c"SIZE", "Segment size", SCOLS_FL_RIGHT),
ColumnInfo::new(c"NATTCH", "Attached processes", SCOLS_FL_RIGHT),
ColumnInfo::new(c"STATUS", "Status", SCOLS_FL_NOEXTREMES),
ColumnInfo::new(c"ATTACH", "Attach time", SCOLS_FL_RIGHT),
ColumnInfo::new(c"DETACH", "Detach time", SCOLS_FL_RIGHT),
ColumnInfo {
id: c"COMMAND",
title: "Creator command",
width_hint: 0.0,
flags: SCOLS_FL_TRUNC,
},
ColumnInfo::new(c"CPID", "Creator PID", SCOLS_FL_RIGHT),
ColumnInfo::new(c"LPID", "Last user PID", SCOLS_FL_RIGHT),
// Semaphores
ColumnInfo::new(c"NSEMS", "Semaphores", SCOLS_FL_RIGHT),
ColumnInfo::new(c"OTIME", "Last operation", SCOLS_FL_RIGHT),
// Summary
ColumnInfo::new(c"RESOURCE", "Resource", 0),
ColumnInfo::new(c"DESCRIPTION", "Description", 0),
ColumnInfo::new(c"USED", "Used", SCOLS_FL_RIGHT),
ColumnInfo::new(c"USE%", "Use", SCOLS_FL_RIGHT),
ColumnInfo::new(c"LIMIT", "Limit", SCOLS_FL_RIGHT),
];
mod all {
pub(crate) static GENERIC: [&str; 13] = [
"KEY", "ID", "OWNER", "PERMS", "CUID", "CUSER", "CGID", "CGROUP", "UID", "USER", "GID",
"GROUP", "CTIME",
];
pub(crate) static QUEUES: [&str; 6] = ["USEDBYTES", "MSGS", "SEND", "RECV", "LSPID", "LRPID"];
pub(crate) static SHARED_MEMORY: [&str; 8] = [
"SIZE", "NATTCH", "STATUS", "ATTACH", "DETACH", "COMMAND", "CPID", "LPID",
];
pub(crate) static SEMAPHORES: [&str; 2] = ["NSEMS", "OTIME"];
pub(crate) static SUMMARY: [&str; 5] = ["RESOURCE", "DESCRIPTION", "USED", "USE%", "LIMIT"];
}
mod default {
pub(crate) static QUEUES: [&str; 8] = [
"KEY",
"ID",
"PERMS",
"OWNER",
"USEDBYTES",
"MSGS",
"LSPID",
"LRPID",
];
pub(crate) static SHARED_MEMORY: [&str; 11] = [
"KEY", "ID", "PERMS", "OWNER", "SIZE", "NATTCH", "STATUS", "CTIME", "CPID", "LPID",
"COMMAND",
];
pub(crate) static SEMAPHORES: [&str; 5] = ["KEY", "ID", "PERMS", "OWNER", "NSEMS"];
pub(crate) static GLOBAL: [&str; 5] = ["RESOURCE", "DESCRIPTION", "LIMIT", "USED", "USE%"];
pub(crate) static CREATOR: [&str; 4] = ["CUID", "CGID", "UID", "GID"];
}
#[derive(Debug, Clone)]
pub(crate) struct OutputColumns {
pub(crate) append: bool,
pub(crate) list: Vec<&'static ColumnInfo>,
}
impl Default for OutputColumns {
fn default() -> Self {
Self {
append: true,
list: Vec::default(),
}
}
}
impl FromStr for OutputColumns {
type Err = LsIpcError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let suffix = s.strip_prefix('+');
let append = suffix.is_some();
let list: Vec<_> = suffix
.unwrap_or(s)
.split(',')
.map(|name| {
COLUMN_INFOS
.iter()
.find(|&column| column.id.to_str().unwrap() == name)
.ok_or_else(|| LsIpcError::InvalidColumnName(name.into()))
})
.collect::<Result<_, _>>()?;
if list.is_empty() {
Err(LsIpcError::InvalidColumnSequence(s.into()))
} else {
Ok(Self { append, list })
}
}
}
impl From<&'_ clap::ArgMatches> for OutputColumns {
fn from(args: &'_ clap::ArgMatches) -> Self {
let Some(columns) = args.get_one::<Self>(crate::options::OUTPUT) else {
return Self::default();
};
let allowed_column_names = if args.get_flag(crate::options::GLOBAL) {
&all::SUMMARY[..]
} else if args.get_flag(crate::options::QUEUES) {
&all::QUEUES[..]
} else if args.get_flag(crate::options::SHMEMS) {
&all::SHARED_MEMORY[..]
} else if args.get_flag(crate::options::SEMAPHORES) {
&all::SEMAPHORES[..]
} else {
unreachable!()
};
let (list, not_applicable): (Vec<&ColumnInfo>, Vec<&ColumnInfo>) =
columns.list.iter().partition(|&&column| {
let id = column.id.to_str().unwrap();
all::GENERIC.contains(&id) || allowed_column_names.contains(&id)
});
if !not_applicable.is_empty() {
let join = move |mut buffer: String, column: &ColumnInfo| {
if !buffer.is_empty() {
buffer.push(',');
}
buffer.push_str(column.id.to_str().unwrap());
buffer
};
let not_applicable = not_applicable.into_iter().fold(String::default(), join);
eprintln!("The following columns do not apply to the specified IPC: {not_applicable}.");
}
Self {
append: columns.append,
list,
}
}
}
pub(crate) fn all_defaults(
args: &clap::ArgMatches,
) -> Result<Vec<&'static ColumnInfo>, LsIpcError> {
let mut iter: Box<dyn Iterator<Item = &str>> = Box::new(all::GENERIC.into_iter());
if args.get_flag(crate::options::QUEUES) {
iter = Box::new(iter.chain(all::QUEUES));
}
if args.get_flag(crate::options::SHMEMS) {
iter = Box::new(iter.chain(all::SHARED_MEMORY));
}
if args.get_flag(crate::options::SEMAPHORES) {
iter = Box::new(iter.chain(all::SEMAPHORES));
}
iter.map(|name| {
COLUMN_INFOS
.iter()
.find(|&column| column.id.to_str().unwrap() == name)
.ok_or_else(|| LsIpcError::InvalidColumnName(name.into()))
})
.collect::<Result<_, _>>()
}
pub(crate) fn filter_defaults(
args: &clap::ArgMatches,
) -> Result<Vec<&'static ColumnInfo>, LsIpcError> {
let mut columns = Vec::default();
if args.get_flag(crate::options::QUEUES) {
columns.extend(default::QUEUES);
}
if args.get_flag(crate::options::SHMEMS) {
columns.extend(default::SHARED_MEMORY);
}
if args.get_flag(crate::options::SEMAPHORES) {
columns.extend(default::SEMAPHORES);
}
if args.get_flag(crate::options::GLOBAL) {
columns.extend(default::GLOBAL);
}
if args.get_flag(crate::options::CREATOR) {
columns.extend(default::CREATOR);
}
if args.get_flag(crate::options::TIME) {
if args.get_flag(crate::options::QUEUES)
|| (!args.get_flag(crate::options::SHMEMS)
&& !args.get_flag(crate::options::SEMAPHORES))
{
columns.extend(["SEND", "RECV", "CTIME"])
}
if args.get_flag(crate::options::SHMEMS)
|| (!args.get_flag(crate::options::QUEUES)
&& !args.get_flag(crate::options::SEMAPHORES))
{
// If "COMMAND" was the last column, then keep it last.
let reappend_command = match columns.pop() {
None => false,
Some("COMMAND") => true,
Some(last) => {
columns.push(last);
false
}
};
columns.extend(["ATTACH", "DETACH"]);
if reappend_command {
columns.push("COMMAND");
}
}
if args.get_flag(crate::options::SEMAPHORES)
|| (!args.get_flag(crate::options::QUEUES) && !args.get_flag(crate::options::SHMEMS))
{
columns.extend(["OTIME", "CTIME"])
}
}
columns
.into_iter()
.map(|name| {
COLUMN_INFOS
.iter()
.find(|&column| column.id.to_str().unwrap() == name)
.ok_or_else(|| LsIpcError::InvalidColumnName(name.into()))
})
.collect::<Result<_, _>>()
}

357
src/uu/lsipc/src/display.rs Normal file
View File

@@ -0,0 +1,357 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::borrow::Cow;
use std::ffi::{CStr, CString, c_uint};
use std::io::{Cursor, Write};
use std::ptr::NonNull;
use std::sync::atomic::AtomicPtr;
use std::{io, ptr};
use crate::column::ColumnInfo;
use crate::errors::LsIpcError;
use crate::smartcols::{IterDirection, Table, TableOperations, TableRef};
use crate::utils::{UserDbRecordRef, find_replace_in_vec, local_time};
use crate::{OutputMode, options};
fn decimal_point() -> &'static str {
use std::sync::atomic::Ordering;
static DEFAULT: &CStr = c".";
static VALUE: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
let mut decimal_point = VALUE.load(Ordering::Acquire);
if decimal_point.is_null() {
decimal_point = unsafe { libc::localeconv().as_ref() }
.and_then(|lc| (!lc.decimal_point.is_null()).then_some(lc.decimal_point))
.unwrap_or(DEFAULT.as_ptr().cast_mut())
.cast();
match VALUE.compare_exchange(
ptr::null_mut(),
decimal_point,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_previous_value) => {}
Err(previous_value) => decimal_point = previous_value,
}
}
unsafe { CStr::from_ptr(decimal_point.cast()) }
.to_str()
.unwrap()
}
fn ascii_mode(mode: c_uint) -> [u8; 11] {
use libc::{
S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP,
S_IXOTH, S_IXUSR,
};
let mut buffer = Cursor::new([0_u8; 11]);
match mode & libc::S_IFMT {
libc::S_IFDIR => write!(&mut buffer, "d"),
libc::S_IFLNK => write!(&mut buffer, "l"),
libc::S_IFCHR => write!(&mut buffer, "c"),
libc::S_IFBLK => write!(&mut buffer, "b"),
libc::S_IFSOCK => write!(&mut buffer, "s"),
libc::S_IFIFO => write!(&mut buffer, "p"),
libc::S_IFREG => write!(&mut buffer, "-"),
_ => Ok(()),
}
.unwrap();
for (readable, writable, executable, set_id_flag, set_id, set_id_without_x) in [
(S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID, 's', 'S'),
(S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID, 's', 'S'),
(S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX, 't', 'T'),
] {
let r = if (mode & readable) == 0 { '-' } else { 'r' };
let w = if (mode & writable) == 0 { '-' } else { 'w' };
let x = match ((mode & set_id_flag) == 0, (mode & executable) == 0) {
(true, true) => '-',
(true, false) => 'x',
(false, true) => set_id_without_x,
(false, false) => set_id,
};
write!(&mut buffer, "{r}{w}{x}").unwrap();
}
write!(&mut buffer, "\x00").unwrap();
buffer.into_inner()
}
fn strftime(format: &CStr, detailed_time: &libc::tm, buffer: &mut [u8]) -> Result<(), LsIpcError> {
buffer.fill(0_u8);
errno::set_errno(errno::Errno(0));
let r = unsafe {
libc::strftime(
buffer.as_mut_ptr().cast(),
buffer.len(),
format.as_ptr(),
detailed_time,
)
};
if r == 0 {
let err = io::Error::last_os_error();
if err.raw_os_error().is_some_and(|n| n != 0) {
return Err(LsIpcError::io0("strftime", err));
}
}
Ok(())
}
pub(crate) fn format_time(
format: crate::TimeFormat,
now: &libc::timeval,
time: libc::time_t,
) -> Result<Option<CString>, LsIpcError> {
if time == 0 {
return Ok(None);
}
let mut buffer: [u8; 256];
match format {
crate::TimeFormat::Short => {
let detailed_time = local_time(time)?;
let detailed_now = local_time(now.tv_sec)?;
if detailed_time.tm_yday == detailed_now.tm_yday
&& detailed_time.tm_year == detailed_now.tm_year
{
let result = format!("{:02}:{:02}", detailed_time.tm_hour, detailed_time.tm_min);
return Ok(Some(CString::new(result).unwrap()));
} else if detailed_time.tm_year == detailed_now.tm_year {
buffer = [0_u8; 256];
strftime(c"%b%d", &detailed_time, &mut buffer)?;
} else {
buffer = [0_u8; 256];
strftime(c"%Y-%b%d", &detailed_time, &mut buffer)?;
}
}
crate::TimeFormat::Full => {
let detailed_time = local_time(time)?;
buffer = [0_u8; 256];
if unsafe { libc::asctime_r(&detailed_time, buffer.as_mut_ptr().cast()) }.is_null() {
return Err(LsIpcError::last_io0("asctime_r"));
}
find_replace_in_vec(b'\n', 0_u8, &mut buffer);
}
crate::TimeFormat::Iso => {
let detailed_time = local_time(time)?;
let tz_minutes = if detailed_time.tm_isdst < 0 {
0
} else {
detailed_time.tm_gmtoff / 60
};
let tz_hours = tz_minutes / 60;
let tz_minutes = (tz_minutes % 60).abs();
buffer = [0_u8; 256];
let mut cursor = Cursor::new(buffer);
write!(
&mut cursor,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{tz_hours:+03}:{tz_minutes:02}",
detailed_time.tm_year + 1900,
detailed_time.tm_mon + 1,
detailed_time.tm_mday,
detailed_time.tm_hour,
detailed_time.tm_min,
detailed_time.tm_sec,
)
.map_err(|err| LsIpcError::io0("failed to write data", err))?;
buffer = cursor.into_inner();
}
}
*buffer.last_mut().unwrap() = 0_u8;
let result = CStr::from_bytes_until_nul(&buffer).unwrap();
Ok(Some(CString::from(result)))
}
// returns exponent (2^x=n) in range KiB..EiB (2^10..2^60).
fn bytes_exponent(bytes: u64) -> u64 {
for shift in (10..=60).step_by(10) {
if bytes < (1 << shift) {
return shift - 10;
}
}
60
}
fn size_to_human_string(bytes: u64) -> String {
static LETTERS: [char; 7] = ['B', 'K', 'M', 'G', 'T', 'P', 'E'];
let exp = bytes_exponent(bytes);
let unit = LETTERS[if exp == 0 { 0 } else { (exp / 10) as usize }];
let mut decimal = if exp == 0 { bytes } else { bytes / (1 << exp) };
let mut fractional = if exp == 0 { 0 } else { bytes % (1 << exp) };
if fractional != 0 {
fractional = if fractional >= (u64::MAX / 1000) {
((fractional / 1024) * 1000) / (1 << (exp - 10))
} else {
(fractional * 1000) / (1 << exp)
};
fractional = ((fractional + 50) / 100) * 10;
if fractional == 100 {
decimal += 1;
fractional = 0;
}
}
if fractional == 0 {
format!("{decimal}{unit}")
} else {
format!("{decimal}{}{fractional:02}{unit}", decimal_point())
}
}
fn size_desc(size: u64, in_bytes: bool) -> String {
if in_bytes {
size.to_string()
} else {
size_to_human_string(size)
}
}
pub(crate) fn describe_key(key: libc::key_t) -> Option<Cow<'static, CStr>> {
Some(Cow::Owned(CString::new(format!("{key:#010x}")).unwrap()))
}
pub(crate) fn describe_integer<T: ToString>(n: T) -> Option<Cow<'static, CStr>> {
Some(Cow::Owned(CString::new(n.to_string()).unwrap()))
}
pub(crate) fn describe_size(size: u64, in_bytes: bool) -> Option<Cow<'static, CStr>> {
Some(Cow::Owned(CString::new(size_desc(size, in_bytes)).unwrap()))
}
pub(crate) fn describe_owner(users: &mut UserDbRecordRef, uid: libc::uid_t) -> Option<Cow<CStr>> {
if let Some(name) = users.for_id(uid).name() {
Some(Cow::Borrowed(name))
} else {
Some(Cow::Owned(CString::new(uid.to_string()).unwrap()))
}
}
pub(crate) fn describe_permissions(
args: &clap::ArgMatches,
permissions: c_uint,
) -> Option<Cow<'static, CStr>> {
if args.get_flag(options::NUMERIC_PERMS) {
let s = format!("{permissions:04o}");
Some(Cow::Owned(CString::new(s).unwrap()))
} else {
let s = ascii_mode(permissions);
let s = CStr::from_bytes_until_nul(&s).unwrap();
Some(Cow::Owned(s.into()))
}
}
pub(crate) fn new_global_line(
columns: &[&ColumnInfo],
table: &mut Table,
resource: &CStr,
description: &CStr,
used: Option<u64>,
limit: u64,
in_bytes: bool,
) -> Result<(), LsIpcError> {
let mut line = table.new_line(None)?;
for (cell_index, &column) in columns.iter().enumerate() {
let data_str = match column.id.to_bytes() {
b"RESOURCE" => Cow::Borrowed(resource),
b"DESCRIPTION" => Cow::Borrowed(description),
b"LIMIT" => Cow::Owned(CString::new(size_desc(limit, in_bytes)).unwrap()),
b"USED" => used.map_or(Cow::Borrowed(c"-"), |used| {
Cow::Owned(CString::new(size_desc(used, in_bytes)).unwrap())
}),
b"USE%" => used.map_or(Cow::Borrowed(c"-"), |used| {
let percent = (used as f64) / (limit as f64) * 100.0;
Cow::Owned(CString::new(format!("{percent:2.2}%")).unwrap())
}),
_ => continue,
};
line.set_data(cell_index, &data_str)?;
}
Ok(())
}
pub(crate) fn new_table(
args: &clap::ArgMatches,
output_mode: OutputMode,
) -> Result<Table, LsIpcError> {
let mut table = Table::new()?;
if args.get_flag(options::NO_HEADINGS) {
table.enable_headings(false)?;
}
if args.get_flag(options::SHELL) {
table.enable_shell_variable(true)?;
}
match output_mode {
OutputMode::Export => table.enable_export(true)?,
OutputMode::NewLine => {
table.set_column_separator(c"\n")?;
table.enable_export(true)?
}
OutputMode::Raw => table.enable_raw(true)?,
OutputMode::Json => table.enable_json(true)?,
OutputMode::Pretty => table.enable_headings(false)?,
OutputMode::None | OutputMode::List => {}
}
Ok(table)
}
pub(crate) fn print_pretty_table(table: &Table) -> Result<(), LsIpcError> {
let line = table.line(0)?;
for (cell_index, _column) in table.column_iter(IterDirection::Forward)?.enumerate() {
if let Some(dstr) = line
.cell(cell_index)?
.data_as_c_str()
.filter(|&s| !s.is_empty())
.map(CStr::to_bytes)
.and_then(|b| std::str::from_utf8(b).ok())
{
let title = crate::column::COLUMN_INFOS[cell_index].title;
println!("{title}:{}{dstr: <36}", " ".repeat(35 - title.len()));
}
}
if let Some(sub_table) = NonNull::new(line.user_data().cast()).map(TableRef::from) {
println!("Elements:");
println!();
sub_table.print()?;
}
Ok(())
}

View File

@@ -0,0 +1,74 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::c_int;
use std::fmt;
use std::path::PathBuf;
use uucore::error::UError;
#[derive(Debug)]
pub enum LsIpcError {
InvalidColumnName(String),
InvalidColumnSequence(String),
InvalidTimeFormat(String),
IO0(String, std::io::Error),
IO1(String, PathBuf, std::io::Error),
}
impl LsIpcError {
pub(crate) fn io0(message: impl Into<String>, error: impl Into<std::io::Error>) -> Self {
Self::IO0(message.into(), error.into())
}
pub(crate) fn last_io0(message: impl Into<String>) -> Self {
let err = std::io::Error::last_os_error();
Self::IO0(message.into(), err)
}
pub(crate) fn io1(
message: impl Into<String>,
path: impl Into<PathBuf>,
error: impl Into<std::io::Error>,
) -> Self {
Self::IO1(message.into(), path.into(), error.into())
}
pub(crate) fn io_from_neg_errno(
message: impl Into<String>,
result: c_int,
) -> Result<usize, LsIpcError> {
if let Ok(result) = usize::try_from(result) {
Ok(result)
} else {
let err = std::io::Error::from_raw_os_error(-result);
Err(Self::IO0(message.into(), err))
}
}
}
impl fmt::Display for LsIpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IO0(message, err) => write!(f, "{message}: {err}"),
Self::IO1(message, path, err) => write!(f, "{message} '{}': {err}", path.display()),
Self::InvalidColumnName(name) => write!(f, "invalid column name: {name}"),
Self::InvalidColumnSequence(seq) => write!(f, "invalid column sequence: {seq}"),
Self::InvalidTimeFormat(mode) => write!(f, "invalid time format: {mode}"),
}
}
}
impl UError for LsIpcError {
fn code(&self) -> i32 {
1
}
fn usage(&self) -> bool {
false
}
}
impl std::error::Error for LsIpcError {}

425
src/uu/lsipc/src/lsipc.rs Normal file
View File

@@ -0,0 +1,425 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// Remove this if the tool is ported to Non-UNIX platforms.
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[cfg(target_os = "linux")]
mod column;
#[cfg(target_os = "linux")]
mod display;
mod errors;
#[cfg(target_os = "linux")]
mod message_queue;
#[cfg(target_os = "linux")]
mod semaphore;
#[cfg(target_os = "linux")]
mod shared_memory;
#[cfg(target_os = "linux")]
mod smartcols;
#[cfg(target_os = "linux")]
mod utils;
use std::ffi::c_uint;
use std::str::FromStr;
use clap::builder::{EnumValueParser, PossibleValue};
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueEnum, crate_version, value_parser};
use uucore::{error::UResult, format_usage, help_about, help_usage};
#[cfg(target_os = "linux")]
use crate::column::{ColumnInfo, OutputColumns};
use crate::errors::LsIpcError;
#[cfg(target_os = "linux")]
use crate::smartcols::TableOperations;
mod options {
pub static BYTES: &str = "bytes";
pub static CREATOR: &str = "creator";
pub static EXPORT: &str = "export";
pub static GLOBAL: &str = "global";
pub static ID: &str = "id";
pub static JSON: &str = "json";
pub static LIST: &str = "list";
pub static NEW_LINE: &str = "newline";
pub static NO_HEADINGS: &str = "noheadings";
pub static NO_TRUNCATE: &str = "notruncate";
pub static NUMERIC_PERMS: &str = "numeric-perms";
pub static OUTPUT: &str = "output";
pub static QUEUES: &str = "queues";
pub static RAW: &str = "raw";
pub static SEMAPHORES: &str = "semaphores";
pub static SHELL: &str = "shell";
pub static SHMEMS: &str = "shmems";
pub static TIME_FORMAT: &str = "time-format";
pub static TIME: &str = "time";
pub static TYPE: &str = "type";
pub const SHORT: &str = "short";
pub const FULL: &str = "full";
pub const ISO: &str = "iso";
}
const ABOUT: &str = help_about!("lsipc.md");
const USAGE: &str = help_usage!("lsipc.md");
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new(options::BYTES)
.short('b')
.long(options::BYTES)
.action(ArgAction::SetTrue)
.help("print SIZE in bytes rather than in human readable format"),
)
.arg(
Arg::new(options::CREATOR)
.short('c')
.long(options::CREATOR)
.action(ArgAction::SetTrue)
.requires("ipc-kind")
.help("show creator and owner"),
)
.arg(
Arg::new(options::EXPORT)
.short('e')
.long(options::EXPORT)
.action(ArgAction::SetTrue)
.help("display in an export-able output format"),
)
.arg(
Arg::new(options::GLOBAL)
.short('g')
.long(options::GLOBAL)
.action(ArgAction::SetTrue)
.conflicts_with(options::ID)
.help("info about system-wide usage"),
)
.arg(
Arg::new(options::ID)
.short('i')
.long(options::ID)
.value_name(options::ID)
.value_parser(value_parser!(c_uint))
.action(ArgAction::Set)
.conflicts_with(options::GLOBAL)
.requires("ipc-kind")
.help("print details on resource identified by id"),
)
.arg(
Arg::new(options::JSON)
.short('J')
.long(options::JSON)
.action(ArgAction::SetTrue)
.help("use the JSON output format"),
)
.arg(
Arg::new(options::LIST)
.short('l')
.long(options::LIST)
.action(ArgAction::SetTrue)
.help("force list output format"),
)
.arg(
Arg::new(options::NEW_LINE)
.short('n')
.long(options::NEW_LINE)
.action(ArgAction::SetTrue)
.help("display each piece of information on a new line"),
)
.arg(
Arg::new(options::NO_HEADINGS)
.long(options::NO_HEADINGS)
.action(ArgAction::SetTrue)
.help("don't print headings"),
)
.arg(
Arg::new(options::NO_TRUNCATE)
.long(options::NO_TRUNCATE)
.action(ArgAction::SetTrue)
.help("don't truncate output"),
)
.arg(
Arg::new(options::NUMERIC_PERMS)
.short('P')
.long(options::NUMERIC_PERMS)
.action(ArgAction::SetTrue)
.help("print numeric permissions"),
)
.arg(
Arg::new(options::OUTPUT)
.short('o')
.long(options::OUTPUT)
.value_name(options::LIST)
.value_parser(OutputColumns::from_str)
.action(ArgAction::Set)
.help("define the columns to output"),
)
.arg(
Arg::new(options::QUEUES)
.short('q')
.long(options::QUEUES)
.action(ArgAction::SetTrue)
.help("message queues"),
)
.arg(
Arg::new(options::RAW)
.short('r')
.long(options::RAW)
.action(ArgAction::SetTrue)
.help("display in raw mode"),
)
.arg(
Arg::new(options::SEMAPHORES)
.short('s')
.long(options::SEMAPHORES)
.action(ArgAction::SetTrue)
.help("semaphores"),
)
.arg(
Arg::new(options::SHELL)
.short('y')
.long(options::SHELL)
.action(ArgAction::SetTrue)
.help("use column names to be usable as shell variable identifiers"),
)
.arg(
Arg::new(options::SHMEMS)
.short('m')
.long(options::SHMEMS)
.action(ArgAction::SetTrue)
.help("shared memory segments"),
)
.arg(
Arg::new(options::TIME_FORMAT)
.long(options::TIME_FORMAT)
.value_name(options::TYPE)
.value_parser(EnumValueParser::<TimeFormat>::new())
.action(ArgAction::Set)
.help("display dates in short, full or iso format"),
)
.arg(
Arg::new(options::TIME)
.short('t')
.long(options::TIME)
.action(ArgAction::SetTrue)
.requires("ipc-kind")
.help("show attach, detach and change times"),
)
.group(
ArgGroup::new("ipc-kind")
.args([options::SHMEMS, options::QUEUES, options::SEMAPHORES])
.multiple(false),
)
.group(
ArgGroup::new("output-kind")
.args([
options::EXPORT,
options::JSON,
options::LIST,
options::NEW_LINE,
options::RAW,
])
.multiple(false),
)
.after_help(include_str!("../after-help.txt"))
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = uu_app().try_get_matches_from_mut(args)?;
let output_mode = OutputMode::from(&args);
let time_format = args
.get_one::<TimeFormat>(options::TIME_FORMAT)
.copied()
.unwrap_or(if output_mode == OutputMode::Pretty {
TimeFormat::Full
} else {
TimeFormat::Short
});
lsipc(&args, output_mode, time_format).map_err(From::from)
}
// The Linux implementation resides in `crate::column`.
#[cfg(not(target_os = "linux"))]
#[derive(Debug, Clone)]
struct OutputColumns;
#[cfg(not(target_os = "linux"))]
impl FromStr for OutputColumns {
type Err = LsIpcError;
fn from_str(_s: &str) -> Result<Self, Self::Err> {
unimplemented!()
}
}
#[cfg(target_os = "linux")]
fn lsipc(
args: &ArgMatches,
output_mode: OutputMode,
time_format: TimeFormat,
) -> Result<(), LsIpcError> {
let columns = OutputColumns::from(args);
let columns = if columns.append {
let mut default_columns = if output_mode == OutputMode::Pretty
&& !args.get_flag(options::CREATOR)
&& !args.get_flag(options::TIME)
{
column::all_defaults(args)?
} else {
column::filter_defaults(args)?
};
default_columns.extend(columns.list);
default_columns
} else {
columns.list
};
smartcols::initialize();
let mut table = display::new_table(args, output_mode)?;
let no_truncate = args.get_flag(options::NO_TRUNCATE);
for &column in &columns {
let mut flags = column.flags;
if no_truncate {
flags &= !smartcols_sys::SCOLS_FL_TRUNC;
}
table.new_column(column.id, column.width_hint, flags)?;
}
let print_global = args.get_flag(options::GLOBAL);
if print_global {
table.set_name(c"ipclimits")?;
}
type GlobalProc =
fn(&ArgMatches, &[&ColumnInfo], &mut smartcols::Table) -> Result<(), LsIpcError>;
type DescribeProc = fn(
&ArgMatches,
TimeFormat,
&[&ColumnInfo],
&mut smartcols::Table,
Option<c_uint>,
) -> Result<(), LsIpcError>;
let queues = args.get_flag(options::QUEUES);
let shmems = args.get_flag(options::SHMEMS);
let semaphores = args.get_flag(options::SEMAPHORES);
let config_list: [(bool, GlobalProc, DescribeProc); 3] = [
(queues, message_queue::print_global, message_queue::describe),
(shmems, shared_memory::print_global, shared_memory::describe),
(semaphores, semaphore::print_global, semaphore::describe),
];
let id = args.get_one::<c_uint>(options::ID).copied();
for (flag, global, describe) in config_list {
if flag || (!queues && !shmems && !semaphores) {
if print_global {
global(args, &columns, &mut table)?;
} else {
describe(args, time_format, &columns, &mut table, id)?;
}
}
}
if output_mode == OutputMode::Pretty {
display::print_pretty_table(&table)?;
} else {
table.print()?;
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn lsipc(
_args: &ArgMatches,
_output_mode: OutputMode,
_time_format: TimeFormat,
) -> Result<(), LsIpcError> {
unimplemented!()
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub(crate) enum TimeFormat {
Short = 0,
Full,
Iso,
}
impl FromStr for TimeFormat {
type Err = LsIpcError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
options::SHORT => Ok(Self::Short),
options::FULL => Ok(Self::Full),
options::ISO => Ok(Self::Iso),
_ => Err(LsIpcError::InvalidTimeFormat(s.into())),
}
}
}
impl ValueEnum for TimeFormat {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Short, Self::Full, Self::Iso]
}
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
let name = match self {
Self::Short => options::SHORT,
Self::Full => options::FULL,
Self::Iso => options::ISO,
};
Some(PossibleValue::new(name))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub(crate) enum OutputMode {
None = 0,
Export,
NewLine,
Raw,
Json,
Pretty,
List,
}
impl From<&'_ ArgMatches> for OutputMode {
fn from(args: &'_ ArgMatches) -> Self {
if args.get_flag(options::EXPORT) {
Self::Export
} else if args.get_flag(options::JSON) {
Self::Json
} else if args.get_flag(options::LIST) {
Self::List
} else if args.get_flag(options::NEW_LINE) {
Self::NewLine
} else if args.get_flag(options::RAW) {
Self::Raw
} else if args.contains_id(options::ID) {
Self::Pretty
} else {
Self::None
}
}
}

1
src/uu/lsipc/src/main.rs Normal file
View File

@@ -0,0 +1 @@
uucore::bin!(uu_lsipc);

View File

@@ -0,0 +1,390 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::borrow::Cow;
use std::ffi::{c_int, c_uint};
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use std::str::FromStr;
use crate::column::ColumnInfo;
use crate::display::{
describe_integer, describe_key, describe_owner, describe_permissions, describe_size,
format_time, new_global_line,
};
use crate::errors::LsIpcError;
use crate::smartcols::{Table, TableOperations};
use crate::utils::{GroupDbRecordRef, UserDbRecordRef, read_value, time_of_day};
static _PATH_PROC_SYSV_MSG: &str = "/proc/sysvipc/msg";
static _PATH_PROC_IPC_MSGMNI: &str = "/proc/sys/kernel/msgmni";
static _PATH_PROC_IPC_MSGMNB: &str = "/proc/sys/kernel/msgmnb";
static _PATH_PROC_IPC_MSGMAX: &str = "/proc/sys/kernel/msgmax";
fn msgctl(id: c_int, cmd: c_int) -> Result<(c_int, libc::msqid_ds), LsIpcError> {
let mut stat: libc::msqid_ds;
let r = unsafe {
stat = std::mem::zeroed();
libc::msgctl(id, cmd, &mut stat)
};
if r == -1 {
Err(LsIpcError::last_io0("msgctl"))
} else {
Ok((r, stat))
}
}
struct Limits {
msg_mni: u64,
msg_mnb: u64,
msg_max: u64,
}
impl Limits {
fn new() -> Result<Self, LsIpcError> {
Self::from_proc().or_else(|_| Self::from_syscall())
}
fn from_proc() -> Result<Self, LsIpcError> {
Ok(Self {
msg_mni: read_value::<u64>(_PATH_PROC_IPC_MSGMNI)?,
msg_mnb: read_value::<u64>(_PATH_PROC_IPC_MSGMNB)?,
msg_max: read_value::<u64>(_PATH_PROC_IPC_MSGMAX)?,
})
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (_, stat) = msgctl(0, libc::IPC_INFO)?;
let msg_info = unsafe { &*(&stat as *const libc::msqid_ds).cast::<libc::msginfo>() };
let err_map = move |_| LsIpcError::io0("invalid data", io::ErrorKind::InvalidData);
Ok(Self {
msg_mni: u64::try_from(msg_info.msgmni).map_err(err_map)?,
msg_mnb: u64::try_from(msg_info.msgmnb).map_err(err_map)?,
msg_max: u64::try_from(msg_info.msgmax).map_err(err_map)?,
})
}
}
struct SysVIpcEntry {
key: libc::key_t,
msqid: c_int,
perms: c_uint,
cbytes: u64,
qnum: libc::msgqnum_t,
lspid: libc::pid_t,
lrpid: libc::pid_t,
uid: libc::uid_t,
gid: libc::gid_t,
cuid: libc::uid_t,
cgid: libc::gid_t,
stime: libc::time_t,
rtime: libc::time_t,
ctime: libc::time_t,
}
impl SysVIpcEntry {
fn from_msqid_ds(msqid: c_int, stat: &libc::msqid_ds) -> Self {
Self {
key: stat.msg_perm.__key,
msqid,
perms: c_uint::from(stat.msg_perm.mode),
cbytes: stat.__msg_cbytes,
qnum: stat.msg_qnum,
lspid: stat.msg_lspid,
lrpid: stat.msg_lrpid,
uid: stat.msg_perm.uid,
gid: stat.msg_perm.gid,
cuid: stat.msg_perm.cuid,
cgid: stat.msg_perm.cgid,
stime: stat.msg_stime,
rtime: stat.msg_rtime,
ctime: stat.msg_ctime,
}
}
}
impl FromStr for SysVIpcEntry {
type Err = LsIpcError;
fn from_str(line: &str) -> Result<Self, Self::Err> {
let err_map = || {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_MSG, err)
};
let mut iter = line.split_ascii_whitespace();
let key = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let msqid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let perms = iter
.next()
.and_then(|s| c_uint::from_str_radix(s, 8).ok())
.ok_or_else(err_map)?;
let cbytes = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let qnum = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let lspid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let lrpid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let uid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let gid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cuid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cgid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let stime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let rtime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let ctime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
Ok(Self {
key,
msqid,
perms,
cbytes,
qnum,
lspid,
lrpid,
uid,
gid,
cuid,
cgid,
stime,
rtime,
ctime,
})
}
}
struct SysVIpc(Vec<SysVIpcEntry>);
impl SysVIpc {
fn new(id: Option<c_uint>) -> Result<Self, LsIpcError> {
if let Ok(file) = File::open(_PATH_PROC_SYSV_MSG).map(BufReader::new) {
if let Some(id) = id {
Self::from_proc_by_id(id, file)
} else {
Self::from_proc(file)
}
} else if let Some(id) = id {
Self::from_syscall_by_id(id)
} else {
Self::from_syscall()
}
}
fn from_proc(mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_MSG, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut list = Vec::default();
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break;
}
let entry = line.parse()?;
list.push(entry);
}
Ok(Self(list))
}
fn from_proc_by_id(id: c_uint, mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_MSG, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break Ok(Self(Vec::default()));
}
let msqid: c_uint = line
.split_ascii_whitespace()
.nth(1)
.and_then(|s| s.parse().ok())
.ok_or_else(|| {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_MSG, err)
})?;
if msqid == id {
let entry = line.parse()?;
break Ok(Self(vec![entry]));
}
}
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (max_id, _) = msgctl(0, libc::MSG_INFO)?;
let list = (0..max_id)
.map(|id| msgctl(id, libc::MSG_STAT))
.filter_map(Result::ok)
.map(|(msqid, stat)| SysVIpcEntry::from_msqid_ds(msqid, &stat))
.collect();
Ok(Self(list))
}
fn from_syscall_by_id(id: c_uint) -> Result<Self, LsIpcError> {
let (max_id, _) = msgctl(0, libc::MSG_INFO)?;
let list = (0..max_id)
.map(|current_id| msgctl(current_id, libc::MSG_STAT))
.filter_map(Result::ok)
.filter(|(msqid, _stat)| c_uint::try_from(*msqid).is_ok_and(|msqid| msqid == id))
.map(|(msqid, stat)| SysVIpcEntry::from_msqid_ds(msqid, &stat))
.next()
.map_or_else(Vec::default, |entry| vec![entry]);
Ok(Self(list))
}
}
pub(crate) fn print_global(
args: &clap::ArgMatches,
columns: &[&ColumnInfo],
table: &mut Table,
) -> Result<(), LsIpcError> {
let limits = Limits::new()?;
let count = SysVIpc::new(None)?.0.len();
let in_bytes = args.get_flag(crate::options::BYTES);
let lines_config = [
(
c"MSGMNI",
c"Number of message queues",
Some(u64::try_from(count).unwrap_or(u64::MAX)),
limits.msg_mni,
true,
),
(
c"MSGMAX",
c"Max size of message (bytes)",
None,
limits.msg_max,
in_bytes,
),
(
c"MSGMNB",
c"Default max size of queue (bytes)",
None,
limits.msg_mnb,
in_bytes,
),
];
lines_config
.into_iter()
.try_for_each(move |(resource, description, used, limit, in_bytes)| {
new_global_line(columns, table, resource, description, used, limit, in_bytes)
})
}
pub(crate) fn describe(
args: &clap::ArgMatches,
time_format: crate::TimeFormat,
columns: &[&ColumnInfo],
table: &mut Table,
id: Option<c_uint>,
) -> Result<(), LsIpcError> {
let now = time_of_day()?;
let sys_v_ipc = SysVIpc::new(id)?;
if let Some(id) = id {
if sys_v_ipc.0.len() != 1 {
eprintln!("id {id} not found");
return Ok(());
}
}
table.set_name(c"messages")?;
let mut users = UserDbRecordRef::default();
let mut groups = GroupDbRecordRef::default();
for entry in sys_v_ipc.0 {
let mut line = table.new_line(None)?;
for (cell_index, &column) in columns.iter().enumerate() {
let data_str = match column.id.to_bytes() {
b"KEY" => describe_key(entry.key),
b"ID" => describe_integer(entry.msqid),
b"CUID" => describe_integer(entry.cuid),
b"CGID" => describe_integer(entry.cgid),
b"UID" => describe_integer(entry.uid),
b"GID" => describe_integer(entry.gid),
b"LSPID" => describe_integer(entry.lspid),
b"LRPID" => describe_integer(entry.lrpid),
b"MSGS" => describe_integer(entry.qnum),
b"CTIME" => format_time(time_format, &now, entry.ctime)?.map(Cow::Owned),
b"SEND" => format_time(time_format, &now, entry.stime)?.map(Cow::Owned),
b"RECV" => format_time(time_format, &now, entry.rtime)?.map(Cow::Owned),
b"USEDBYTES" => describe_size(entry.cbytes, args.get_flag(crate::options::BYTES)),
b"OWNER" => describe_owner(&mut users, entry.uid),
b"CUSER" => users.for_id(entry.cuid).name().map(Cow::Borrowed),
b"USER" => users.for_id(entry.uid).name().map(Cow::Borrowed),
b"CGROUP" => groups.for_id(entry.cgid).name().map(Cow::Borrowed),
b"GROUP" => groups.for_id(entry.gid).name().map(Cow::Borrowed),
b"PERMS" => describe_permissions(args, entry.perms & 0o777),
_ => continue,
};
if let Some(data_str) = data_str {
line.set_data(cell_index, &data_str)?;
}
}
}
Ok(())
}

View File

@@ -0,0 +1,493 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::borrow::Cow;
use std::ffi::{CStr, CString, c_int, c_uint};
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use std::str::FromStr;
use crate::column::ColumnInfo;
use crate::display::{
describe_integer, describe_key, describe_owner, describe_permissions, format_time,
new_global_line,
};
use crate::errors::LsIpcError;
use crate::smartcols::{LineRef, Table, TableOperations};
use crate::utils::{GroupDbRecordRef, UserDbRecordRef, pid_command_line, time_of_day};
const SEMVMX: u64 = 0x7fff;
static _PATH_PROC_SYSV_SEM: &str = "/proc/sysvipc/sem";
static _PATH_PROC_IPC_SEM: &str = "/proc/sys/kernel/sem";
fn semctl(id: c_int, index: c_int, cmd: c_int) -> Result<(c_int, libc::semid_ds), LsIpcError> {
let mut stat: libc::semid_ds;
let r = unsafe {
stat = std::mem::zeroed();
libc::semctl(id, index, cmd, &mut stat)
};
if r == -1 {
Err(LsIpcError::last_io0("semctl"))
} else {
Ok((r, stat))
}
}
struct Limits {
sem_vmx: u64,
sem_mni: u64,
sem_msl: u64,
sem_mns: u64,
sem_opm: u64,
}
impl Limits {
fn new() -> Result<Self, LsIpcError> {
if let Ok(file) = File::open(_PATH_PROC_IPC_SEM).map(BufReader::new) {
Self::from_proc(file)
} else {
Self::from_syscall()
}
}
fn from_proc(mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let mut line = String::default();
let count = file
.read_line(&mut line)
.map_err(|err| LsIpcError::io1("reading file", _PATH_PROC_IPC_SEM, err))?;
if count == 0 {
let err = io::ErrorKind::UnexpectedEof;
Err(LsIpcError::io1("reading file", _PATH_PROC_IPC_SEM, err))
} else {
line.parse()
}
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (_, stat) = semctl(0, 0, libc::IPC_INFO)?;
let sem_info = unsafe { &*(&stat as *const libc::semid_ds).cast::<libc::seminfo>() };
let err_map = move |_| LsIpcError::io0("invalid data", io::ErrorKind::InvalidData);
Ok(Self {
sem_vmx: u64::try_from(sem_info.semvmx).map_err(err_map)?,
sem_mni: u64::try_from(sem_info.semmni).map_err(err_map)?,
sem_msl: u64::try_from(sem_info.semmsl).map_err(err_map)?,
sem_mns: u64::try_from(sem_info.semmns).map_err(err_map)?,
sem_opm: u64::try_from(sem_info.semopm).map_err(err_map)?,
})
}
}
impl FromStr for Limits {
type Err = LsIpcError;
fn from_str(line: &str) -> Result<Self, Self::Err> {
let err_map = || {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_IPC_SEM, err)
};
let mut iter = line.split_ascii_whitespace();
let sem_msl = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let sem_mns = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let sem_opm = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let sem_mni = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
Ok(Self {
sem_vmx: SEMVMX,
sem_mni,
sem_msl,
sem_mns,
sem_opm,
})
}
}
struct SysVIpcEntryElement {
sem_val: c_int,
ncount: u64,
zcount: u64,
pid: libc::pid_t,
}
impl SysVIpcEntryElement {
fn new(semid: c_int, index: usize) -> Result<Self, LsIpcError> {
let (sem_val, _) = semctl(semid, index as c_int, libc::GETVAL)?;
let (ncount, _) = semctl(semid, index as c_int, libc::GETNCNT)?;
let (zcount, _) = semctl(semid, index as c_int, libc::GETZCNT)?;
let (pid, _) = semctl(semid, index as c_int, libc::GETPID)?;
let err_map = move |_| LsIpcError::io0("invalid data", io::ErrorKind::InvalidData);
Ok(Self {
sem_val,
ncount: u64::try_from(ncount).map_err(err_map)?,
zcount: u64::try_from(zcount).map_err(err_map)?,
pid,
})
}
}
fn semaphore_elements(semid: c_int, nsems: u64) -> Result<Vec<SysVIpcEntryElement>, LsIpcError> {
(0..usize::try_from(nsems).unwrap_or(usize::MAX))
.map(move |index| SysVIpcEntryElement::new(semid, index))
.collect()
}
struct SysVIpcEntry {
key: libc::key_t,
semid: c_int,
perms: c_uint,
uid: libc::uid_t,
gid: libc::gid_t,
cuid: libc::uid_t,
cgid: libc::gid_t,
otime: libc::time_t,
ctime: libc::time_t,
elements: Vec<SysVIpcEntryElement>,
}
impl SysVIpcEntry {
fn from_semid_ds(
semid: c_int,
stat: &libc::semid_ds,
elements: Vec<SysVIpcEntryElement>,
) -> Self {
Self {
key: stat.sem_perm.__key,
semid,
perms: c_uint::from(stat.sem_perm.mode),
uid: stat.sem_perm.uid,
gid: stat.sem_perm.gid,
cuid: stat.sem_perm.cuid,
cgid: stat.sem_perm.cgid,
otime: stat.sem_otime,
ctime: stat.sem_ctime,
elements,
}
}
}
impl FromStr for SysVIpcEntry {
type Err = LsIpcError;
fn from_str(line: &str) -> Result<Self, Self::Err> {
let err_map = || {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_SEM, err)
};
let mut iter = line.split_ascii_whitespace();
let key = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let semid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let perms = iter
.next()
.and_then(|s| c_uint::from_str_radix(s, 8).ok())
.ok_or_else(err_map)?;
let nsems = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let uid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let gid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cuid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cgid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let otime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let ctime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let elements = semaphore_elements(semid, nsems)?;
Ok(Self {
key,
semid,
perms,
uid,
gid,
cuid,
cgid,
otime,
ctime,
elements,
})
}
}
struct SysVIpc(Vec<SysVIpcEntry>);
impl SysVIpc {
fn new(id: Option<c_uint>) -> Result<Self, LsIpcError> {
if let Ok(file) = File::open(_PATH_PROC_SYSV_SEM).map(BufReader::new) {
if let Some(id) = id {
Self::from_proc_by_id(id, file)
} else {
Self::from_proc(file)
}
} else if let Some(id) = id {
Self::from_syscall_by_id(id)
} else {
Self::from_syscall()
}
}
fn from_proc(mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_SEM, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut list = Vec::default();
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break;
}
let entry = line.parse()?;
list.push(entry);
}
Ok(Self(list))
}
fn from_proc_by_id(id: c_uint, mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_SEM, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break Ok(Self(Vec::default()));
}
let semid: c_uint = line
.split_ascii_whitespace()
.nth(1)
.and_then(|s| s.parse().ok())
.ok_or_else(|| {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_SEM, err)
})?;
if semid == id {
let entry = line.parse()?;
break Ok(Self(vec![entry]));
}
}
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (max_id, _) = semctl(0, 0, libc::SEM_INFO)?;
let list = (0..max_id)
.filter_map(|id| semctl(id, 0, libc::SEM_STAT).ok())
.filter_map(|(semid, stat)| {
let elements = semaphore_elements(semid, stat.sem_nsems).ok()?;
Some(SysVIpcEntry::from_semid_ds(semid, &stat, elements))
})
.collect();
Ok(Self(list))
}
fn from_syscall_by_id(id: c_uint) -> Result<Self, LsIpcError> {
let (max_id, _) = semctl(0, 0, libc::SEM_INFO)?;
let list = (0..max_id)
.map(|current_id| semctl(current_id, 0, libc::SEM_STAT))
.filter_map(Result::ok)
.filter(|(semid, _stat)| c_uint::try_from(*semid).is_ok_and(|semid| semid == id))
.filter_map(|(semid, stat)| {
let elements = semaphore_elements(semid, stat.sem_nsems).ok()?;
Some(SysVIpcEntry::from_semid_ds(semid, &stat, elements))
})
.next()
.map_or_else(Vec::default, |entry| vec![entry]);
Ok(Self(list))
}
}
pub(crate) fn print_global(
_args: &clap::ArgMatches,
columns: &[&ColumnInfo],
table: &mut Table,
) -> Result<(), LsIpcError> {
let limits = Limits::new()?;
let sys_v_ipc = SysVIpc::new(None)?;
let total_nsems: usize = sys_v_ipc.0.iter().map(|sem| sem.elements.len()).sum();
let lines_config = [
(
c"SEMMNI",
c"Number of semaphore identifiers",
Some(u64::try_from(sys_v_ipc.0.len()).unwrap_or(u64::MAX)),
limits.sem_mni,
),
(
c"SEMMNS",
c"Total number of semaphores",
Some(u64::try_from(total_nsems).unwrap_or(u64::MAX)),
limits.sem_mns,
),
(
c"SEMMSL",
c"Max semaphores per semaphore set",
None,
limits.sem_msl,
),
(
c"SEMOPM",
c"Max number of operations per semop(2)",
None,
limits.sem_opm,
),
(c"SEMVMX", c"Semaphore max value", None, limits.sem_vmx),
];
lines_config
.into_iter()
.try_for_each(move |(resource, description, used, limit)| {
new_global_line(columns, table, resource, description, used, limit, true)
})
}
pub(crate) fn describe(
args: &clap::ArgMatches,
time_format: crate::TimeFormat,
columns: &[&ColumnInfo],
table: &mut Table,
id: Option<c_uint>,
) -> Result<(), LsIpcError> {
let now = time_of_day()?;
let sys_v_ipc = SysVIpc::new(id)?;
if let Some(id) = id {
if sys_v_ipc.0.len() != 1 {
eprintln!("id {id} not found");
return Ok(());
}
}
table.set_name(c"semaphores")?;
let mut users = UserDbRecordRef::default();
let mut groups = GroupDbRecordRef::default();
for entry in sys_v_ipc.0 {
let mut line = table.new_line(None)?;
for (cell_index, &column) in columns.iter().enumerate() {
let data_str = match column.id.to_bytes() {
b"KEY" => describe_key(entry.key),
b"ID" => describe_integer(entry.semid),
b"CUID" => describe_integer(entry.cuid),
b"CGID" => describe_integer(entry.cgid),
b"UID" => describe_integer(entry.uid),
b"GID" => describe_integer(entry.gid),
b"NSEMS" => describe_integer(entry.elements.len()),
b"CTIME" => format_time(time_format, &now, entry.ctime)?.map(Cow::Owned),
b"OTIME" => format_time(time_format, &now, entry.otime)?.map(Cow::Owned),
b"OWNER" => describe_owner(&mut users, entry.uid),
b"CUSER" => users.for_id(entry.cuid).name().map(Cow::Borrowed),
b"USER" => users.for_id(entry.uid).name().map(Cow::Borrowed),
b"CGROUP" => groups.for_id(entry.cgid).name().map(Cow::Borrowed),
b"GROUP" => groups.for_id(entry.gid).name().map(Cow::Borrowed),
b"PERMS" => describe_permissions(args, entry.perms & 0o777),
_ => continue,
};
if let Some(data_str) = data_str {
line.set_data(cell_index, &data_str)?;
}
}
if id.is_some() && !entry.elements.is_empty() {
describe_elements(&mut line, &entry.elements)?;
}
}
Ok(())
}
fn describe_elements(
line: &mut LineRef,
elements: &[SysVIpcEntryElement],
) -> Result<(), LsIpcError> {
static ELEMENT_COLUMNS: [&CStr; 6] = [
c"SEMNUM", c"VALUE", c"NCOUNT", c"ZCOUNT", c"PID", c"COMMAND",
];
let mut sub_table = Table::new()?;
sub_table.set_name(c"elements")?;
sub_table.enable_headings(true)?;
ELEMENT_COLUMNS.into_iter().try_for_each(|name| {
sub_table.new_column(name, 0.0, smartcols_sys::SCOLS_FL_RIGHT)?;
Ok(())
})?;
for (index, element) in elements.iter().enumerate() {
let mut sub_line = sub_table.new_line(None)?;
sub_line.set_data(0, &CString::new(index.to_string()).unwrap())?;
sub_line.set_data(1, &CString::new(element.sem_val.to_string()).unwrap())?;
sub_line.set_data(2, &CString::new(element.ncount.to_string()).unwrap())?;
sub_line.set_data(3, &CString::new(element.zcount.to_string()).unwrap())?;
sub_line.set_data(4, &CString::new(element.pid.to_string()).unwrap())?;
if let Ok(cmd_line) = pid_command_line(element.pid) {
sub_line.set_data(5, &cmd_line)?;
}
}
line.set_user_data(sub_table.into_inner().as_ptr().cast())
}

View File

@@ -0,0 +1,442 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::borrow::Cow;
use std::ffi::{CString, c_int, c_uint, c_ulong};
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};
use std::str::FromStr;
use crate::column::ColumnInfo;
use crate::display::{
describe_integer, describe_key, describe_owner, describe_permissions, describe_size,
format_time, new_global_line,
};
use crate::errors::LsIpcError;
use crate::smartcols::{Table, TableOperations};
use crate::utils::{
GroupDbRecordRef, UserDbRecordRef, get_page_size, pid_command_line, read_value, time_of_day,
};
const SHM_STAT: c_int = 13 | (libc::IPC_STAT & 0x100);
const SHM_INFO: c_int = 14;
const SHM_DEST: c_uint = 0o1000;
const SHM_LOCKED: c_uint = 0o2000;
static _PATH_PROC_SYSV_SHM: &str = "/proc/sysvipc/shm";
static _PATH_PROC_IPC_SHMMAX: &str = "/proc/sys/kernel/shmmax";
static _PATH_PROC_IPC_SHMMNI: &str = "/proc/sys/kernel/shmmni";
static _PATH_PROC_IPC_SHMALL: &str = "/proc/sys/kernel/shmall";
// The C struct is defined in <sys/shm.h>
#[allow(non_camel_case_types)]
#[repr(C)]
struct shminfo {
shmmax: c_ulong,
shmmin: c_ulong,
shmmni: c_ulong,
shmseg: c_ulong,
shmall: c_ulong,
__unused: [c_ulong; 4],
}
fn shmctl(id: c_int, cmd: c_int) -> Result<(c_int, libc::shmid_ds), LsIpcError> {
let mut stat: libc::shmid_ds;
let r = unsafe {
stat = std::mem::zeroed();
libc::shmctl(id, cmd, &mut stat)
};
if r == -1 {
Err(LsIpcError::last_io0("shmctl"))
} else {
Ok((r, stat))
}
}
struct Limits {
shm_max: u64,
shm_min: u64,
shm_mni: u64,
shm_all: u64,
}
impl Limits {
fn new() -> Result<Self, LsIpcError> {
Self::from_proc().or_else(|_| Self::from_syscall())
}
fn from_proc() -> Result<Self, LsIpcError> {
Ok(Self {
shm_max: read_value::<u64>(_PATH_PROC_IPC_SHMMAX)?,
shm_min: 1,
shm_mni: read_value::<u64>(_PATH_PROC_IPC_SHMMNI)?,
shm_all: read_value::<u64>(_PATH_PROC_IPC_SHMALL)?,
})
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (_, stat) = shmctl(0, libc::IPC_INFO)?;
let shm_info = unsafe { &*(&stat as *const libc::shmid_ds).cast::<shminfo>() };
Ok(Self {
shm_max: shm_info.shmmax,
shm_min: shm_info.shmmin,
shm_mni: shm_info.shmmni,
shm_all: shm_info.shmall,
})
}
}
struct SysVIpcEntry {
key: libc::key_t,
shmid: c_int,
perms: c_uint,
segsz: u64,
cpid: libc::pid_t,
lpid: libc::pid_t,
nattch: u64,
uid: libc::uid_t,
gid: libc::gid_t,
cuid: libc::uid_t,
cgid: libc::gid_t,
atime: libc::time_t,
dtime: libc::time_t,
ctime: libc::time_t,
}
impl SysVIpcEntry {
fn from_shmid_ds(shmid: c_int, stat: &libc::shmid_ds) -> Self {
Self {
key: stat.shm_perm.__key,
shmid,
perms: c_uint::from(stat.shm_perm.mode),
segsz: u64::try_from(stat.shm_segsz).unwrap_or(u64::MAX),
cpid: stat.shm_cpid,
lpid: stat.shm_lpid,
nattch: stat.shm_nattch,
uid: stat.shm_perm.uid,
gid: stat.shm_perm.gid,
cuid: stat.shm_perm.cuid,
cgid: stat.shm_perm.cgid,
atime: stat.shm_atime,
dtime: stat.shm_dtime,
ctime: stat.shm_ctime,
}
}
fn status_desc(&self) -> String {
static STATUS_MAP: [(c_uint, &str); 4] = [
(SHM_DEST, "dest"),
(SHM_LOCKED, "locked"),
(libc::SHM_HUGETLB as c_uint, "hugetlb"),
(libc::SHM_NORESERVE as c_uint, "noreserve"),
];
let mut separator = "";
let mut result = String::default();
for (flag, name) in STATUS_MAP {
if (self.perms & flag) != 0 {
result.push_str(separator);
result.push_str(name);
separator = ",";
}
}
result
}
}
impl FromStr for SysVIpcEntry {
type Err = LsIpcError;
fn from_str(line: &str) -> Result<Self, Self::Err> {
let err_map = || {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_SHM, err)
};
let mut iter = line.split_ascii_whitespace();
let key = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let shmid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let perms = iter
.next()
.and_then(|s| c_uint::from_str_radix(s, 8).ok())
.ok_or_else(err_map)?;
let segsz = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cpid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let lpid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let nattch = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let uid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let gid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cuid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let cgid = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let atime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let dtime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
let ctime = iter
.next()
.and_then(|s| s.parse().ok())
.ok_or_else(err_map)?;
Ok(Self {
key,
shmid,
perms,
segsz,
cpid,
lpid,
nattch,
uid,
gid,
cuid,
cgid,
atime,
dtime,
ctime,
})
}
}
struct SysVIpc(Vec<SysVIpcEntry>);
impl SysVIpc {
fn new(id: Option<c_uint>) -> Result<Self, LsIpcError> {
if let Ok(file) = File::open(_PATH_PROC_SYSV_SHM).map(BufReader::new) {
if let Some(id) = id {
Self::from_proc_by_id(id, file)
} else {
Self::from_proc(file)
}
} else if let Some(id) = id {
Self::from_syscall_by_id(id)
} else {
Self::from_syscall()
}
}
fn from_proc(mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_SHM, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut list = Vec::default();
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break;
}
let entry = line.parse()?;
list.push(entry);
}
Ok(Self(list))
}
fn from_proc_by_id(id: c_uint, mut file: BufReader<File>) -> Result<Self, LsIpcError> {
let err_map =
move |err: std::io::Error| LsIpcError::io1("reading file", _PATH_PROC_SYSV_SHM, err);
file.skip_until(b'\n').map_err(err_map)?; // Skip the header line.
let mut line = String::default();
loop {
line.clear();
if file.read_line(&mut line).map_err(err_map)? == 0 {
break Ok(Self(Vec::default()));
}
let shmid: c_uint = line
.split_ascii_whitespace()
.nth(1)
.and_then(|s| s.parse().ok())
.ok_or_else(|| {
let err = io::ErrorKind::InvalidData;
LsIpcError::io1("invalid data", _PATH_PROC_SYSV_SHM, err)
})?;
if shmid == id {
let entry = line.parse()?;
break Ok(Self(vec![entry]));
}
}
}
fn from_syscall() -> Result<Self, LsIpcError> {
let (max_id, _) = shmctl(0, SHM_INFO)?;
let list = (0..max_id)
.map(|id| shmctl(id, SHM_STAT))
.filter_map(Result::ok)
.map(|(shmid, stat)| SysVIpcEntry::from_shmid_ds(shmid, &stat))
.collect();
Ok(Self(list))
}
fn from_syscall_by_id(id: c_uint) -> Result<Self, LsIpcError> {
let (max_id, _) = shmctl(0, SHM_INFO)?;
let list = (0..max_id)
.map(|current_id| shmctl(current_id, SHM_STAT))
.filter_map(Result::ok)
.filter(|(shmid, _stat)| c_uint::try_from(*shmid).is_ok_and(|shmid| shmid == id))
.map(|(shmid, stat)| SysVIpcEntry::from_shmid_ds(shmid, &stat))
.next()
.map_or_else(Vec::default, |entry| vec![entry]);
Ok(Self(list))
}
}
pub(crate) fn print_global(
args: &clap::ArgMatches,
columns: &[&ColumnInfo],
table: &mut Table,
) -> Result<(), LsIpcError> {
let limits = Limits::new()?;
let sys_v_ipc = SysVIpc::new(None)?;
let segsz_pages = sys_v_ipc.0.iter().map(|shm| shm.segsz).sum::<u64>() / get_page_size()?;
let in_bytes = args.get_flag(crate::options::BYTES);
let lines_config = [
(
c"SHMMNI",
c"Shared memory segments",
Some(u64::try_from(sys_v_ipc.0.len()).unwrap_or(u64::MAX)),
limits.shm_mni,
true,
),
(
c"SHMALL",
c"Shared memory pages",
Some(segsz_pages),
limits.shm_all,
true,
),
(
c"SHMMAX",
c"Max size of shared memory segment (bytes)",
None,
limits.shm_max,
in_bytes,
),
(
c"SHMMIN",
c"Min size of shared memory segment (bytes)",
None,
limits.shm_min,
in_bytes,
),
];
lines_config
.into_iter()
.try_for_each(move |(resource, description, used, limit, in_bytes)| {
new_global_line(columns, table, resource, description, used, limit, in_bytes)
})
}
pub(crate) fn describe(
args: &clap::ArgMatches,
time_format: crate::TimeFormat,
columns: &[&ColumnInfo],
table: &mut Table,
id: Option<c_uint>,
) -> Result<(), LsIpcError> {
let now = time_of_day()?;
let sys_v_ipc = SysVIpc::new(id)?;
if let Some(id) = id {
if sys_v_ipc.0.len() != 1 {
eprintln!("id {id} not found");
return Ok(());
}
}
table.set_name(c"sharedmemory")?;
let mut users = UserDbRecordRef::default();
let mut groups = GroupDbRecordRef::default();
for entry in sys_v_ipc.0 {
let mut line = table.new_line(None)?;
for (cell_index, &column) in columns.iter().enumerate() {
let data_str = match column.id.to_bytes() {
b"KEY" => describe_key(entry.key),
b"ID" => describe_integer(entry.shmid),
b"CUID" => describe_integer(entry.cuid),
b"CGID" => describe_integer(entry.cgid),
b"UID" => describe_integer(entry.uid),
b"GID" => describe_integer(entry.gid),
b"NATTCH" => describe_integer(entry.nattch),
b"CPID" => describe_integer(entry.cpid),
b"LPID" => describe_integer(entry.lpid),
b"CTIME" => format_time(time_format, &now, entry.ctime)?.map(Cow::Owned),
b"ATTACH" => format_time(time_format, &now, entry.atime)?.map(Cow::Owned),
b"DETACH" => format_time(time_format, &now, entry.dtime)?.map(Cow::Owned),
b"COMMAND" => pid_command_line(entry.cpid).map(Cow::Owned).map(Some)?,
b"STATUS" => Some(Cow::Owned(CString::new(entry.status_desc()).unwrap())),
b"SIZE" => describe_size(entry.segsz, args.get_flag(crate::options::BYTES)),
b"OWNER" => describe_owner(&mut users, entry.uid),
b"CUSER" => users.for_id(entry.cuid).name().map(Cow::Borrowed),
b"USER" => users.for_id(entry.uid).name().map(Cow::Borrowed),
b"CGROUP" => groups.for_id(entry.cgid).name().map(Cow::Borrowed),
b"GROUP" => groups.for_id(entry.gid).name().map(Cow::Borrowed),
b"PERMS" => describe_permissions(args, entry.perms & 0o777),
_ => continue,
};
if let Some(data_str) = data_str {
line.set_data(cell_index, &data_str)?;
}
}
}
Ok(())
}

View File

@@ -0,0 +1,225 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{CStr, c_int, c_uint, c_void};
use std::ptr::NonNull;
use std::{io, mem, ptr};
use smartcols_sys::{
SCOLS_ITER_BACKWARD, SCOLS_ITER_FORWARD, libscols_cell, libscols_column, libscols_iter,
libscols_line, libscols_table, scols_cell_get_data, scols_free_iter, scols_init_debug,
scols_line_get_cell, scols_line_get_userdata, scols_line_set_data, scols_line_set_userdata,
scols_new_iter, scols_new_table, scols_print_table, scols_table_enable_export,
scols_table_enable_json, scols_table_enable_noheadings, scols_table_enable_raw,
scols_table_enable_shellvar, scols_table_get_line, scols_table_new_column,
scols_table_new_line, scols_table_next_column, scols_table_set_column_separator,
scols_table_set_name, scols_unref_table,
};
use crate::errors::LsIpcError;
pub(crate) fn initialize() {
unsafe { scols_init_debug(0) };
}
#[repr(transparent)]
pub(crate) struct TableRef(NonNull<libscols_table>);
impl From<NonNull<libscols_table>> for TableRef {
fn from(value: NonNull<libscols_table>) -> Self {
Self(value)
}
}
impl TableOperations for TableRef {
fn as_ptr(&self) -> *mut libscols_table {
self.0.as_ptr()
}
}
#[repr(transparent)]
pub(crate) struct Table(NonNull<libscols_table>);
impl Table {
pub(crate) fn new() -> Result<Self, LsIpcError> {
NonNull::new(unsafe { scols_new_table() })
.ok_or_else(|| LsIpcError::io0("scols_new_table", io::ErrorKind::OutOfMemory))
.map(Self)
}
pub(crate) fn into_inner(self) -> NonNull<libscols_table> {
let ptr = self.0;
mem::forget(self);
ptr
}
}
impl TableOperations for Table {
fn as_ptr(&self) -> *mut libscols_table {
self.0.as_ptr()
}
}
impl Drop for Table {
fn drop(&mut self) {
unsafe { scols_unref_table(self.0.as_ptr()) }
}
}
pub(crate) trait TableOperations: Sized {
fn as_ptr(&self) -> *mut libscols_table;
fn enable_headings(&mut self, enable: bool) -> Result<(), LsIpcError> {
let no_headings = c_int::from(!enable);
let r = unsafe { scols_table_enable_noheadings(self.as_ptr(), no_headings) };
LsIpcError::io_from_neg_errno("scols_table_enable_noheadings", r).map(|_| ())
}
fn enable_shell_variable(&mut self, enable: bool) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_enable_shellvar(self.as_ptr(), c_int::from(enable)) };
LsIpcError::io_from_neg_errno("scols_table_enable_shellvar", r).map(|_| ())
}
fn enable_export(&mut self, enable: bool) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_enable_export(self.as_ptr(), c_int::from(enable)) };
LsIpcError::io_from_neg_errno("scols_table_enable_export", r).map(|_| ())
}
fn enable_raw(&mut self, enable: bool) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_enable_raw(self.as_ptr(), c_int::from(enable)) };
LsIpcError::io_from_neg_errno("scols_table_enable_raw", r).map(|_| ())
}
fn enable_json(&mut self, enable: bool) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_enable_json(self.as_ptr(), c_int::from(enable)) };
LsIpcError::io_from_neg_errno("scols_table_enable_json", r).map(|_| ())
}
fn set_column_separator(&mut self, separator: &CStr) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_set_column_separator(self.as_ptr(), separator.as_ptr()) };
LsIpcError::io_from_neg_errno("scols_table_set_column_separator", r).map(|_| ())
}
fn new_column(
&mut self,
name: &CStr,
width_hint: f64,
flags: c_uint,
) -> Result<ColumnRef, LsIpcError> {
NonNull::new(unsafe {
scols_table_new_column(self.as_ptr(), name.as_ptr(), width_hint, flags as c_int)
})
.ok_or_else(|| LsIpcError::io0("scols_table_new_column", io::ErrorKind::OutOfMemory))
.map(ColumnRef)
}
fn new_line(&mut self, parent: Option<&mut LineRef>) -> Result<LineRef, LsIpcError> {
let parent = parent.map_or(ptr::null_mut(), |parent| parent.0.as_ptr());
NonNull::new(unsafe { scols_table_new_line(self.as_ptr(), parent) })
.ok_or_else(|| LsIpcError::io0("scols_table_new_line", io::ErrorKind::OutOfMemory))
.map(LineRef)
}
fn set_name(&mut self, name: &CStr) -> Result<(), LsIpcError> {
let r = unsafe { scols_table_set_name(self.as_ptr(), name.as_ptr()) };
LsIpcError::io_from_neg_errno("scols_table_set_name", r).map(|_| ())
}
fn line(&self, column_index: usize) -> Result<LineRef, LsIpcError> {
NonNull::new(unsafe { scols_table_get_line(self.as_ptr(), column_index) })
.ok_or_else(|| LsIpcError::io0("scols_table_get_line", io::ErrorKind::InvalidInput))
.map(LineRef)
}
fn column_iter(&self, direction: IterDirection) -> Result<ColumnIter<'_, Self>, LsIpcError> {
let iter = NonNull::new(unsafe { scols_new_iter(direction as c_int) })
.ok_or_else(|| LsIpcError::io0("scols_new_iter", io::ErrorKind::OutOfMemory))?;
Ok(ColumnIter { table: self, iter })
}
fn print(&self) -> Result<(), LsIpcError> {
let r = unsafe { scols_print_table(self.as_ptr()) };
LsIpcError::io_from_neg_errno("scols_print_table", r).map(|_| ())
}
}
#[repr(transparent)]
pub(crate) struct LineRef(NonNull<libscols_line>);
impl LineRef {
pub(crate) fn user_data(&self) -> *mut c_void {
unsafe { scols_line_get_userdata(self.0.as_ptr()) }
}
pub(crate) fn set_user_data(&mut self, user_data: *mut c_void) -> Result<(), LsIpcError> {
let r = unsafe { scols_line_set_userdata(self.0.as_ptr(), user_data) };
LsIpcError::io_from_neg_errno("scols_line_set_userdata", r).map(|_| ())
}
pub(crate) fn set_data(&mut self, cell_index: usize, data: &CStr) -> Result<(), LsIpcError> {
let r = unsafe { scols_line_set_data(self.0.as_ptr(), cell_index, data.as_ptr()) };
LsIpcError::io_from_neg_errno("scols_line_set_data", r).map(|_| ())
}
pub(crate) fn cell(&self, cell_index: usize) -> Result<CellRef, LsIpcError> {
NonNull::new(unsafe { scols_line_get_cell(self.0.as_ptr(), cell_index) })
.ok_or_else(|| LsIpcError::io0("scols_line_get_cell", io::ErrorKind::InvalidInput))
.map(CellRef)
}
}
#[repr(transparent)]
pub(crate) struct ColumnRef(NonNull<libscols_column>);
#[repr(transparent)]
pub(crate) struct CellRef(NonNull<libscols_cell>);
impl CellRef {
pub(crate) fn data_as_c_str(&self) -> Option<&CStr> {
unsafe {
let data_ptr = scols_cell_get_data(self.0.as_ptr());
(!data_ptr.is_null()).then(|| CStr::from_ptr(data_ptr))
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
#[repr(i32)]
pub(crate) enum IterDirection {
Forward = SCOLS_ITER_FORWARD as i32,
Backward = SCOLS_ITER_BACKWARD as i32,
}
pub(crate) struct ColumnIter<'table, T: TableOperations> {
table: &'table T,
iter: NonNull<libscols_iter>,
}
impl<T: TableOperations> Iterator for ColumnIter<'_, T> {
type Item = Result<ColumnRef, LsIpcError>;
fn next(&mut self) -> Option<Self::Item> {
let mut column = ptr::null_mut();
let table_ptr = self.table.as_ptr();
let r = unsafe { scols_table_next_column(table_ptr, self.iter.as_ptr(), &mut column) };
match LsIpcError::io_from_neg_errno("scols_table_next_column", r) {
Err(err) => Some(Err(err)),
Ok(r) => NonNull::new(column)
.filter(|_| r == 0)
.map(ColumnRef)
.map(Ok),
}
}
}
impl<T: TableOperations> Drop for ColumnIter<'_, T> {
fn drop(&mut self) {
unsafe { scols_free_iter(self.iter.as_ptr()) }
}
}

165
src/uu/lsipc/src/utils.rs Normal file
View File

@@ -0,0 +1,165 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::str::FromStr;
use std::sync::atomic::AtomicU64;
use std::{io, ptr};
use crate::errors::LsIpcError;
pub(crate) fn get_page_size() -> Result<u64, LsIpcError> {
use std::sync::atomic::Ordering;
static VALUE: AtomicU64 = AtomicU64::new(0);
let mut page_size = VALUE.load(Ordering::Acquire);
if page_size == 0 {
let value = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) };
if value == -1 || value == 0 {
return Err(LsIpcError::last_io0("sysconf(_SC_PAGE_SIZE)"));
}
let value = value as u64;
match VALUE.compare_exchange(0, value, Ordering::AcqRel, Ordering::Acquire) {
Ok(_previous_value) => page_size = value,
Err(previous_value) => page_size = previous_value,
}
}
Ok(page_size)
}
pub(crate) fn local_time(time: libc::time_t) -> Result<libc::tm, LsIpcError> {
let mut detailed_time: libc::tm;
let r = unsafe {
detailed_time = std::mem::zeroed();
libc::localtime_r(&time, &mut detailed_time)
};
if r.is_null() {
Err(LsIpcError::last_io0("localtime_r"))
} else {
Ok(detailed_time)
}
}
pub(crate) fn time_of_day() -> Result<libc::timeval, LsIpcError> {
let mut time: libc::timeval;
let r = unsafe {
time = std::mem::zeroed();
libc::gettimeofday(&mut time, ptr::null_mut())
};
if r == -1 {
Err(LsIpcError::last_io0("gettimeofday"))
} else {
Ok(time)
}
}
pub(crate) fn pid_command_line(pid: libc::pid_t) -> Result<CString, LsIpcError> {
let path = Path::new("/proc").join(pid.to_string()).join("cmdline");
let mut contents =
std::fs::read(&path).map_err(|err| LsIpcError::io1("reading file", path, err))?;
if contents.last().copied() == Some(0_u8) {
contents.pop();
}
find_replace_in_vec(0_u8, b' ', &mut contents);
contents.push(0_u8);
Ok(unsafe { CString::from_vec_with_nul_unchecked(contents) })
}
pub(crate) fn find_replace_in_vec<T>(needle: T, replacement: T, data: &mut [T])
where
T: Copy + Eq,
{
let mut start = 0;
while let Some((index, found)) = data[start..]
.iter_mut()
.enumerate()
.find(|&(_index, &mut element)| element == needle)
{
*found = replacement;
start += index + 1;
if start >= data.len() {
break;
}
}
}
pub(crate) fn read_value<T>(path: impl AsRef<Path>) -> Result<T, LsIpcError>
where
T: FromStr,
{
let path = path.as_ref();
let mut file = File::open(path)
.map(BufReader::new)
.map_err(|err| LsIpcError::io1("opening file", path, err))?;
let mut line = String::default();
file.read_line(&mut line)
.map_err(|err| LsIpcError::io1("reading file", path, err))?;
line.trim()
.parse()
.map_err(|_| LsIpcError::io1("invalid data", path, io::ErrorKind::InvalidData))
}
pub(crate) struct UserDbRecordRef(*const libc::passwd);
impl Default for UserDbRecordRef {
fn default() -> Self {
Self(ptr::null())
}
}
impl UserDbRecordRef {
pub(crate) fn for_id(&mut self, uid: libc::uid_t) -> &Self {
if unsafe { self.0.as_ref() }.is_none_or(|record| record.pw_uid != uid) {
self.0 = unsafe { libc::getpwuid(uid) };
}
self
}
pub(crate) fn name(&self) -> Option<&CStr> {
unsafe { self.0.as_ref() }
.and_then(|record| (!record.pw_name.is_null()).then_some(record.pw_name))
.map(|name| unsafe { CStr::from_ptr(name) })
}
}
pub(crate) struct GroupDbRecordRef(*const libc::group);
impl Default for GroupDbRecordRef {
fn default() -> Self {
Self(ptr::null())
}
}
impl GroupDbRecordRef {
pub(crate) fn for_id(&mut self, gid: libc::gid_t) -> &Self {
if unsafe { self.0.as_ref() }.is_none_or(|record| record.gr_gid != gid) {
self.0 = unsafe { libc::getgrgid(gid) };
}
self
}
pub(crate) fn name(&self) -> Option<&CStr> {
unsafe { self.0.as_ref() }
.and_then(|record| (!record.gr_name.is_null()).then_some(record.gr_name))
.map(|name| unsafe { CStr::from_ptr(name) })
}
}