15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -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
216
Cargo.lock
generated
@@ -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",
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -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
18
src/uu/lsipc/Cargo.toml
Normal 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 }
|
||||
43
src/uu/lsipc/after-help.txt
Normal file
43
src/uu/lsipc/after-help.txt
Normal 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
10
src/uu/lsipc/lsipc.md
Normal 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
301
src/uu/lsipc/src/column.rs
Normal 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
357
src/uu/lsipc/src/display.rs
Normal 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(())
|
||||
}
|
||||
74
src/uu/lsipc/src/errors.rs
Normal file
74
src/uu/lsipc/src/errors.rs
Normal 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
425
src/uu/lsipc/src/lsipc.rs
Normal 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
1
src/uu/lsipc/src/main.rs
Normal file
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_lsipc);
|
||||
390
src/uu/lsipc/src/message_queue.rs
Normal file
390
src/uu/lsipc/src/message_queue.rs
Normal 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(())
|
||||
}
|
||||
493
src/uu/lsipc/src/semaphore.rs
Normal file
493
src/uu/lsipc/src/semaphore.rs
Normal 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())
|
||||
}
|
||||
442
src/uu/lsipc/src/shared_memory.rs
Normal file
442
src/uu/lsipc/src/shared_memory.rs
Normal 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(())
|
||||
}
|
||||
225
src/uu/lsipc/src/smartcols.rs
Normal file
225
src/uu/lsipc/src/smartcols.rs
Normal 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
165
src/uu/lsipc/src/utils.rs
Normal 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) })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user