Implemented lslocks.
This commit is contained in:
124
Cargo.lock
generated
124
Cargo.lock
generated
@@ -125,9 +125,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.17"
|
version = "1.2.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
@@ -270,9 +270,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.4.1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
@@ -381,9 +381,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.62"
|
version = "0.1.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -391,7 +391,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.52.0",
|
"windows-core 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -405,9 +405,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -457,7 +457,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
@@ -572,9 +583,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-foundation"
|
name = "objc2-core-foundation"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
@@ -679,9 +690,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.31"
|
version = "0.2.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -689,9 +700,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.94"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -848,9 +859,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -945,9 +956,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -998,7 +1009,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.5",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1008,7 +1019,7 @@ version = "0.4.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.5",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1240,8 +1251,9 @@ name = "uu_lslocks"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"libc",
|
||||||
"serde_json",
|
"libmount-sys",
|
||||||
|
"smartcols-sys",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1460,7 +1472,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1481,23 +1493,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.57.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"windows-implement 0.57.0",
|
||||||
|
"windows-interface 0.57.0",
|
||||||
|
"windows-result 0.1.2",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.57.0"
|
version = "0.61.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.60.0",
|
||||||
"windows-interface",
|
"windows-interface 0.59.1",
|
||||||
"windows-result",
|
"windows-link",
|
||||||
"windows-targets 0.52.6",
|
"windows-result 0.3.2",
|
||||||
|
"windows-strings",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1511,6 +1527,17 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.57.0"
|
version = "0.57.0"
|
||||||
@@ -1522,6 +1549,17 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1537,6 +1575,24 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
@@ -1701,7 +1757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ clap_mangen = "0.2"
|
|||||||
dns-lookup = "2.0.4"
|
dns-lookup = "2.0.4"
|
||||||
errno = "0.3"
|
errno = "0.3"
|
||||||
libc = "0.2.171"
|
libc = "0.2.171"
|
||||||
|
libmount-sys = "0.1.1"
|
||||||
linux-raw-sys = { version = "0.9.0", features = ["ioctl"] }
|
linux-raw-sys = { version = "0.9.0", features = ["ioctl"] }
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
nix = { version = "0.29", default-features = false }
|
nix = { version = "0.29", default-features = false }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uu_lslocks"
|
name = "uu_lslocks"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/lslocks.rs"
|
path = "src/lslocks.rs"
|
||||||
@@ -13,5 +13,6 @@ path = "src/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
uucore = { workspace = true }
|
uucore = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
serde = { workspace = true }
|
libc = { workspace = true }
|
||||||
serde_json = { 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
|
||||||
|
|
||||||
```
|
```
|
||||||
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
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// 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};
|
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
|
#[cfg(target_os = "linux")]
|
||||||
#[derive(Debug)]
|
use crate::column::{ColumnInfo, OutputColumns};
|
||||||
struct Lock {
|
#[cfg(target_os = "linux")]
|
||||||
_ord: usize,
|
use crate::display::{describe_holders, describe_integer, describe_size};
|
||||||
lock_type: LockType,
|
use crate::errors::LsLocksError;
|
||||||
mandatory: bool,
|
#[cfg(target_os = "linux")]
|
||||||
mode: LockMode,
|
use crate::smartcols::{Table, TableOperations};
|
||||||
pid: Option<usize>, // This value is -1 for OFD locks, hence the Option
|
#[cfg(target_os = "linux")]
|
||||||
major_minor: String,
|
use crate::utils::{
|
||||||
inode: usize,
|
_PATH_PROC, _PATH_PROC_LOCKS, BinFileLineIter, LockInfo, entry_is_dir_or_unknown,
|
||||||
start_offset: usize, // Byte offset to start of lock
|
proc_pid_command_name,
|
||||||
end_offset: Option<usize>, // None = lock does not have an explicit end offset and applies until the end of the file
|
|
||||||
}
|
|
||||||
|
|
||||||
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(());
|
|
||||||
};
|
};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use libc::pid_t;
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
type pid_t = i32;
|
||||||
|
|
||||||
// This field has a format of MAJOR:MINOR:INODE
|
mod options {
|
||||||
let major_minor_inode: Vec<_> = parts.next().unwrap().split(":").collect();
|
pub static BYTES: &str = "bytes";
|
||||||
assert_eq!(major_minor_inode.len(), 3);
|
pub static JSON: &str = "json";
|
||||||
let major_minor = [major_minor_inode[0], major_minor_inode[1]].join(":");
|
pub static LIST_COLUMNS: &str = "list-columns";
|
||||||
let inode = major_minor_inode[2].parse::<usize>().unwrap();
|
pub static LIST: &str = "list";
|
||||||
|
pub static NO_HEADINGS: &str = "noheadings";
|
||||||
let start_offset = parts.next().unwrap().parse::<usize>().unwrap();
|
pub static NO_INACCESSIBLE: &str = "noinaccessible";
|
||||||
let end_offset: Option<usize> = parts.next().and_then(|offset_str| match offset_str {
|
pub static NO_TRUNCATE: &str = "notruncate";
|
||||||
"EOF" => None,
|
pub static OUTPUT_ALL: &str = "output-all";
|
||||||
other => other.parse::<usize>().ok(),
|
pub static OUTPUT: &str = "output";
|
||||||
});
|
pub static PID: &str = "pid";
|
||||||
|
pub static RAW: &str = "raw";
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("lslocks.md");
|
const ABOUT: &str = help_about!("lslocks.md");
|
||||||
@@ -303,4 +69,447 @@ pub fn uu_app() -> Command {
|
|||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.infer_long_args(true)
|
.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 header_line = stdout.lines().next().unwrap();
|
||||||
let cols: Vec<_> = header_line.split_whitespace().collect();
|
let cols: Vec<_> = header_line.split_whitespace().collect();
|
||||||
|
|
||||||
assert_eq!(cols.len(), 7);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cols,
|
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