Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 2 additions & 32 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -739,22 +739,7 @@ jobs:
# selinux and systemd headers needed to build tests
sudo apt-get -y update
sudo apt-get -y install libselinux1-dev libsystemd-dev
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
# To work around these issues for pinky (and who) tests, we create a fake utmp file with a
# system boot entry and a login entry for the GH runner account.
FAKE_UTMP_2='[2] [00000] [~~ ] [reboot] [~ ] [6.0.0-test] [0.0.0.0] [2022-02-22T22:11:22,222222+00:00]'
FAKE_UTMP_7='[7] [999999] [tty2] [runner] [tty2] [ ] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]'
(echo "$FAKE_UTMP_2" ; echo "$FAKE_UTMP_7") | sudo utmpdump -r -o /var/run/utmp
# ... and add a full name to each account with a gecos field but no full name.
sudo sed -i 's/:,/:runner name,/' /etc/passwd
# We also create a couple optional files pinky looks for
touch /home/runner/.project
echo "foo" > /home/runner/.plan
# add user with digital username for testing with issue #7787
echo 200:x:2000:2000::/home/200:/bin/bash | sudo tee -a /etc/passwd
echo 200:x:2000: | sudo tee -a /etc/group
bash util/setup-gh-runner-utmp.sh
;;
esac
- uses: taiki-e/install-action@v2
Expand Down Expand Up @@ -1102,22 +1087,7 @@ jobs:
# selinux and systemd headers needed to build tests
sudo apt-get -y update
sudo apt-get -y install libselinux1-dev libsystemd-dev
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
# To work around these issues for pinky (and who) tests, we create a fake utmp file with a
# system boot entry and a login entry for the GH runner account.
FAKE_UTMP_2='[2] [00000] [~~ ] [reboot] [~ ] [6.0.0-test] [0.0.0.0] [2022-02-22T22:11:22,222222+00:00]'
FAKE_UTMP_7='[7] [999999] [tty2] [runner] [tty2] [ ] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]'
(echo "$FAKE_UTMP_2" ; echo "$FAKE_UTMP_7") | sudo utmpdump -r -o /var/run/utmp
# ... and add a full name to each account with a gecos field but no full name.
sudo sed -i 's/:,/:runner name,/' /etc/passwd
# We also create a couple optional files pinky looks for
touch /home/runner/.project
echo "foo" > /home/runner/.plan
# add user with digital username for testing with issue #7787
echo 200:x:2000:2000::/home/200:/bin/bash | sudo tee -a /etc/passwd
echo 200:x:2000: | sudo tee -a /etc/group
bash util/setup-gh-runner-utmp.sh
;;
esac

Expand Down
2 changes: 1 addition & 1 deletion src/uu/users/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ path = "src/users.rs"

[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = ["utmpx"] }
uucore = { workspace = true, features = ["utmpx", "process"] }
fluent = { workspace = true }

[target.'cfg(target_os = "openbsd")'.dependencies]
Expand Down
7 changes: 5 additions & 2 deletions src/uu/users/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (paths) wtmp
// spell-checker:ignore (paths) wtmp ESRCH

use std::ffi::OsString;
use std::path::Path;
Expand Down Expand Up @@ -61,12 +61,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
}
};
// OpenBSD uses the older UTMP format (not UTMPX) which doesn't reliably track PIDs,
// so we only filter by pid_is_alive on non-OpenBSD systems.
#[cfg(not(target_os = "openbsd"))]
{
use uucore::process::pid_is_alive;
let filename = maybe_file.unwrap_or(utmpx::DEFAULT_FILE.as_ref());

users = Utmpx::iter_all_records_from(filename)
.filter(|ut| ut.is_user_process())
.filter(|ut| ut.is_user_process() && pid_is_alive(ut.pid()))
.map(|ut| ut.user())
.collect::<Vec<_>>();
};
Expand Down
2 changes: 1 addition & 1 deletion src/uu/who/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ path = "src/who.rs"

[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = ["utmpx"] }
uucore = { workspace = true, features = ["utmpx", "process"] }
fluent = { workspace = true }

[[bin]]
Expand Down
5 changes: 1 addition & 4 deletions src/uu/who/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ who-help-users = list users logged in
who-help-mesg = add user's message status as +, - or ?

# Output messages
who-user-count = # { $count ->
[one] user={ $count }
*[other] users={ $count }
}
who-user-count = # users={ $count }

# Idle time indicators
who-idle-current = .
Expand Down
5 changes: 1 addition & 4 deletions src/uu/who/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ who-help-users = liste les utilisateurs connectés
who-help-mesg = ajoute le statut de message de l'utilisateur comme +, - ou ?

# Output messages
who-user-count = # { $count ->
[one] utilisateur={ $count }
*[other] utilisateurs={ $count }
}
who-user-count = # utilisateurs={ $count }

# Idle time indicators
who-idle-old = anc.
Expand Down
7 changes: 4 additions & 3 deletions src/uu/who/src/platform/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr pidstr exitstr hoststr
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr pidstr exitstr hoststr ESRCH

use crate::options;
use crate::uu_app;

use uucore::display::Quotable;
use uucore::error::{FromIo, UResult};
use uucore::libc::{S_IWGRP, STDIN_FILENO, ttyname};
use uucore::process::pid_is_alive;
use uucore::translate;

use uucore::utmpx::{self, UtmpxRecord, time};
Expand Down Expand Up @@ -210,7 +211,7 @@ impl Who {
};
if self.short_list {
let users = utmpx::Utmpx::iter_all_records_from(f)
.filter(|ut| ut.is_user_process())
.filter(|ut| ut.is_user_process() && pid_is_alive(ut.pid()))
.map(|ut| ut.user())
.collect::<Vec<_>>();
println!("{}", users.join(" "));
Expand All @@ -229,7 +230,7 @@ impl Who {

for ut in records {
if !self.my_line_only || cur_tty == ut.tty_device() {
if self.need_users && ut.is_user_process() {
if self.need_users && ut.is_user_process() && pid_is_alive(ut.pid()) {
self.print_user(&ut)?;
} else {
match ut.record_type() {
Expand Down
29 changes: 28 additions & 1 deletion src/uucore/src/lib/features/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
// spell-checker:ignore pgrep pwait snice getpgrp

use libc::{gid_t, pid_t, uid_t};
#[cfg(not(target_os = "redox"))]
use nix::errno::Errno;
#[cfg(not(target_os = "openbsd"))]
use nix::sys::signal::kill;
#[cfg(not(target_os = "openbsd"))]
use nix::unistd::Pid;
use std::io;
use std::process::Child;
use std::process::ExitStatus;
Expand Down Expand Up @@ -51,6 +54,30 @@ pub fn getpid() -> pid_t {
unsafe { libc::getpid() }
}

/// Check if a process with the given PID is alive.
///
/// Uses `kill(pid, 0)` which sends signal 0 (null signal) to check process existence
/// without actually sending a signal. This is a standard POSIX technique for checking
/// if a process exists.
///
/// Returns `true` if:
/// - The process exists (kill returns 0)
/// - We lack permission to signal the process (errno != ESRCH)
/// This means the process exists but we can't signal it
///
/// Returns `false` only if:
/// - errno is ESRCH (No such process), confirming the process doesn't exist
///
/// PIDs <= 0 are considered alive for compatibility with utmp records that may
/// contain special or invalid PID values.
#[cfg(not(target_os = "openbsd"))]
pub fn pid_is_alive(pid: i32) -> bool {
if pid <= 0 {
return true;
}
kill(Pid::from_raw(pid), None).is_ok() || Errno::last() != Errno::ESRCH
}

/// `getsid()` returns the session ID of the process with process ID pid.
///
/// If pid is 0, getsid() returns the session ID of the calling process.
Expand Down
18 changes: 4 additions & 14 deletions tests/by-util/test_users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// file that was distributed with this source code.
use uutests::new_ucmd;
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
use uutests::{util::TestScenario, util_name};
use uutests::{unwrap_or_return, util::TestScenario, util::expected_result, util_name};

#[test]
fn test_invalid_arg() {
Expand All @@ -18,20 +18,10 @@ fn test_users_no_arg() {

#[test]
#[cfg(any(target_vendor = "apple", target_os = "linux"))]
#[ignore = "issue #3219"]
fn test_users_check_name() {
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(target_vendor = "apple")]
let util_name = &format!("g{}", util_name!());

let expected = TestScenario::new(util_name)
.cmd(util_name)
.env("LC_ALL", "C")
.succeeds()
.stdout_move_str();

new_ucmd!().succeeds().stdout_is(&expected);
let ts = TestScenario::new(util_name!());
let expected_stdout = unwrap_or_return!(expected_result(&ts, &[])).stdout_move_str();
ts.ucmd().succeeds().stdout_is(expected_stdout);
}

#[test]
Expand Down
10 changes: 1 addition & 9 deletions tests/by-util/test_who.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ fn test_invalid_arg() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_count() {
let ts = TestScenario::new(util_name!());
for opt in ["-q", "--count", "--c"] {
Expand All @@ -42,7 +41,6 @@ fn test_boot() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_heading() {
let ts = TestScenario::new(util_name!());
for opt in ["-H", "--heading", "--head"] {
Expand All @@ -61,7 +59,6 @@ fn test_heading() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_short() {
let ts = TestScenario::new(util_name!());
for opt in ["-s", "--short", "--s"] {
Expand Down Expand Up @@ -128,7 +125,6 @@ fn test_time() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_mesg() {
// -T, -w, --mesg
// add user's message status as +, - or ?
Expand Down Expand Up @@ -172,7 +168,6 @@ fn test_too_many_args() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_users() {
let ts = TestScenario::new(util_name!());
for opt in ["-u", "--users", "--us"] {
Expand All @@ -198,7 +193,6 @@ fn test_users() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_lookup() {
let opt = "--lookup";
let ts = TestScenario::new(util_name!());
Expand All @@ -219,7 +213,6 @@ fn test_dead() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_all_separately() {
if cfg!(target_os = "macos") {
// TODO: fix `-u`, see: test_users
Expand All @@ -237,7 +230,6 @@ fn test_all_separately() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
fn test_all() {
if cfg!(target_os = "macos") {
// TODO: fix `-u`, see: test_users
Expand All @@ -253,7 +245,7 @@ fn test_all() {

#[cfg(unix)]
#[test]
#[ignore = "issue #3219"]
#[ignore = "This test is the only one in the suite that checks if the reference version is 8.3. This has not been the case for a while"]
fn test_locale() {
let ts = TestScenario::new(util_name!());

Expand Down
20 changes: 20 additions & 0 deletions util/setup-gh-runner-utmp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# spell-checker:ignore utmpdump gecos
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
# To work around these issues for pinky (and who) tests, we create a fake utmp file with a
# system boot entry and a login entry for the GH runner account.
printf '%s\n%s\n%s' \
'[2] [00000] [~~ ] [reboot ] [~ ] [6.0.0-test ] [0.0.0.0 ] [2022-02-22T22:11:22,222222+0000]' \
'[7] [999999] [tty2] [runner ] [tty2 ] [ ] [0.0.0.0 ] [2022-02-22T22:22:22,222222+0000]' \
'[7] [00001] [tty3] [runner ] [tty3 ] [ ] [0.0.0.0 ] [2022-02-22T22:22:22,222222+0000]' \
| sudo utmpdump -r -o /var/run/utmp
# ... and add a full name to each account with a gecos field but no full name.
sudo sed -i 's/:,/:runner name,/' /etc/passwd
# We also create a couple optional files pinky looks for
touch /home/runner/.project
echo "foo" > /home/runner/.plan
# add user with digital username for testing with issue #7787
echo 200:x:2000:2000::/home/200:/bin/bash | sudo tee -a /etc/passwd
echo 200:x:2000: | sudo tee -a /etc/group
Loading