mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-09-17 20:26:18 +00:00
pinky: tool unsupported on OpenBSD
- utmpx not supported on OpenBSD - add src/uu/pinky/src/platform directory and platform/mod.rs for conditional compilation according to target_os - platform/openbsd.rs: implementation on OpenBSD (unsupported tool) - platform/unix.rs: implementation on other OS - src/uu/pinky/src/pinky.rs: use platform module for uucore::main function
This commit is contained in:
parent
e6d12732f5
commit
d3e6e7a947
4 changed files with 327 additions and 281 deletions
|
@ -5,21 +5,11 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf
|
// spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf
|
||||||
|
|
||||||
use uucore::entries::{Locate, Passwd};
|
|
||||||
use uucore::error::{FromIo, UResult};
|
|
||||||
use uucore::libc::S_IWGRP;
|
|
||||||
use uucore::utmpx::{self, time, Utmpx};
|
|
||||||
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::io::BufReader;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use std::path::PathBuf;
|
|
||||||
use uucore::{format_usage, help_about, help_usage};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
|
mod platform;
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("pinky.md");
|
const ABOUT: &str = help_about!("pinky.md");
|
||||||
const USAGE: &str = help_usage!("pinky.md");
|
const USAGE: &str = help_usage!("pinky.md");
|
||||||
|
|
||||||
|
@ -37,88 +27,8 @@ mod options {
|
||||||
pub const HELP: &str = "help";
|
pub const HELP: &str = "help";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_long_usage() -> String {
|
|
||||||
format!(
|
|
||||||
"A lightweight 'finger' program; print user information.\n\
|
|
||||||
The utmp file will be {}.",
|
|
||||||
utmpx::DEFAULT_FILE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
use platform::uumain;
|
||||||
let args = args.collect_ignore();
|
|
||||||
|
|
||||||
let matches = uu_app()
|
|
||||||
.after_help(get_long_usage())
|
|
||||||
.try_get_matches_from(args)?;
|
|
||||||
|
|
||||||
let users: Vec<String> = matches
|
|
||||||
.get_many::<String>(options::USER)
|
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// If true, display the hours:minutes since each user has touched
|
|
||||||
// the keyboard, or blank if within the last minute, or days followed
|
|
||||||
// by a 'd' if not within the last day.
|
|
||||||
let mut include_idle = true;
|
|
||||||
|
|
||||||
// If true, display a line at the top describing each field.
|
|
||||||
let include_heading = !matches.get_flag(options::OMIT_HEADINGS);
|
|
||||||
|
|
||||||
// if true, display the user's full name from pw_gecos.
|
|
||||||
let mut include_fullname = true;
|
|
||||||
|
|
||||||
// if true, display the user's ~/.project file when doing long format.
|
|
||||||
let include_project = !matches.get_flag(options::OMIT_PROJECT_FILE);
|
|
||||||
|
|
||||||
// if true, display the user's ~/.plan file when doing long format.
|
|
||||||
let include_plan = !matches.get_flag(options::OMIT_PLAN_FILE);
|
|
||||||
|
|
||||||
// if true, display the user's home directory and shell
|
|
||||||
// when doing long format.
|
|
||||||
let include_home_and_shell = !matches.get_flag(options::OMIT_HOME_DIR);
|
|
||||||
|
|
||||||
// if true, use the "short" output format.
|
|
||||||
let do_short_format = !matches.get_flag(options::LONG_FORMAT);
|
|
||||||
|
|
||||||
/* if true, display the ut_host field. */
|
|
||||||
let mut include_where = true;
|
|
||||||
|
|
||||||
if matches.get_flag(options::OMIT_NAME) {
|
|
||||||
include_fullname = false;
|
|
||||||
}
|
|
||||||
if matches.get_flag(options::OMIT_NAME_HOST) {
|
|
||||||
include_fullname = false;
|
|
||||||
include_where = false;
|
|
||||||
}
|
|
||||||
if matches.get_flag(options::OMIT_NAME_HOST_TIME) {
|
|
||||||
include_fullname = false;
|
|
||||||
include_idle = false;
|
|
||||||
include_where = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pk = Pinky {
|
|
||||||
include_idle,
|
|
||||||
include_heading,
|
|
||||||
include_fullname,
|
|
||||||
include_project,
|
|
||||||
include_plan,
|
|
||||||
include_home_and_shell,
|
|
||||||
include_where,
|
|
||||||
names: users,
|
|
||||||
};
|
|
||||||
|
|
||||||
if do_short_format {
|
|
||||||
match pk.short_pinky() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => Err(e.map_err_context(String::new)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pk.long_pinky();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
|
@ -197,17 +107,6 @@ pub fn uu_app() -> Command {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Pinky {
|
|
||||||
include_idle: bool,
|
|
||||||
include_heading: bool,
|
|
||||||
include_fullname: bool,
|
|
||||||
include_project: bool,
|
|
||||||
include_plan: bool,
|
|
||||||
include_where: bool,
|
|
||||||
include_home_and_shell: bool,
|
|
||||||
names: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Capitalize {
|
pub trait Capitalize {
|
||||||
fn capitalize(&self) -> String;
|
fn capitalize(&self) -> String;
|
||||||
}
|
}
|
||||||
|
@ -225,180 +124,3 @@ impl Capitalize for str {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idle_string(when: i64) -> String {
|
|
||||||
thread_local! {
|
|
||||||
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
|
||||||
}
|
|
||||||
NOW.with(|n| {
|
|
||||||
let duration = n.unix_timestamp() - when;
|
|
||||||
if duration < 60 {
|
|
||||||
// less than 1min
|
|
||||||
" ".to_owned()
|
|
||||||
} else if duration < 24 * 3600 {
|
|
||||||
// less than 1day
|
|
||||||
let hours = duration / (60 * 60);
|
|
||||||
let minutes = (duration % (60 * 60)) / 60;
|
|
||||||
format!("{hours:02}:{minutes:02}")
|
|
||||||
} else {
|
|
||||||
// more than 1day
|
|
||||||
let days = duration / (24 * 3600);
|
|
||||||
format!("{days}d")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn time_string(ut: &Utmpx) -> String {
|
|
||||||
// "%b %e %H:%M"
|
|
||||||
let time_format: Vec<time::format_description::FormatItem> =
|
|
||||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
|
||||||
.unwrap();
|
|
||||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
|
||||||
let mut gecos = if let Some(gecos) = &pw.user_info {
|
|
||||||
gecos.clone()
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
if let Some(n) = gecos.find(',') {
|
|
||||||
gecos.truncate(n);
|
|
||||||
}
|
|
||||||
Some(gecos.replace('&', &pw.name.capitalize()))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pinky {
|
|
||||||
fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> {
|
|
||||||
let mut pts_path = PathBuf::from("/dev");
|
|
||||||
pts_path.push(ut.tty_device().as_str());
|
|
||||||
|
|
||||||
let mesg;
|
|
||||||
let last_change;
|
|
||||||
|
|
||||||
match pts_path.metadata() {
|
|
||||||
#[allow(clippy::unnecessary_cast)]
|
|
||||||
Ok(meta) => {
|
|
||||||
mesg = if meta.mode() & S_IWGRP as u32 == 0 {
|
|
||||||
'*'
|
|
||||||
} else {
|
|
||||||
' '
|
|
||||||
};
|
|
||||||
last_change = meta.atime();
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
mesg = '?';
|
|
||||||
last_change = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
|
||||||
|
|
||||||
if self.include_fullname {
|
|
||||||
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
|
||||||
gecos_to_fullname(&pw)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(fullname) = fullname {
|
|
||||||
print!(" {fullname:<19.19}");
|
|
||||||
} else {
|
|
||||||
print!(" {:19}", " ???");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, ut.tty_device());
|
|
||||||
|
|
||||||
if self.include_idle {
|
|
||||||
if last_change == 0 {
|
|
||||||
print!(" {:<6}", "?????");
|
|
||||||
} else {
|
|
||||||
print!(" {:<6}", idle_string(last_change));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print!(" {}", time_string(ut));
|
|
||||||
|
|
||||||
let mut s = ut.host();
|
|
||||||
if self.include_where && !s.is_empty() {
|
|
||||||
s = ut.canon_host()?;
|
|
||||||
print!(" {s}");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_heading(&self) {
|
|
||||||
print!("{:<8}", "Login");
|
|
||||||
if self.include_fullname {
|
|
||||||
print!(" {:<19}", "Name");
|
|
||||||
}
|
|
||||||
print!(" {:<9}", " TTY");
|
|
||||||
if self.include_idle {
|
|
||||||
print!(" {:<6}", "Idle");
|
|
||||||
}
|
|
||||||
print!(" {:<16}", "When");
|
|
||||||
if self.include_where {
|
|
||||||
print!(" Where");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn short_pinky(&self) -> std::io::Result<()> {
|
|
||||||
if self.include_heading {
|
|
||||||
self.print_heading();
|
|
||||||
}
|
|
||||||
for ut in Utmpx::iter_all_records() {
|
|
||||||
if ut.is_user_process()
|
|
||||||
&& (self.names.is_empty() || self.names.iter().any(|n| n.as_str() == ut.user()))
|
|
||||||
{
|
|
||||||
self.print_entry(&ut)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn long_pinky(&self) {
|
|
||||||
for u in &self.names {
|
|
||||||
print!("Login name: {u:<28}In real life: ");
|
|
||||||
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
|
||||||
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
|
|
||||||
let user_dir = pw.user_dir.unwrap_or_default();
|
|
||||||
let user_shell = pw.user_shell.unwrap_or_default();
|
|
||||||
println!(" {fullname}");
|
|
||||||
if self.include_home_and_shell {
|
|
||||||
print!("Directory: {user_dir:<29}");
|
|
||||||
println!("Shell: {user_shell}");
|
|
||||||
}
|
|
||||||
if self.include_project {
|
|
||||||
let mut p = PathBuf::from(&user_dir);
|
|
||||||
p.push(".project");
|
|
||||||
if let Ok(f) = File::open(p) {
|
|
||||||
print!("Project: ");
|
|
||||||
read_to_console(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.include_plan {
|
|
||||||
let mut p = PathBuf::from(&user_dir);
|
|
||||||
p.push(".plan");
|
|
||||||
if let Ok(f) = File::open(p) {
|
|
||||||
println!("Plan:");
|
|
||||||
read_to_console(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
} else {
|
|
||||||
println!(" ???");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_to_console<F: Read>(f: F) {
|
|
||||||
let mut reader = BufReader::new(f);
|
|
||||||
let mut iobuf = Vec::new();
|
|
||||||
if reader.read_to_end(&mut iobuf).is_ok() {
|
|
||||||
print!("{}", String::from_utf8_lossy(&iobuf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
14
src/uu/pinky/src/platform/mod.rs
Normal file
14
src/uu/pinky/src/platform/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
mod unix;
|
||||||
|
#[cfg(not(target_os = "openbsd"))]
|
||||||
|
pub use self::unix::*;
|
||||||
|
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
mod openbsd;
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
pub use self::openbsd::*;
|
17
src/uu/pinky/src/platform/openbsd.rs
Normal file
17
src/uu/pinky/src/platform/openbsd.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
// Specific implementation for OpenBSD: tool unsupported (utmpx not supported)
|
||||||
|
|
||||||
|
use crate::uu_app;
|
||||||
|
|
||||||
|
use uucore::error::UResult;
|
||||||
|
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
let _matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
|
||||||
|
println!("unsupported command on OpenBSD");
|
||||||
|
Ok(())
|
||||||
|
}
|
293
src/uu/pinky/src/platform/unix.rs
Normal file
293
src/uu/pinky/src/platform/unix.rs
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE
|
||||||
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore (ToDO) BUFSIZE gecos fullname, mesg iobuf
|
||||||
|
|
||||||
|
use crate::options;
|
||||||
|
use crate::uu_app;
|
||||||
|
use crate::Capitalize;
|
||||||
|
|
||||||
|
use uucore::entries::{Locate, Passwd};
|
||||||
|
use uucore::error::{FromIo, UResult};
|
||||||
|
use uucore::libc::S_IWGRP;
|
||||||
|
use uucore::utmpx::{self, time, Utmpx};
|
||||||
|
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn get_long_usage() -> String {
|
||||||
|
format!(
|
||||||
|
"A lightweight 'finger' program; print user information.\n\
|
||||||
|
The utmp file will be {}.",
|
||||||
|
utmpx::DEFAULT_FILE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
let args = args.collect_ignore();
|
||||||
|
|
||||||
|
let matches = uu_app()
|
||||||
|
.after_help(get_long_usage())
|
||||||
|
.try_get_matches_from(args)?;
|
||||||
|
|
||||||
|
let users: Vec<String> = matches
|
||||||
|
.get_many::<String>(options::USER)
|
||||||
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// If true, display the hours:minutes since each user has touched
|
||||||
|
// the keyboard, or blank if within the last minute, or days followed
|
||||||
|
// by a 'd' if not within the last day.
|
||||||
|
let mut include_idle = true;
|
||||||
|
|
||||||
|
// If true, display a line at the top describing each field.
|
||||||
|
let include_heading = !matches.get_flag(options::OMIT_HEADINGS);
|
||||||
|
|
||||||
|
// if true, display the user's full name from pw_gecos.
|
||||||
|
let mut include_fullname = true;
|
||||||
|
|
||||||
|
// if true, display the user's ~/.project file when doing long format.
|
||||||
|
let include_project = !matches.get_flag(options::OMIT_PROJECT_FILE);
|
||||||
|
|
||||||
|
// if true, display the user's ~/.plan file when doing long format.
|
||||||
|
let include_plan = !matches.get_flag(options::OMIT_PLAN_FILE);
|
||||||
|
|
||||||
|
// if true, display the user's home directory and shell
|
||||||
|
// when doing long format.
|
||||||
|
let include_home_and_shell = !matches.get_flag(options::OMIT_HOME_DIR);
|
||||||
|
|
||||||
|
// if true, use the "short" output format.
|
||||||
|
let do_short_format = !matches.get_flag(options::LONG_FORMAT);
|
||||||
|
|
||||||
|
/* if true, display the ut_host field. */
|
||||||
|
let mut include_where = true;
|
||||||
|
|
||||||
|
if matches.get_flag(options::OMIT_NAME) {
|
||||||
|
include_fullname = false;
|
||||||
|
}
|
||||||
|
if matches.get_flag(options::OMIT_NAME_HOST) {
|
||||||
|
include_fullname = false;
|
||||||
|
include_where = false;
|
||||||
|
}
|
||||||
|
if matches.get_flag(options::OMIT_NAME_HOST_TIME) {
|
||||||
|
include_fullname = false;
|
||||||
|
include_idle = false;
|
||||||
|
include_where = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pk = Pinky {
|
||||||
|
include_idle,
|
||||||
|
include_heading,
|
||||||
|
include_fullname,
|
||||||
|
include_project,
|
||||||
|
include_plan,
|
||||||
|
include_home_and_shell,
|
||||||
|
include_where,
|
||||||
|
names: users,
|
||||||
|
};
|
||||||
|
|
||||||
|
if do_short_format {
|
||||||
|
match pk.short_pinky() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e.map_err_context(String::new)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pk.long_pinky();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pinky {
|
||||||
|
include_idle: bool,
|
||||||
|
include_heading: bool,
|
||||||
|
include_fullname: bool,
|
||||||
|
include_project: bool,
|
||||||
|
include_plan: bool,
|
||||||
|
include_where: bool,
|
||||||
|
include_home_and_shell: bool,
|
||||||
|
names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idle_string(when: i64) -> String {
|
||||||
|
thread_local! {
|
||||||
|
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||||
|
}
|
||||||
|
NOW.with(|n| {
|
||||||
|
let duration = n.unix_timestamp() - when;
|
||||||
|
if duration < 60 {
|
||||||
|
// less than 1min
|
||||||
|
" ".to_owned()
|
||||||
|
} else if duration < 24 * 3600 {
|
||||||
|
// less than 1day
|
||||||
|
let hours = duration / (60 * 60);
|
||||||
|
let minutes = (duration % (60 * 60)) / 60;
|
||||||
|
format!("{hours:02}:{minutes:02}")
|
||||||
|
} else {
|
||||||
|
// more than 1day
|
||||||
|
let days = duration / (24 * 3600);
|
||||||
|
format!("{days}d")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_string(ut: &Utmpx) -> String {
|
||||||
|
// "%b %e %H:%M"
|
||||||
|
let time_format: Vec<time::format_description::FormatItem> =
|
||||||
|
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||||
|
.unwrap();
|
||||||
|
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
||||||
|
let mut gecos = if let Some(gecos) = &pw.user_info {
|
||||||
|
gecos.clone()
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if let Some(n) = gecos.find(',') {
|
||||||
|
gecos.truncate(n);
|
||||||
|
}
|
||||||
|
Some(gecos.replace('&', &pw.name.capitalize()))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pinky {
|
||||||
|
fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> {
|
||||||
|
let mut pts_path = PathBuf::from("/dev");
|
||||||
|
pts_path.push(ut.tty_device().as_str());
|
||||||
|
|
||||||
|
let mesg;
|
||||||
|
let last_change;
|
||||||
|
|
||||||
|
match pts_path.metadata() {
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
Ok(meta) => {
|
||||||
|
mesg = if meta.mode() & S_IWGRP as u32 == 0 {
|
||||||
|
'*'
|
||||||
|
} else {
|
||||||
|
' '
|
||||||
|
};
|
||||||
|
last_change = meta.atime();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
mesg = '?';
|
||||||
|
last_change = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user());
|
||||||
|
|
||||||
|
if self.include_fullname {
|
||||||
|
let fullname = if let Ok(pw) = Passwd::locate(ut.user().as_ref()) {
|
||||||
|
gecos_to_fullname(&pw)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(fullname) = fullname {
|
||||||
|
print!(" {fullname:<19.19}");
|
||||||
|
} else {
|
||||||
|
print!(" {:19}", " ???");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print!(" {}{:<8.*}", mesg, utmpx::UT_LINESIZE, ut.tty_device());
|
||||||
|
|
||||||
|
if self.include_idle {
|
||||||
|
if last_change == 0 {
|
||||||
|
print!(" {:<6}", "?????");
|
||||||
|
} else {
|
||||||
|
print!(" {:<6}", idle_string(last_change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print!(" {}", time_string(ut));
|
||||||
|
|
||||||
|
let mut s = ut.host();
|
||||||
|
if self.include_where && !s.is_empty() {
|
||||||
|
s = ut.canon_host()?;
|
||||||
|
print!(" {s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_heading(&self) {
|
||||||
|
print!("{:<8}", "Login");
|
||||||
|
if self.include_fullname {
|
||||||
|
print!(" {:<19}", "Name");
|
||||||
|
}
|
||||||
|
print!(" {:<9}", " TTY");
|
||||||
|
if self.include_idle {
|
||||||
|
print!(" {:<6}", "Idle");
|
||||||
|
}
|
||||||
|
print!(" {:<16}", "When");
|
||||||
|
if self.include_where {
|
||||||
|
print!(" Where");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_pinky(&self) -> std::io::Result<()> {
|
||||||
|
if self.include_heading {
|
||||||
|
self.print_heading();
|
||||||
|
}
|
||||||
|
for ut in Utmpx::iter_all_records() {
|
||||||
|
if ut.is_user_process()
|
||||||
|
&& (self.names.is_empty() || self.names.iter().any(|n| n.as_str() == ut.user()))
|
||||||
|
{
|
||||||
|
self.print_entry(&ut)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn long_pinky(&self) {
|
||||||
|
for u in &self.names {
|
||||||
|
print!("Login name: {u:<28}In real life: ");
|
||||||
|
if let Ok(pw) = Passwd::locate(u.as_str()) {
|
||||||
|
let fullname = gecos_to_fullname(&pw).unwrap_or_default();
|
||||||
|
let user_dir = pw.user_dir.unwrap_or_default();
|
||||||
|
let user_shell = pw.user_shell.unwrap_or_default();
|
||||||
|
println!(" {fullname}");
|
||||||
|
if self.include_home_and_shell {
|
||||||
|
print!("Directory: {user_dir:<29}");
|
||||||
|
println!("Shell: {user_shell}");
|
||||||
|
}
|
||||||
|
if self.include_project {
|
||||||
|
let mut p = PathBuf::from(&user_dir);
|
||||||
|
p.push(".project");
|
||||||
|
if let Ok(f) = File::open(p) {
|
||||||
|
print!("Project: ");
|
||||||
|
read_to_console(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.include_plan {
|
||||||
|
let mut p = PathBuf::from(&user_dir);
|
||||||
|
p.push(".plan");
|
||||||
|
if let Ok(f) = File::open(p) {
|
||||||
|
println!("Plan:");
|
||||||
|
read_to_console(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
|
println!(" ???");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_to_console<F: Read>(f: F) {
|
||||||
|
let mut reader = BufReader::new(f);
|
||||||
|
let mut iobuf = Vec::new();
|
||||||
|
if reader.read_to_end(&mut iobuf).is_ok() {
|
||||||
|
print!("{}", String::from_utf8_lossy(&iobuf));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue