Implemented lslocks.
This commit is contained in:
124
Cargo.lock
generated
124
Cargo.lock
generated
@@ -125,9 +125,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.17"
|
||||
version = "1.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -270,9 +270,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
@@ -381,9 +381,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -391,7 +391,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.52.0",
|
||||
"windows-core 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -405,9 +405,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@@ -457,7 +457,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libmount-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef1e0e42bbd34fa3f8fe6c9ffeb27906263d59d680825e4e71a5dacd7613e90a"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -572,9 +583,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@@ -679,9 +690,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.31"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
||||
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
@@ -689,9 +700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -848,9 +859,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.3"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@@ -945,9 +956,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
||||
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -998,7 +1009,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix 1.0.3",
|
||||
"rustix 1.0.5",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -1008,7 +1019,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
||||
dependencies = [
|
||||
"rustix 1.0.3",
|
||||
"rustix 1.0.5",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -1240,8 +1251,9 @@ name = "uu_lslocks"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"libc",
|
||||
"libmount-sys",
|
||||
"smartcols-sys",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
@@ -1460,7 +1472,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1481,23 +1493,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement 0.57.0",
|
||||
"windows-interface 0.57.0",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1511,6 +1527,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
@@ -1522,6 +1549,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
@@ -1537,6 +1575,24 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@@ -1701,7 +1757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix 1.0.3",
|
||||
"rustix 1.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -51,6 +51,7 @@ clap_mangen = "0.2"
|
||||
dns-lookup = "2.0.4"
|
||||
errno = "0.3"
|
||||
libc = "0.2.171"
|
||||
libmount-sys = "0.1.1"
|
||||
linux-raw-sys = { version = "0.9.0", features = ["ioctl"] }
|
||||
md-5 = "0.10.6"
|
||||
nix = { version = "0.29", default-features = false }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "uu_lslocks"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
path = "src/lslocks.rs"
|
||||
@@ -13,5 +13,6 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
uucore = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
smartcols-sys = { workspace = true }
|
||||
libmount-sys = { workspace = true }
|
||||
|
||||
69
src/uu/lslocks/columns.json
Normal file
69
src/uu/lslocks/columns.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"lslocks-columns": [
|
||||
{
|
||||
"holder": "COMMAND",
|
||||
"type": "<string>",
|
||||
"description": "command of the process holding the lock"
|
||||
},
|
||||
{
|
||||
"holder": "PID",
|
||||
"type": "<integer>",
|
||||
"description": "PID of the process holding the lock"
|
||||
},
|
||||
{
|
||||
"holder": "TYPE",
|
||||
"type": "<string>",
|
||||
"description": "kind of lock"
|
||||
},
|
||||
{
|
||||
"holder": "SIZE",
|
||||
"type": "<string|number>",
|
||||
"description": "size of the lock, use <number> if --bytes is given"
|
||||
},
|
||||
{
|
||||
"holder": "INODE",
|
||||
"type": "<integer>",
|
||||
"description": "inode number"
|
||||
},
|
||||
{
|
||||
"holder": "MAJ:MIN",
|
||||
"type": "<string>",
|
||||
"description": "major:minor device number"
|
||||
},
|
||||
{
|
||||
"holder": "MODE",
|
||||
"type": "<string>",
|
||||
"description": "lock access mode"
|
||||
},
|
||||
{
|
||||
"holder": "M",
|
||||
"type": "<boolean>",
|
||||
"description": "mandatory state of the lock: 0 (none), 1 (set)"
|
||||
},
|
||||
{
|
||||
"holder": "START",
|
||||
"type": "<integer>",
|
||||
"description": "relative byte offset of the lock"
|
||||
},
|
||||
{
|
||||
"holder": "END",
|
||||
"type": "<integer>",
|
||||
"description": "ending offset of the lock"
|
||||
},
|
||||
{
|
||||
"holder": "PATH",
|
||||
"type": "<string>",
|
||||
"description": "path of the locked file"
|
||||
},
|
||||
{
|
||||
"holder": "BLOCKER",
|
||||
"type": "<integer>",
|
||||
"description": "PID of the process blocking the lock"
|
||||
},
|
||||
{
|
||||
"holder": "HOLDERS",
|
||||
"type": "<string>",
|
||||
"description": "holders of the lock"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
src/uu/lslocks/columns.raw
Normal file
13
src/uu/lslocks/columns.raw
Normal file
@@ -0,0 +1,13 @@
|
||||
COMMAND <string> command\x20of\x20the\x20process\x20holding\x20the\x20lock
|
||||
PID <integer> PID\x20of\x20the\x20process\x20holding\x20the\x20lock
|
||||
TYPE <string> kind\x20of\x20lock
|
||||
SIZE <string|number> size\x20of\x20the\x20lock,\x20use\x20<number>\x20if\x20--bytes\x20is\x20given
|
||||
INODE <integer> inode\x20number
|
||||
MAJ:MIN <string> major:minor\x20device\x20number
|
||||
MODE <string> lock\x20access\x20mode
|
||||
M <boolean> mandatory\x20state\x20of\x20the\x20lock:\x200\x20(none),\x201\x20(set)
|
||||
START <integer> relative\x20byte\x20offset\x20of\x20the\x20lock
|
||||
END <integer> ending\x20offset\x20of\x20the\x20lock
|
||||
PATH <string> path\x20of\x20the\x20locked\x20file
|
||||
BLOCKER <integer> PID\x20of\x20the\x20process\x20blocking\x20the\x20lock
|
||||
HOLDERS <string> holders\x20of\x20the\x20lock
|
||||
13
src/uu/lslocks/columns.txt
Normal file
13
src/uu/lslocks/columns.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
COMMAND <string> command of the process holding the lock
|
||||
PID <integer> PID of the process holding the lock
|
||||
TYPE <string> kind of lock
|
||||
SIZE <string|number> size of the lock, use <number> if --bytes is given
|
||||
INODE <integer> inode number
|
||||
MAJ:MIN <string> major:minor device number
|
||||
MODE <string> lock access mode
|
||||
M <boolean> mandatory state of the lock: 0 (none), 1 (set)
|
||||
START <integer> relative byte offset of the lock
|
||||
END <integer> ending offset of the lock
|
||||
PATH <string> path of the locked file
|
||||
BLOCKER <integer> PID of the process blocking the lock
|
||||
HOLDERS <string> holders of the lock
|
||||
@@ -1,7 +1,10 @@
|
||||
# lslocks
|
||||
|
||||
```
|
||||
lslocks [OPTION]...
|
||||
lslocks [-b|--bytes] [-i|--noinaccessible] [-J|--json|-r|--raw] [-n|--noheadings] [-o list|--output list] [--output-all] [-p pid|--pid pid] [-u|--notruncate]
|
||||
lslocks {-H|--list-columns} [-J|--json|-r|--raw]
|
||||
lslocks {-V|--version}
|
||||
lslocks {-h|--help}
|
||||
```
|
||||
|
||||
lists system locks
|
||||
list local system locks.
|
||||
|
||||
122
src/uu/lslocks/src/column.rs
Normal file
122
src/uu/lslocks/src/column.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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_RIGHT, SCOLS_FL_TRUNC, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING, SCOLS_JSON_BOOLEAN,
|
||||
SCOLS_JSON_NUMBER, SCOLS_JSON_STRING,
|
||||
};
|
||||
|
||||
use crate::errors::LsLocksError;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct ColumnInfo {
|
||||
pub(crate) id: &'static CStr,
|
||||
pub(crate) width_hint: f64,
|
||||
pub(crate) flags: c_uint,
|
||||
pub(crate) default_json_type: c_uint,
|
||||
}
|
||||
|
||||
impl ColumnInfo {
|
||||
const fn new(
|
||||
id: &'static CStr,
|
||||
width_hint: f64,
|
||||
flags: c_uint,
|
||||
default_json_type: c_uint,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
width_hint,
|
||||
flags,
|
||||
default_json_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn json_type(&self, in_bytes: bool) -> c_uint {
|
||||
if in_bytes && self.id.to_bytes() == b"SIZE" {
|
||||
SCOLS_JSON_NUMBER
|
||||
} else {
|
||||
self.default_json_type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static COLUMN_INFOS: [ColumnInfo; 13] = [
|
||||
ColumnInfo::new(c"COMMAND", 15.0, 0, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"PID", 5.0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER),
|
||||
ColumnInfo::new(c"TYPE", 5.0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"SIZE", 4.0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"INODE", 5.0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER),
|
||||
ColumnInfo::new(c"MAJ:MIN", 6.0, 0, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"MODE", 5.0, 0, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"M", 1.0, 0, SCOLS_JSON_BOOLEAN),
|
||||
ColumnInfo::new(c"START", 10.0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER),
|
||||
ColumnInfo::new(c"END", 10.0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER),
|
||||
ColumnInfo::new(c"PATH", 0.0, SCOLS_FL_TRUNC, SCOLS_JSON_STRING),
|
||||
ColumnInfo::new(c"BLOCKER", 0.0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER),
|
||||
ColumnInfo::new(c"HOLDERS", 0.0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING),
|
||||
];
|
||||
|
||||
pub(crate) static ALL: [&str; 13] = [
|
||||
"COMMAND", "PID", "TYPE", "SIZE", "INODE", "MAJ:MIN", "MODE", "M", "START", "END", "PATH",
|
||||
"BLOCKER", "HOLDERS",
|
||||
];
|
||||
|
||||
pub(crate) static DEFAULT: [&str; 9] = [
|
||||
"COMMAND", "PID", "TYPE", "SIZE", "MODE", "M", "START", "END", "PATH",
|
||||
];
|
||||
|
||||
#[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 = LsLocksError;
|
||||
|
||||
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(|| LsLocksError::InvalidColumnName(name.into()))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
if list.is_empty() {
|
||||
Err(LsLocksError::InvalidColumnSequence(s.into()))
|
||||
} else {
|
||||
Ok(Self { append, list })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'_ clap::ArgMatches> for OutputColumns {
|
||||
fn from(args: &clap::ArgMatches) -> Self {
|
||||
args.get_one::<Self>(crate::options::OUTPUT)
|
||||
.map_or_else(Self::default, |columns| Self {
|
||||
append: columns.append,
|
||||
list: columns.list.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
136
src/uu/lslocks/src/display.rs
Normal file
136
src/uu/lslocks/src/display.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// 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};
|
||||
use std::sync::atomic::AtomicPtr;
|
||||
use std::{io, ptr};
|
||||
|
||||
use crate::errors::LsLocksError;
|
||||
use crate::utils::LockInfo;
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
let value = if in_bytes {
|
||||
size.to_string()
|
||||
} else {
|
||||
size_to_human_string(size)
|
||||
};
|
||||
|
||||
Some(Cow::Owned(CString::new(value).unwrap()))
|
||||
}
|
||||
|
||||
pub(crate) fn describe_holders(
|
||||
proc_lock: &LockInfo,
|
||||
pid_locks: &[LockInfo],
|
||||
) -> Result<CString, LsLocksError> {
|
||||
let lock_compare = move |lock: &&LockInfo| {
|
||||
lock.range == proc_lock.range
|
||||
&& lock.inode == proc_lock.inode
|
||||
&& lock.device_id == proc_lock.device_id
|
||||
&& lock.mandatory == proc_lock.mandatory
|
||||
&& lock.blocked == proc_lock.blocked
|
||||
&& lock.kind == proc_lock.kind
|
||||
&& lock.mode == proc_lock.mode
|
||||
};
|
||||
|
||||
let mut separator: &[u8] = &[];
|
||||
|
||||
let append_holder = move |mut buffer: Vec<u8>, lock: &LockInfo| {
|
||||
buffer.extend(separator);
|
||||
separator = b"\n";
|
||||
|
||||
buffer.extend(lock.process_id.to_string().into_bytes());
|
||||
buffer.push(b',');
|
||||
|
||||
if let Some(command_line) = lock.command_name.as_deref().map(CStr::to_bytes) {
|
||||
buffer.extend(command_line);
|
||||
}
|
||||
|
||||
buffer.push(b',');
|
||||
buffer.extend(lock.file_descriptor.to_string().into_bytes());
|
||||
buffer
|
||||
};
|
||||
|
||||
let buffer = pid_locks
|
||||
.iter()
|
||||
.filter(lock_compare)
|
||||
.fold(Vec::default(), append_holder);
|
||||
|
||||
CString::new(buffer).map_err(|_| LsLocksError::io0("invalid data", io::ErrorKind::InvalidData))
|
||||
}
|
||||
67
src/uu/lslocks/src/errors.rs
Normal file
67
src/uu/lslocks/src/errors.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 LsLocksError {
|
||||
InvalidColumnName(String),
|
||||
InvalidColumnSequence(String),
|
||||
IO0(String, std::io::Error),
|
||||
IO1(String, PathBuf, std::io::Error),
|
||||
}
|
||||
|
||||
impl LsLocksError {
|
||||
pub(crate) fn io0(message: impl Into<String>, error: impl Into<std::io::Error>) -> Self {
|
||||
Self::IO0(message.into(), error.into())
|
||||
}
|
||||
|
||||
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, LsLocksError> {
|
||||
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 LsLocksError {
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UError for LsLocksError {
|
||||
fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn usage(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LsLocksError {}
|
||||
@@ -3,295 +3,61 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::{fmt, fs, str::FromStr};
|
||||
// Remove this if the tool is ported to Non-UNIX platforms.
|
||||
#![cfg_attr(
|
||||
not(target_os = "linux"),
|
||||
allow(dead_code, non_camel_case_types, unused_imports)
|
||||
)]
|
||||
|
||||
use clap::{crate_version, Command};
|
||||
#[cfg(target_os = "linux")]
|
||||
mod column;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod display;
|
||||
mod errors;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod smartcols;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod utils;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString, OsStr};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command, crate_version, value_parser};
|
||||
use uucore::{error::UResult, format_usage, help_about, help_usage};
|
||||
|
||||
// See https://www.man7.org/linux/man-pages/man5/proc_locks.5.html for details on each field's meaning
|
||||
#[derive(Debug)]
|
||||
struct Lock {
|
||||
_ord: usize,
|
||||
lock_type: LockType,
|
||||
mandatory: bool,
|
||||
mode: LockMode,
|
||||
pid: Option<usize>, // This value is -1 for OFD locks, hence the Option
|
||||
major_minor: String,
|
||||
inode: usize,
|
||||
start_offset: usize, // Byte offset to start of lock
|
||||
end_offset: Option<usize>, // None = lock does not have an explicit end offset and applies until the end of the file
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::column::{ColumnInfo, OutputColumns};
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::display::{describe_holders, describe_integer, describe_size};
|
||||
use crate::errors::LsLocksError;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::smartcols::{Table, TableOperations};
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::utils::{
|
||||
_PATH_PROC, _PATH_PROC_LOCKS, BinFileLineIter, LockInfo, entry_is_dir_or_unknown,
|
||||
proc_pid_command_name,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use libc::pid_t;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
type pid_t = i32;
|
||||
|
||||
impl Lock {
|
||||
fn get_value(&self, col: &Column) -> String {
|
||||
match col {
|
||||
Column::Command => resolve_command(self).unwrap_or("<unknown>".to_string()),
|
||||
Column::Pid => self
|
||||
.pid
|
||||
.map(|pid| pid.to_string())
|
||||
.unwrap_or("-".to_string()),
|
||||
Column::Type => self.lock_type.to_string(),
|
||||
Column::Size => todo!(),
|
||||
Column::Inode => self.inode.to_string(),
|
||||
Column::MajorMinor => self.major_minor.clone(),
|
||||
Column::Mode => self.mode.to_string(),
|
||||
Column::Mandatory => {
|
||||
if self.mandatory {
|
||||
"1".to_string()
|
||||
} else {
|
||||
"0".to_string()
|
||||
}
|
||||
}
|
||||
Column::Start => self.start_offset.to_string(),
|
||||
// TODO: In the case of EOF end_offset, we should actually resolve the actual file size and display that as the end offset
|
||||
Column::End => self
|
||||
.end_offset
|
||||
.map(|offset| offset.to_string())
|
||||
.unwrap_or("EOF".to_string()),
|
||||
Column::Path => todo!(), // TODO: Resolve filepath of the lock target
|
||||
Column::Blocker => todo!(), // TODO: Check if lock is blocker (and by what)
|
||||
Column::Holders => todo!(), // TODO: Resolve all holders of the lock (this would also let us display a process/PID for OFD locks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Lock {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = input.split_whitespace();
|
||||
|
||||
// Ordinal position comes in the form of `<value>:`, so we need to strip away the `:`
|
||||
let ord = parts
|
||||
.next()
|
||||
.and_then(|s| s.strip_suffix(":"))
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
let lock_type = parts
|
||||
.next()
|
||||
.and_then(|part| LockType::from_str(part).ok())
|
||||
.unwrap();
|
||||
let mandatory = parts
|
||||
.next()
|
||||
.map(|part| match part {
|
||||
"MANDATORY" => true,
|
||||
"ADVISORY" => false,
|
||||
_ => panic!("Unrecognized value in lock line: {}", part),
|
||||
})
|
||||
.unwrap();
|
||||
let mode = parts
|
||||
.next()
|
||||
.and_then(|part| LockMode::from_str(part).ok())
|
||||
.unwrap();
|
||||
let pid: Option<usize> = parts.next().and_then(|pid_str| match pid_str {
|
||||
"-1" => None,
|
||||
other => other.parse::<usize>().ok(),
|
||||
});
|
||||
|
||||
if lock_type == LockType::OFDLCK && pid.is_some() {
|
||||
println!("Unexpected PID value on OFD lock: '{}'", input);
|
||||
return Err(());
|
||||
};
|
||||
|
||||
// This field has a format of MAJOR:MINOR:INODE
|
||||
let major_minor_inode: Vec<_> = parts.next().unwrap().split(":").collect();
|
||||
assert_eq!(major_minor_inode.len(), 3);
|
||||
let major_minor = [major_minor_inode[0], major_minor_inode[1]].join(":");
|
||||
let inode = major_minor_inode[2].parse::<usize>().unwrap();
|
||||
|
||||
let start_offset = parts.next().unwrap().parse::<usize>().unwrap();
|
||||
let end_offset: Option<usize> = parts.next().and_then(|offset_str| match offset_str {
|
||||
"EOF" => None,
|
||||
other => other.parse::<usize>().ok(),
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
_ord: ord,
|
||||
lock_type,
|
||||
mandatory,
|
||||
mode,
|
||||
pid,
|
||||
major_minor,
|
||||
inode,
|
||||
start_offset,
|
||||
end_offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum LockType {
|
||||
FLOCK, // BSD file lock
|
||||
OFDLCK, // Open file descriptor
|
||||
POSIX, // POSIX byte-range lock
|
||||
}
|
||||
|
||||
impl FromStr for LockType {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
match input {
|
||||
"FLOCK" => Ok(Self::FLOCK),
|
||||
"OFDLCK" => Ok(Self::OFDLCK),
|
||||
"POSIX" => Ok(Self::POSIX),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LockType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LockType::FLOCK => write!(f, "FLOCK"),
|
||||
LockType::OFDLCK => write!(f, "OFDLCK"),
|
||||
LockType::POSIX => write!(f, "POSIX"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LockMode {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
impl FromStr for LockMode {
|
||||
type Err = ();
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
match input {
|
||||
"WRITE" => Ok(Self::Write),
|
||||
"READ" => Ok(Self::Read),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LockMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LockMode::Write => write!(f, "WRITE"),
|
||||
LockMode::Read => write!(f, "READ"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All of the columns that need to be supported in the final version
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Column {
|
||||
Command,
|
||||
Pid,
|
||||
Type,
|
||||
Size,
|
||||
Inode,
|
||||
MajorMinor,
|
||||
Mode,
|
||||
Mandatory,
|
||||
Start,
|
||||
End,
|
||||
Path,
|
||||
Blocker,
|
||||
Holders,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
fn header_text(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Command => "COMMAND",
|
||||
Self::Pid => "PID",
|
||||
Self::Type => "TYPE",
|
||||
Self::Size => "SIZE",
|
||||
Self::Inode => "INODE",
|
||||
Self::MajorMinor => "MAJ:MIN",
|
||||
Self::Mode => "MODE",
|
||||
Self::Mandatory => "M",
|
||||
Self::Start => "START",
|
||||
Self::End => "END",
|
||||
Self::Path => "PATH",
|
||||
Self::Blocker => "BLOCKER",
|
||||
Self::Holders => "HOLDERS",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_command(lock: &Lock) -> Option<String> {
|
||||
if let Some(pid) = lock.pid {
|
||||
return fs::read_to_string(format!("/proc/{}/comm", pid))
|
||||
.map(|content| content.trim().to_string())
|
||||
.ok();
|
||||
}
|
||||
|
||||
// File descriptor locks don't have a real notion of an "owner process", since it can be shared by multiple processes.
|
||||
// The original `lslocks` goes through `/proc/<pid>/fdinfo/*` to find *any* of the processes that reference the same device/inode combo from the lock
|
||||
// We don't implement this behaviour yet, and just show "unknown" for the locks which don't have a clear owner
|
||||
None
|
||||
}
|
||||
|
||||
const DEFAULT_COLS: &[Column] = &[
|
||||
Column::Command,
|
||||
Column::Pid,
|
||||
Column::Type,
|
||||
//TODO: Implement Column::Size here
|
||||
Column::Mode,
|
||||
Column::Mandatory,
|
||||
Column::Start,
|
||||
Column::End,
|
||||
//TODO: Implements Column::Path here
|
||||
];
|
||||
|
||||
struct OutputOptions {
|
||||
cols: Vec<Column>,
|
||||
}
|
||||
|
||||
fn print_output(locks: Vec<Lock>, output_opts: OutputOptions) {
|
||||
let mut column_widths: Vec<_> = output_opts
|
||||
.cols
|
||||
.iter()
|
||||
.map(|col| col.header_text().len())
|
||||
.collect();
|
||||
|
||||
for lock in &locks {
|
||||
for (i, col) in output_opts.cols.iter().enumerate() {
|
||||
column_widths[i] = column_widths[i].max(lock.get_value(col).len());
|
||||
}
|
||||
}
|
||||
|
||||
let headers: Vec<_> = output_opts
|
||||
.cols
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, col)| format!("{:<width$}", col.header_text(), width = column_widths[i]))
|
||||
.collect();
|
||||
|
||||
println!("{}", headers.join(" "));
|
||||
|
||||
for lock in &locks {
|
||||
let values: Vec<_> = output_opts
|
||||
.cols
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, col)| format!("{:<width$}", lock.get_value(col), width = column_widths[i]))
|
||||
.collect();
|
||||
println!("{}", values.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(_args: impl uucore::Args) -> UResult<()> {
|
||||
let output_opts = OutputOptions {
|
||||
cols: Vec::from(DEFAULT_COLS),
|
||||
};
|
||||
|
||||
let locks: Vec<_> = match fs::read_to_string("/proc/locks") {
|
||||
Ok(content) => content
|
||||
.lines()
|
||||
.map(|line| Lock::from_str(line).unwrap())
|
||||
.collect(),
|
||||
Err(e) => panic!("Could not read /proc/locks: {}", e),
|
||||
};
|
||||
|
||||
print_output(locks, output_opts);
|
||||
Ok(())
|
||||
mod options {
|
||||
pub static BYTES: &str = "bytes";
|
||||
pub static JSON: &str = "json";
|
||||
pub static LIST_COLUMNS: &str = "list-columns";
|
||||
pub static LIST: &str = "list";
|
||||
pub static NO_HEADINGS: &str = "noheadings";
|
||||
pub static NO_INACCESSIBLE: &str = "noinaccessible";
|
||||
pub static NO_TRUNCATE: &str = "notruncate";
|
||||
pub static OUTPUT_ALL: &str = "output-all";
|
||||
pub static OUTPUT: &str = "output";
|
||||
pub static PID: &str = "pid";
|
||||
pub static RAW: &str = "raw";
|
||||
}
|
||||
|
||||
const ABOUT: &str = help_about!("lslocks.md");
|
||||
@@ -303,4 +69,447 @@ pub fn uu_app() -> Command {
|
||||
.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::NO_INACCESSIBLE)
|
||||
.short('i')
|
||||
.long(options::NO_INACCESSIBLE)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("ignore locks without read permissions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::JSON)
|
||||
.short('J')
|
||||
.long(options::JSON)
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with(options::RAW)
|
||||
.help("use JSON output format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::LIST_COLUMNS)
|
||||
.short('H')
|
||||
.long(options::LIST_COLUMNS)
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with_all([
|
||||
options::BYTES,
|
||||
options::NO_INACCESSIBLE,
|
||||
options::NO_HEADINGS,
|
||||
options::OUTPUT,
|
||||
options::OUTPUT_ALL,
|
||||
options::PID,
|
||||
options::NO_TRUNCATE,
|
||||
])
|
||||
.help("list the available columns"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NO_HEADINGS)
|
||||
.short('n')
|
||||
.long(options::NO_HEADINGS)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("don't print headings"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::OUTPUT)
|
||||
.short('o')
|
||||
.long(options::OUTPUT)
|
||||
.value_name(options::LIST)
|
||||
.value_parser(OutputColumns::from_str)
|
||||
.action(ArgAction::Set)
|
||||
.help("output columns (see --list-columns)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::OUTPUT_ALL)
|
||||
.long(options::OUTPUT_ALL)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("output all columns"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::PID)
|
||||
.short('p')
|
||||
.long(options::PID)
|
||||
.value_name(options::PID)
|
||||
.value_parser(value_parser!(pid_t))
|
||||
.action(ArgAction::Set)
|
||||
.help("display only locks held by this process"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NO_TRUNCATE)
|
||||
.short('u')
|
||||
.long(options::NO_TRUNCATE)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("don't truncate text in columns"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::RAW)
|
||||
.short('r')
|
||||
.long(options::RAW)
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with(options::JSON)
|
||||
.help("use the raw output format"),
|
||||
)
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
if args.get_flag(options::LIST_COLUMNS) {
|
||||
list_columns(output_mode)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
lslocks(&args, output_mode).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 = LsLocksError;
|
||||
|
||||
fn from_str(_s: &str) -> Result<Self, Self::Err> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn lslocks(args: &ArgMatches, output_mode: OutputMode) -> Result<(), LsLocksError> {
|
||||
let columns = collect_output_columns(args)?;
|
||||
|
||||
let in_bytes = args.get_flag(options::BYTES);
|
||||
let mut table = setup_table(args, output_mode, in_bytes, &columns)?;
|
||||
|
||||
let no_inaccessible = args.get_flag(options::NO_INACCESSIBLE);
|
||||
let pid_locks = collect_pid_locks(no_inaccessible)?;
|
||||
|
||||
let path = Path::new(_PATH_PROC_LOCKS);
|
||||
let mut lines = BinFileLineIter::open(path)?;
|
||||
|
||||
let mut proc_locks = Vec::default();
|
||||
|
||||
while let Some(line) = lines.next_line()? {
|
||||
if let Some(lock) =
|
||||
LockInfo::parse(no_inaccessible, path, None, -1, c"", Some(&pid_locks), line)?
|
||||
{
|
||||
proc_locks.push(lock);
|
||||
}
|
||||
}
|
||||
|
||||
let target_pid = args.get_one::<pid_t>(options::PID).copied();
|
||||
|
||||
fill_table(
|
||||
output_mode,
|
||||
&columns,
|
||||
target_pid,
|
||||
in_bytes,
|
||||
&mut table,
|
||||
&pid_locks,
|
||||
&proc_locks,
|
||||
)?;
|
||||
|
||||
table.print()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn lslocks(_args: &ArgMatches, _output_mode: OutputMode) -> Result<(), LsLocksError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum OutputMode {
|
||||
None = 0,
|
||||
Raw,
|
||||
Json,
|
||||
}
|
||||
|
||||
impl From<&'_ ArgMatches> for OutputMode {
|
||||
fn from(args: &ArgMatches) -> Self {
|
||||
if args.get_flag(options::JSON) {
|
||||
Self::Json
|
||||
} else if args.get_flag(options::RAW) {
|
||||
Self::Raw
|
||||
} else {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_columns(output_mode: OutputMode) -> Result<(), LsLocksError> {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
|
||||
match output_mode {
|
||||
OutputMode::None => stdout.write_all(include_bytes!("../columns.txt")),
|
||||
OutputMode::Raw => stdout.write_all(include_bytes!("../columns.raw")),
|
||||
OutputMode::Json => stdout.write_all(include_bytes!("../columns.json")),
|
||||
}
|
||||
.map_err(|err| LsLocksError::io0("stdout", err))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn collect_output_columns(args: &ArgMatches) -> Result<Vec<&'static ColumnInfo>, LsLocksError> {
|
||||
let columns = OutputColumns::from(args);
|
||||
|
||||
let columns = if columns.append {
|
||||
let default_names: &[&str] = if args.get_flag(options::OUTPUT_ALL) {
|
||||
&column::ALL
|
||||
} else {
|
||||
&column::DEFAULT
|
||||
};
|
||||
|
||||
let mut default_columns = default_names
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
column::COLUMN_INFOS
|
||||
.iter()
|
||||
.find(|&column| column.id.to_str().unwrap() == name)
|
||||
.ok_or_else(|| LsLocksError::InvalidColumnName(name.into()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
default_columns.extend(columns.list);
|
||||
default_columns
|
||||
} else {
|
||||
columns.list
|
||||
};
|
||||
|
||||
Ok(columns)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn setup_table(
|
||||
args: &ArgMatches,
|
||||
output_mode: OutputMode,
|
||||
in_bytes: bool,
|
||||
columns: &[&ColumnInfo],
|
||||
) -> Result<Table, LsLocksError> {
|
||||
use smartcols_sys::{
|
||||
SCOLS_FL_TRUNC, SCOLS_FL_WRAP, scols_wrapnl_chunksize, scols_wrapnl_nextchunk,
|
||||
};
|
||||
|
||||
smartcols::initialize();
|
||||
|
||||
let mut table = Table::new()?;
|
||||
|
||||
if args.get_flag(options::JSON) {
|
||||
table.set_name(c"locks")?;
|
||||
}
|
||||
|
||||
if args.get_flag(options::NO_HEADINGS) {
|
||||
table.enable_headings(false)?;
|
||||
}
|
||||
|
||||
match output_mode {
|
||||
OutputMode::Raw => table.enable_raw(true)?,
|
||||
OutputMode::Json => table.enable_json(true)?,
|
||||
OutputMode::None => {}
|
||||
}
|
||||
|
||||
let no_truncate = args.get_flag(options::NO_TRUNCATE);
|
||||
|
||||
for &column_info in columns {
|
||||
let mut flags = column_info.flags;
|
||||
if no_truncate {
|
||||
flags &= !SCOLS_FL_TRUNC;
|
||||
}
|
||||
let mut column = table.new_column(column_info.id, column_info.width_hint, flags)?;
|
||||
|
||||
if (flags & SCOLS_FL_WRAP) != 0 {
|
||||
column.set_wrap_func(
|
||||
Some(scols_wrapnl_chunksize),
|
||||
Some(scols_wrapnl_nextchunk),
|
||||
ptr::null_mut(),
|
||||
)?;
|
||||
|
||||
column.set_safe_chars(c"\n")?;
|
||||
}
|
||||
|
||||
if output_mode == OutputMode::Json {
|
||||
column.set_json_type(column_info.json_type(in_bytes))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn collect_pid_locks(no_inaccessible: bool) -> Result<Vec<LockInfo>, LsLocksError> {
|
||||
let path = Path::new(_PATH_PROC);
|
||||
let dir_entries = path
|
||||
.read_dir()
|
||||
.map_err(|err| LsLocksError::io1("reading directory", path, err))?;
|
||||
|
||||
let mut pid_locks = Vec::default();
|
||||
|
||||
for entry in dir_entries {
|
||||
let entry = entry.map_err(|err| LsLocksError::io1("reading directory entry", path, err))?;
|
||||
|
||||
let Some(process_id) = entry.file_name().to_str().and_then(|s| s.parse().ok()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = entry.path();
|
||||
let file_type = entry
|
||||
.file_type()
|
||||
.map_err(|err| LsLocksError::io1("reading directory entry type", &path, err))?;
|
||||
|
||||
if !entry_is_dir_or_unknown(&file_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let command_name = proc_pid_command_name(&path);
|
||||
|
||||
// We should report the error instead of silently continuing.
|
||||
let _ignored = append_pid_locks(
|
||||
no_inaccessible,
|
||||
&path.join("fdinfo"),
|
||||
process_id,
|
||||
command_name.as_deref().unwrap_or(c""),
|
||||
&mut pid_locks,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(pid_locks)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn append_pid_locks(
|
||||
no_inaccessible: bool,
|
||||
path: &Path,
|
||||
process_id: pid_t,
|
||||
command_name: &CStr,
|
||||
pid_locks: &mut Vec<LockInfo>,
|
||||
) -> Result<(), LsLocksError> {
|
||||
let Ok(dir_entries) = path.read_dir() else {
|
||||
//eprintln!("{}", LsLocksError::io1("reading directory", &path, err));
|
||||
return Ok(()); // We should report the error instead of silently continuing.
|
||||
};
|
||||
|
||||
for entry in dir_entries {
|
||||
let entry = entry.map_err(|err| LsLocksError::io1("reading directory entry", path, err))?;
|
||||
|
||||
let Some(file_descriptor) = entry.file_name().to_str().and_then(|s| s.parse().ok()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = entry.path();
|
||||
let mut lines = BinFileLineIter::open(&path)?;
|
||||
|
||||
// This silently ignores potential errors that prevent us from reading the next line.
|
||||
// We should report the error instead of silently ignoring it.
|
||||
while let Ok(Some(line)) = lines.next_line() {
|
||||
let Some(suffix) = line.strip_prefix(b"lock:").map(<[u8]>::trim_ascii) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(lock) = LockInfo::parse(
|
||||
no_inaccessible,
|
||||
&path,
|
||||
Some(process_id),
|
||||
file_descriptor,
|
||||
command_name,
|
||||
None,
|
||||
suffix,
|
||||
)? {
|
||||
pid_locks.push(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn fill_table(
|
||||
output_mode: OutputMode,
|
||||
columns: &[&ColumnInfo],
|
||||
target_pid: Option<pid_t>,
|
||||
in_bytes: bool,
|
||||
table: &mut Table,
|
||||
pid_locks: &[LockInfo],
|
||||
proc_locks: &[LockInfo],
|
||||
) -> Result<(), LsLocksError> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
for proc_lock in proc_locks
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|lock| target_pid.is_none_or(|target_pid| lock.process_id == target_pid))
|
||||
{
|
||||
let mut line = table.new_line(None)?;
|
||||
|
||||
for (cell_index, &column) in columns.iter().enumerate() {
|
||||
let data_str = match column.id.to_bytes() {
|
||||
b"PID" => describe_integer(proc_lock.process_id),
|
||||
b"INODE" => describe_integer(proc_lock.inode),
|
||||
b"M" => describe_integer(u8::from(proc_lock.mandatory)),
|
||||
b"START" => describe_integer(proc_lock.range.start),
|
||||
b"END" => describe_integer(proc_lock.range.end),
|
||||
|
||||
b"SIZE" => proc_lock
|
||||
.size
|
||||
.and_then(|size| describe_size(size, in_bytes)),
|
||||
|
||||
b"TYPE" => Some(Cow::Borrowed(proc_lock.kind.as_c_str())),
|
||||
|
||||
b"MAJ:MIN" => {
|
||||
let major = libc::major(proc_lock.device_id);
|
||||
let minor = libc::minor(proc_lock.device_id);
|
||||
let value = if matches!(output_mode, OutputMode::Json | OutputMode::Raw) {
|
||||
format!("{major}:{minor}")
|
||||
} else {
|
||||
format!("{major:3}:{minor:<3}")
|
||||
};
|
||||
Some(Cow::Owned(CString::new(value).unwrap()))
|
||||
}
|
||||
|
||||
b"MODE" => {
|
||||
if proc_lock.blocked {
|
||||
let mut buffer = proc_lock.mode.clone().into_bytes();
|
||||
buffer.push(b'*');
|
||||
Some(Cow::Owned(CString::new(buffer).unwrap()))
|
||||
} else {
|
||||
Some(Cow::Borrowed(proc_lock.mode.as_c_str()))
|
||||
}
|
||||
}
|
||||
|
||||
b"COMMAND" => proc_lock.command_name.as_deref().map(Cow::Borrowed),
|
||||
|
||||
b"PATH" => proc_lock
|
||||
.path
|
||||
.as_deref()
|
||||
.map(Path::as_os_str)
|
||||
.map(OsStr::as_bytes)
|
||||
.map(|path| Cow::Owned(CString::new(path).unwrap())),
|
||||
|
||||
b"BLOCKER" => (proc_lock.blocked && proc_lock.id != -1)
|
||||
.then(|| proc_locks.iter())
|
||||
.and_then(|mut iter| {
|
||||
iter.find(|&lock| !lock.blocked && lock.id == proc_lock.id)
|
||||
})
|
||||
.and_then(|found| describe_integer(found.process_id)),
|
||||
|
||||
b"HOLDERS" => describe_holders(proc_lock, pid_locks)
|
||||
.map(Cow::Owned)
|
||||
.map(Some)?,
|
||||
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if let Some(data_str) = data_str {
|
||||
line.set_data(cell_index, &data_str)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
137
src/uu/lslocks/src/smartcols.rs
Normal file
137
src/uu/lslocks/src/smartcols.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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_char, c_int, c_uint, c_void};
|
||||
use std::ptr::NonNull;
|
||||
use std::{io, ptr};
|
||||
|
||||
use smartcols_sys::{
|
||||
libscols_column, libscols_line, libscols_table, scols_column_set_json_type,
|
||||
scols_column_set_safechars, scols_column_set_wrapfunc, scols_init_debug, scols_line_set_data,
|
||||
scols_new_table, scols_print_table, scols_table_enable_json, scols_table_enable_noheadings,
|
||||
scols_table_enable_raw, scols_table_new_column, scols_table_new_line, scols_table_set_name,
|
||||
scols_unref_table,
|
||||
};
|
||||
|
||||
use crate::errors::LsLocksError;
|
||||
|
||||
pub(crate) fn initialize() {
|
||||
unsafe { scols_init_debug(0) };
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Table(NonNull<libscols_table>);
|
||||
|
||||
impl Table {
|
||||
pub(crate) fn new() -> Result<Self, LsLocksError> {
|
||||
NonNull::new(unsafe { scols_new_table() })
|
||||
.ok_or_else(|| LsLocksError::io0("scols_new_table", io::ErrorKind::OutOfMemory))
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
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<(), LsLocksError> {
|
||||
let no_headings = c_int::from(!enable);
|
||||
let r = unsafe { scols_table_enable_noheadings(self.as_ptr(), no_headings) };
|
||||
LsLocksError::io_from_neg_errno("scols_table_enable_noheadings", r).map(|_| ())
|
||||
}
|
||||
|
||||
fn enable_raw(&mut self, enable: bool) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_table_enable_raw(self.as_ptr(), c_int::from(enable)) };
|
||||
LsLocksError::io_from_neg_errno("scols_table_enable_raw", r).map(|_| ())
|
||||
}
|
||||
|
||||
fn enable_json(&mut self, enable: bool) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_table_enable_json(self.as_ptr(), c_int::from(enable)) };
|
||||
LsLocksError::io_from_neg_errno("scols_table_enable_json", r).map(|_| ())
|
||||
}
|
||||
|
||||
fn new_column(
|
||||
&mut self,
|
||||
name: &CStr,
|
||||
width_hint: f64,
|
||||
flags: c_uint,
|
||||
) -> Result<ColumnRef, LsLocksError> {
|
||||
NonNull::new(unsafe {
|
||||
scols_table_new_column(self.as_ptr(), name.as_ptr(), width_hint, flags as c_int)
|
||||
})
|
||||
.ok_or_else(|| LsLocksError::io0("scols_table_new_column", io::ErrorKind::OutOfMemory))
|
||||
.map(ColumnRef)
|
||||
}
|
||||
|
||||
fn new_line(&mut self, parent: Option<&mut LineRef>) -> Result<LineRef, LsLocksError> {
|
||||
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(|| LsLocksError::io0("scols_table_new_line", io::ErrorKind::OutOfMemory))
|
||||
.map(LineRef)
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: &CStr) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_table_set_name(self.as_ptr(), name.as_ptr()) };
|
||||
LsLocksError::io_from_neg_errno("scols_table_set_name", r).map(|_| ())
|
||||
}
|
||||
|
||||
fn print(&self) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_print_table(self.as_ptr()) };
|
||||
LsLocksError::io_from_neg_errno("scols_print_table", r).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct LineRef(NonNull<libscols_line>);
|
||||
|
||||
impl LineRef {
|
||||
pub(crate) fn set_data(&mut self, cell_index: usize, data: &CStr) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_line_set_data(self.0.as_ptr(), cell_index, data.as_ptr()) };
|
||||
LsLocksError::io_from_neg_errno("scols_line_set_data", r).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct ColumnRef(NonNull<libscols_column>);
|
||||
|
||||
impl ColumnRef {
|
||||
pub(crate) fn set_json_type(&mut self, json_type: c_uint) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_column_set_json_type(self.0.as_ptr(), json_type as c_int) };
|
||||
LsLocksError::io_from_neg_errno("scols_column_set_json_type", r).map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn set_safe_chars(&mut self, safe: &CStr) -> Result<(), LsLocksError> {
|
||||
let r = unsafe { scols_column_set_safechars(self.0.as_ptr(), safe.as_ptr()) };
|
||||
LsLocksError::io_from_neg_errno("scols_column_set_safechars", r).map(|_| ())
|
||||
}
|
||||
|
||||
pub(crate) fn set_wrap_func(
|
||||
&mut self,
|
||||
wrap_chunk_size: Option<
|
||||
unsafe extern "C" fn(*const libscols_column, *const c_char, *mut c_void) -> usize,
|
||||
>,
|
||||
wrap_next_chunk: Option<
|
||||
unsafe extern "C" fn(*const libscols_column, *mut c_char, *mut c_void) -> *mut c_char,
|
||||
>,
|
||||
user_data: *mut c_void,
|
||||
) -> Result<(), LsLocksError> {
|
||||
let r = unsafe {
|
||||
scols_column_set_wrapfunc(self.0.as_ptr(), wrap_chunk_size, wrap_next_chunk, user_data)
|
||||
};
|
||||
LsLocksError::io_from_neg_errno("scols_column_set_wrapfunc", r).map(|_| ())
|
||||
}
|
||||
}
|
||||
365
src/uu/lslocks/src/utils.rs
Normal file
365
src/uu/lslocks/src/utils.rs
Normal file
@@ -0,0 +1,365 @@
|
||||
// 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, c_int, c_uint};
|
||||
use std::fs::{File, FileType};
|
||||
use std::io;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::ops::Range;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use libmount_sys::{
|
||||
MNT_ITER_BACKWARD, mnt_fs_get_target, mnt_new_table_from_file, mnt_table_find_devno,
|
||||
};
|
||||
|
||||
use crate::errors::LsLocksError;
|
||||
|
||||
pub(crate) static _PATH_PROC: &str = "/proc";
|
||||
pub(crate) static _PATH_PROC_LOCKS: &str = "/proc/locks";
|
||||
static _PATH_PROC_MOUNTINFO: &str = "/proc/self/mountinfo";
|
||||
static _PATH_PROC_MOUNTINFO_C: &CStr = c"/proc/self/mountinfo";
|
||||
|
||||
pub(crate) fn entry_is_dir_or_unknown(file_type: &FileType) -> bool {
|
||||
file_type.is_dir()
|
||||
|| (!file_type.is_file()
|
||||
&& !file_type.is_symlink()
|
||||
&& !file_type.is_block_device()
|
||||
&& !file_type.is_char_device()
|
||||
&& !file_type.is_fifo()
|
||||
&& !file_type.is_socket())
|
||||
}
|
||||
|
||||
pub(crate) fn proc_pid_command_name(proc_path: &Path) -> Result<CString, LsLocksError> {
|
||||
let path = proc_path.join("comm");
|
||||
|
||||
let mut contents =
|
||||
std::fs::read(&path).map_err(|err| LsLocksError::io1("reading file", &path, err))?;
|
||||
if contents.last().copied() == Some(0_u8) {
|
||||
contents.pop();
|
||||
}
|
||||
if contents.last().copied() == Some(b'\n') {
|
||||
contents.pop();
|
||||
}
|
||||
|
||||
CString::new(contents).map_err(|_| {
|
||||
let err = io::ErrorKind::InvalidData;
|
||||
LsLocksError::io1("invalid data", &path, err)
|
||||
})
|
||||
}
|
||||
|
||||
fn pid_command_name(pid: libc::pid_t) -> Result<CString, LsLocksError> {
|
||||
proc_pid_command_name(&Path::new(_PATH_PROC).join(pid.to_string()))
|
||||
}
|
||||
|
||||
fn path_and_size_of_inode_opened_by_process(
|
||||
pid: libc::pid_t,
|
||||
inode: libc::ino_t,
|
||||
) -> Result<(PathBuf, u64), LsLocksError> {
|
||||
let path = Path::new(_PATH_PROC).join(format!("{pid}")).join("fd");
|
||||
|
||||
let dir_entries = path
|
||||
.read_dir()
|
||||
.map_err(|err| LsLocksError::io1("reading directory", &path, err))?;
|
||||
|
||||
for entry in dir_entries {
|
||||
let entry =
|
||||
entry.map_err(|err| LsLocksError::io1("reading directory entry", &path, err))?;
|
||||
|
||||
if entry
|
||||
.file_name()
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.any(|&b| !b.is_ascii_digit())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
|
||||
let md = path
|
||||
.metadata()
|
||||
.map_err(|err| LsLocksError::io1("reading file metadata", &path, err))?;
|
||||
|
||||
if md.ino() == inode {
|
||||
let path = entry
|
||||
.path()
|
||||
.read_link()
|
||||
.map_err(|err| LsLocksError::io1("reading symbolic link", &path, err))?;
|
||||
|
||||
return Ok((path, md.len()));
|
||||
}
|
||||
}
|
||||
|
||||
Err(LsLocksError::io0(
|
||||
"looking for inode open in process",
|
||||
io::ErrorKind::NotFound,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct BinFileLineIter {
|
||||
path: PathBuf,
|
||||
input: BufReader<File>,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BinFileLineIter {
|
||||
pub(crate) fn open(path: &Path) -> Result<Self, LsLocksError> {
|
||||
let input = File::open(path)
|
||||
.map(BufReader::new)
|
||||
.map_err(|err| LsLocksError::io1("opening file", path, err))?;
|
||||
|
||||
Ok(Self {
|
||||
path: path.into(),
|
||||
input,
|
||||
buffer: Vec::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn next_line(&mut self) -> Result<Option<&[u8]>, LsLocksError> {
|
||||
self.buffer.clear();
|
||||
|
||||
let count = self
|
||||
.input
|
||||
.read_until(b'\n', &mut self.buffer)
|
||||
.map_err(|err| LsLocksError::io1("reading file", &self.path, err))?;
|
||||
if count == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if self.buffer.last().copied() == Some(b'\n') {
|
||||
self.buffer.pop();
|
||||
}
|
||||
if self.buffer.last().copied() == Some(b'\r') {
|
||||
self.buffer.pop();
|
||||
}
|
||||
Ok(Some(&self.buffer))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LockInfo {
|
||||
pub(crate) command_name: Option<CString>,
|
||||
pub(crate) process_id: libc::pid_t,
|
||||
pub(crate) path: Option<PathBuf>,
|
||||
pub(crate) kind: CString,
|
||||
pub(crate) mode: CString,
|
||||
pub(crate) range: Range<u64>,
|
||||
pub(crate) inode: libc::ino_t,
|
||||
pub(crate) device_id: libc::dev_t,
|
||||
pub(crate) mandatory: bool,
|
||||
pub(crate) blocked: bool,
|
||||
pub(crate) size: Option<u64>,
|
||||
pub(crate) file_descriptor: c_int,
|
||||
pub(crate) id: i64,
|
||||
}
|
||||
|
||||
impl LockInfo {
|
||||
pub(crate) fn parse(
|
||||
no_inaccessible: bool,
|
||||
fdinfo_path: &Path,
|
||||
process_id: Option<libc::pid_t>,
|
||||
file_descriptor: c_int,
|
||||
command_name: &CStr,
|
||||
pid_locks: Option<&[Self]>,
|
||||
line: &[u8],
|
||||
) -> Result<Option<Self>, LsLocksError> {
|
||||
let err_map = || {
|
||||
let err = io::ErrorKind::InvalidData;
|
||||
LsLocksError::io1("parsing lock information", fdinfo_path, err)
|
||||
};
|
||||
|
||||
let mut elements = line
|
||||
.split(|&b| b.is_ascii_whitespace())
|
||||
.filter(|&b| !b.is_empty());
|
||||
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
|
||||
let id = if process_id.is_none() {
|
||||
element
|
||||
.strip_suffix(b":")
|
||||
.ok_or_else(err_map)
|
||||
.and_then(|b| std::str::from_utf8(b).map_err(|_| err_map()))?
|
||||
.parse()
|
||||
.map_err(|_| err_map())?
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
let mut blocked = false;
|
||||
|
||||
let kind = loop {
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
if element != b"->" {
|
||||
break CString::new(element).map_err(|_| err_map())?;
|
||||
}
|
||||
|
||||
blocked = true;
|
||||
};
|
||||
|
||||
let mandatory = elements.next().ok_or_else(err_map)?.starts_with(b"M");
|
||||
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
let mode = CString::new(element).map_err(|_| err_map())?;
|
||||
|
||||
let mut unknown_command_name = false;
|
||||
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
|
||||
let (mut process_id, mut command_name) = if let Some(process_id) = process_id {
|
||||
(process_id, Some(CString::from(command_name)))
|
||||
} else {
|
||||
let process_id: libc::pid_t = std::str::from_utf8(element)
|
||||
.map_err(|_| err_map())?
|
||||
.parse()
|
||||
.map_err(|_| err_map())?;
|
||||
|
||||
let command_name = if process_id > 0 {
|
||||
if let Ok(cmd_line) = pid_command_name(process_id).map(Some) {
|
||||
cmd_line
|
||||
} else {
|
||||
unknown_command_name = true;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
(process_id, command_name)
|
||||
};
|
||||
|
||||
let mut iter = elements.next().ok_or_else(err_map)?.split(|&b| b == b':');
|
||||
|
||||
let major = iter
|
||||
.next()
|
||||
.ok_or_else(err_map)
|
||||
.and_then(|b| std::str::from_utf8(b).map_err(|_| err_map()))
|
||||
.and_then(|s| c_uint::from_str_radix(s, 16).map_err(|_| err_map()))?;
|
||||
let minor = iter
|
||||
.next()
|
||||
.ok_or_else(err_map)
|
||||
.and_then(|b| std::str::from_utf8(b).map_err(|_| err_map()))
|
||||
.and_then(|s| c_uint::from_str_radix(s, 16).map_err(|_| err_map()))?;
|
||||
let inode = iter
|
||||
.next()
|
||||
.ok_or_else(err_map)
|
||||
.and_then(|b| std::str::from_utf8(b).map_err(|_| err_map()))?
|
||||
.parse()
|
||||
.map_err(|_| err_map())?;
|
||||
|
||||
let device_id = libc::makedev(major, minor);
|
||||
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
|
||||
let start = if element == b"EOF" {
|
||||
0
|
||||
} else {
|
||||
std::str::from_utf8(element)
|
||||
.map_err(|_| err_map())?
|
||||
.parse()
|
||||
.map_err(|_| err_map())?
|
||||
};
|
||||
|
||||
let element = elements.next().ok_or_else(err_map)?;
|
||||
|
||||
let end = if element == b"EOF" {
|
||||
0
|
||||
} else {
|
||||
std::str::from_utf8(element)
|
||||
.map_err(|_| err_map())?
|
||||
.parse()
|
||||
.map_err(|_| err_map())?
|
||||
};
|
||||
|
||||
let range = start..end;
|
||||
|
||||
if let Some(pid_locks) = pid_locks {
|
||||
if command_name.is_none() && !blocked {
|
||||
let lock_compare = |lock: &&LockInfo| {
|
||||
lock.range == range
|
||||
&& lock.inode == inode
|
||||
&& lock.device_id == device_id
|
||||
&& lock.mandatory == mandatory
|
||||
&& lock.blocked == blocked
|
||||
&& lock.kind == kind
|
||||
&& lock.mode == mode
|
||||
};
|
||||
|
||||
if let Some(found) = pid_locks.iter().find(lock_compare) {
|
||||
process_id = found.process_id;
|
||||
command_name = found.command_name.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if command_name.is_none() {
|
||||
command_name = if unknown_command_name {
|
||||
Some(CString::from(c"(unknown)"))
|
||||
} else {
|
||||
Some(CString::from(c"(undefined)"))
|
||||
};
|
||||
}
|
||||
|
||||
let (mut path, size) = path_and_size_of_inode_opened_by_process(process_id, inode)
|
||||
.ok()
|
||||
.map_or((None, None), |(path, size)| (Some(path), Some(size)));
|
||||
|
||||
if path.is_none() {
|
||||
if no_inaccessible {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
path = fall_back_file_name(device_id).ok();
|
||||
}
|
||||
|
||||
Ok(Some(Self {
|
||||
command_name,
|
||||
process_id,
|
||||
path,
|
||||
kind,
|
||||
mode,
|
||||
range,
|
||||
inode,
|
||||
device_id,
|
||||
mandatory,
|
||||
blocked,
|
||||
size,
|
||||
file_descriptor,
|
||||
id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn fall_back_file_name(device_id: libc::dev_t) -> Result<PathBuf, LsLocksError> {
|
||||
let table = unsafe { mnt_new_table_from_file(_PATH_PROC_MOUNTINFO_C.as_ptr()) };
|
||||
if table.is_null() {
|
||||
return Err(LsLocksError::io1(
|
||||
"mnt_new_table_from_file",
|
||||
_PATH_PROC_MOUNTINFO,
|
||||
io::ErrorKind::InvalidData,
|
||||
));
|
||||
};
|
||||
|
||||
let fs = unsafe { mnt_table_find_devno(table, device_id, MNT_ITER_BACKWARD as c_int) };
|
||||
if fs.is_null() {
|
||||
let err = io::ErrorKind::NotFound;
|
||||
return Err(LsLocksError::io0("mnt_table_find_devno", err));
|
||||
};
|
||||
|
||||
let target = unsafe { mnt_fs_get_target(fs) };
|
||||
if target.is_null() {
|
||||
let err = io::ErrorKind::NotFound;
|
||||
return Err(LsLocksError::io0("mnt_fs_get_target", err));
|
||||
};
|
||||
|
||||
let target = unsafe { CStr::from_ptr(target) }
|
||||
.to_str()
|
||||
.map_err(|_| LsLocksError::io0("data is not UTF-8", io::ErrorKind::InvalidData))?;
|
||||
|
||||
Ok(PathBuf::from(format!(
|
||||
"{target}{}...",
|
||||
if target.ends_with("/") { "" } else { "/" }
|
||||
)))
|
||||
}
|
||||
@@ -15,9 +15,8 @@ fn test_column_headers() {
|
||||
let header_line = stdout.lines().next().unwrap();
|
||||
let cols: Vec<_> = header_line.split_whitespace().collect();
|
||||
|
||||
assert_eq!(cols.len(), 7);
|
||||
assert_eq!(
|
||||
cols,
|
||||
vec!["COMMAND", "PID", "TYPE", "MODE", "M", "START", "END"]
|
||||
["COMMAND", "PID", "TYPE", "SIZE", "MODE", "M", "START", "END", "PATH"]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user