lslocks: Print output in a table format similar to the original
				
					
				
			This commit is contained in:
		
							
								
								
									
										7
									
								
								src/uu/lslocks/lslocks.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/uu/lslocks/lslocks.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| # lslocks | ||||
|  | ||||
| ``` | ||||
| lslocks [OPTION]... | ||||
| ``` | ||||
|  | ||||
| lists system locks | ||||
| @@ -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<usize>, // This value is -1 for OFD locks, hence the Option | ||||
|     major_minor: String, | ||||
|     inode: usize, | ||||
| @@ -22,6 +22,39 @@ struct Lock { | ||||
|     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::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<Self, Self::Err> { | ||||
| @@ -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<usize> = 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<Self, Self::Err> { | ||||
|         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<Self, Self::Err> { | ||||
|         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<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() | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 alxndrv
					alxndrv