Implemented lslocks.

This commit is contained in:
Koutheir Attouchi
2025-03-18 11:52:38 -04:00
parent 2882ec3305
commit c9df39b261
14 changed files with 1520 additions and 329 deletions
Generated
+90 -34
View File
@@ -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]]
+1
View File
@@ -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 }
+7 -6
View File
@@ -1,7 +1,7 @@
[package]
name = "uu_lslocks"
name = "uu_lslocks"
version = "0.0.1"
edition = "2021"
edition = "2024"
[lib]
path = "src/lslocks.rs"
@@ -11,7 +11,8 @@ name = "lslocks"
path = "src/main.rs"
[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
uucore = { workspace = true }
clap = { workspace = true }
libc = { workspace = true }
smartcols-sys = { workspace = true }
libmount-sys = { workspace = true }
+69
View 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
View 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
View 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
+5 -2
View File
@@ -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
View 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
View 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
View 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 {}
File diff suppressed because it is too large Load Diff
+137
View 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
View 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 { "/" }
)))
}
+1 -2
View File
@@ -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"]
);
}