diff --git a/src/uu/lslocks/lslocks.md b/src/uu/lslocks/lslocks.md new file mode 100644 index 0000000..8982646 --- /dev/null +++ b/src/uu/lslocks/lslocks.md @@ -0,0 +1,7 @@ +# lslocks + +``` +lslocks [OPTION]... +``` + +lists system locks diff --git a/src/uu/lslocks/src/lslocks.rs b/src/uu/lslocks/src/lslocks.rs index a2e35a3..63b03b9 100644 --- a/src/uu/lslocks/src/lslocks.rs +++ b/src/uu/lslocks/src/lslocks.rs @@ -3,18 +3,18 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{fs, str::FromStr}; +use std::{fmt, fs, str::FromStr}; use clap::{crate_version, Command}; -use uucore::error::UResult; +use uucore::{error::UResult, format_usage, help_about, help_usage}; // See https://www.man7.org/linux/man-pages/man5/proc_locks.5.html for details on each field's meaning #[derive(Debug)] struct Lock { - ord: usize, + _ord: usize, lock_type: LockType, - strictness: Strictness, - variant: Variant, + mandatory: bool, + mode: LockMode, pid: Option, // This value is -1 for OFD locks, hence the Option major_minor: String, inode: usize, @@ -22,6 +22,39 @@ struct Lock { end_offset: Option, // 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("".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::MajMin => 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 { @@ -38,13 +71,17 @@ impl FromStr for Lock { .next() .and_then(|part| LockType::from_str(part).ok()) .unwrap(); - let strictness = parts + let mandatory = parts .next() - .and_then(|part| Strictness::from_str(part).ok()) + .map(|part| match part { + "MANDATORY" => true, + "ADVISORY" => false, + _ => panic!("Unrecognized value in lock line: {}", part), + }) .unwrap(); - let variant = parts + let mode = parts .next() - .and_then(|part| Variant::from_str(part).ok()) + .and_then(|part| LockMode::from_str(part).ok()) .unwrap(); let pid: Option = parts.next().and_then(|pid_str| match pid_str { "-1" => None, @@ -69,10 +106,10 @@ impl FromStr for Lock { }); Ok(Self { - ord, + _ord: ord, lock_type, - strictness, - variant, + mandatory, + mode, pid, major_minor, inode, @@ -83,6 +120,7 @@ impl FromStr for Lock { } #[derive(Debug, PartialEq, Eq)] +#[allow(clippy::upper_case_acronyms)] enum LockType { FLOCK, // BSD file lock OFDLCK, // Open file descriptor @@ -101,30 +139,23 @@ impl FromStr for LockType { } } -#[derive(Debug)] -enum Strictness { - Advisory, - Mandatory, -} - -impl FromStr for Strictness { - type Err = (); - fn from_str(input: &str) -> Result { - match input { - "ADVISORY" => Ok(Self::Advisory), - "MANDATORY" => Ok(Self::Mandatory), - _ => 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 Variant { +enum LockMode { Read, Write, } -impl FromStr for Variant { +impl FromStr for LockMode { type Err = (); fn from_str(input: &str) -> Result { match input { @@ -135,8 +166,122 @@ impl FromStr for Variant { } } +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, + MajMin, + 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::MajMin => "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 { + 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//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, +} + +fn print_output(locks: Vec, 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!("{: = output_opts + .cols + .iter() + .enumerate() + .map(|(i, col)| format!("{: UResult<()> { + let output_opts = OutputOptions { + cols: Vec::from(DEFAULT_COLS), + }; + let locks: Vec<_> = match fs::read_to_string("/proc/locks") { Ok(content) => content .lines() @@ -145,16 +290,17 @@ pub fn uumain(_args: impl uucore::Args) -> UResult<()> { Err(e) => panic!("Could not read /proc/locks: {}", e), }; - for lock in locks { - println!("{:?}", lock); - } + print_output(locks, output_opts); Ok(()) } +const ABOUT: &str = help_about!("lslocks.md"); +const USAGE: &str = help_usage!("lslocks.md"); + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) - //.about(ABOUT) - //.override_usage(format_usage(USAGE)) + .about(ABOUT) + .override_usage(format_usage(USAGE)) .infer_long_args(true) }