6

I have a system I'm managing (running RHEL 8) that has multiple users in our small office, who log into it in various ways -- locally at the console, remotely via SSH and NoMachine Workstation. When I do updates that include a new kernel, or for some other reason I need to reboot the machine, I'd like to make sure there are no currently logged-in users, so I'm not interrupting users who are running software on the machine.

So what I'd like to have is a command that lists all currently logged in users.

I've done quite a bit of searching on this topic, and the methods I've found in response to this question are simply wrong, in the sense that they provably do not work.

The commands who, w, and users do NOT list all logged-in users. As I'm writing this there are three users currently logged in to the computer in question, including myself. These commands list only one of these three (incidentally, I'm not one of them). The one user who is listed by these commands is logged in via SSH and has an open terminal. Another user who has no TTY but has several GUI applications open with their X displays piped to his laptop through SSH does not appear, and neither do I (I have a graphical login via NoMachine). In fact, who -m returns no output when I run it. I can use ps -ef to list all processes and find processes currently running for all these users.

The command last | grep 'still logged in' (suggested here) results in the same incomplete list as above (it's getting its information from the same source).

So, repeating the question -- what is the definitive method to get a list of all logged in users (users who have authenticated via the normal mechanisms, and have interactive processes currently running under their user IDs) ? I'd like to do this without searching through the output of ps.

EDIT -- The users on this workstation are all authenticating via LDAP, however I've verified that this is not related to the question. I have created a local user account, which also does not show up in response to who or users when logged in by the same means as described above.

  • I know you said you don't want to use ps (hence a comment instead of an answer) but would something like ps -aux | tail -n+2 | cut -d' ' -f1 | sort -u work? Of course, that also includes system users running non-interactive processes, but we can work on that if you think this is a promising avenue to pursue. – terdon Nov 02 '20 at 16:26
  • List LDAP users on linux client? suggests you need to make some changes so that LDAP users are included in the output of getent. – terdon Nov 02 '20 at 17:23
  • It's an interesting question, but I can't help notice the collision between "users ... who have interactive processes currently running under their user IDs" and "without searching through the output of ps". Since ps is the tool to query active processes, could you clarify why ps is not allowed? – Jeff Schaller Nov 02 '20 at 17:50
  • Your requirement of "not using ps" could be easily bypassed by getting the information directly from /proc (from the same place ps gets it) grep Uid: /proc/*/status, etc ;-) Keep in mind that a user id is simply a number, and any privileged process can just setuid(some_uid), without it having to go through pam/utmp, and without some_uid having to exist in any database, or have attached to it a name or a home directory. The only way to get a list of all active uids is to look through all the processes running on that machine. –  Nov 02 '20 at 22:31
  • Also, there's no way to know if a process is "interactive" -- which is a nebulous concept. Just like having "authenticated" via "normal" mechanisms. –  Nov 02 '20 at 22:34

2 Answers2

7

Given the context of wanting to reboot a Linux system, I would take a multi-pronged approach.

First, disable future logins by creating an /etc/nologin file. You could leave it blank or enter informative text in there, such as:

"Logins to this system have been temporarily disabled in preparation for a server reboot, scheduled for (time and date). Please try again after (expected end time)."

Don't forget to remove /etc/nologin when you're done!

Additionally, since a reboot will clear all processes, whether they're interactive or not, I would use ps to look for processes owned by users. This will take some manual investigation to determine whether the processes are worth keeping or not, but should narrow the field some. I've hard-coded 1000 here as the value of UID_MIN from /etc/login.defs as the cutoff for "system" vs "user" UIDs. If any of your users have UIDs below 1000, you'll need to adjust that number.

ps -eo pid,uid,args | awk '$2 >= 1000'

Of course, you could adjust the ps columns to taste, perhaps to add the translated username and process start time (ps -eo pid,uid,user,start,args) or others -- just be careful to keep the ps UID and awk field in sync with each other.

To get the list of unique user names, use:

ps -eo user,uid | awk 'NR>1 && $2 >= 1000 && ++seen[$2]==1{print $1}'
terdon
  • 242,166
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • 2
    This is the closest I may get, though I think I'd follow this in reverse order, in practice. The final command line is what I think I'd generally run first to quickly check for other users, and then if there are any, I'd check processes, set the nologin, and contact users. – Chris Robison Nov 03 '20 at 14:33
3

I know you said you don't want to go through the output of ps, but what if you could automate it?

$ join <(ps -aux | tail -n+2 | cut -d' ' -f1 | sort -u)  \
       <(getent passwd | grep -f <(grep '^/' /etc/shells) | cut -d: -f1 | sort)
root
terdon

That might look a little complicated, but here's a breakdown:

ps -aux | tail -n+2 | cut -d' ' -f1 | sort -u : this runs ps showing the processes of all users, passes the output through tail -n+2 which will print all lines after the 1st so we filter out the ps header, then uses cut to print out the user name and the list of user names is passed through sort -u to get a sorted, deduplicated list.

getent passwd | grep -f <(grep '^/' /etc/shells) | cut -d: -f1 | sort: here, we select all lines in /etc/shells that start with a slash (this should give us the list of valid login shells), and use that as input for a grep which searches the output of getent passwod (whcih should include LDAP users) for known users with that shell. The result of the above should be a list of users with real shells (as opposed to /usr/bin/nologin or /bin/false etc.).

Finally, the output of both of the commands is given as input to join which results in only printing those users who are both in the output of ps and in the list of users with valid login shells.

If this works for you, you can make it into an alias by adding this to your shell's initialization file (~/.bashrc if you're running bash):

alias getUsers="join <(ps -aux | tail -n+2 | cut -d' ' -f1 | sort -u) <(getent passwd | grep -f <(grep '^/' /etc/shells) | cut -d: -f1 | sort)"

This is not perfect, it might still find some system users. For example, on my system I have git with:

$ getent passwd | grep git
git:x:996:996:git daemon user:/:/bin/bash

I don't really know when that user would be shown as logged in though. Presumably when running some git commands? In any case, while far from perfect this might serve at least as a temporary workaround.

terdon
  • 242,166
  • Thanks for digging into this. Pasting your command line verbatim, I get none of the three currently logged-in users. The only line of output is 'root'. Not even the one user who is logged in normally via SSH, whom I can still see via who. – Chris Robison Nov 02 '20 at 17:02
  • After thinking about it for a moment... what's probably causing the problem is that all interactive users on this machine are authenticated via LDAP, so they're not in /etc/passwd. – Chris Robison Nov 02 '20 at 17:06
  • @ChrisRobison argh! That probably explains why they're not included in the who output either. Please [edit] your question and add that you're using LDAP. I am afraid I only know LDAP as "that annoying thing that makes me need to talk to my sysadmin" so I can't help, but there must be some sort of LDAP-aware version of who, right? Alternatively, do you know of an /etc/passwd equivalent for LDAP? – terdon Nov 02 '20 at 17:09
  • Actually, @ChrisRobison, try the updated answer. getent passwd might be what we're looking for. I just confirmed on a machine with LDAP and the output seems complete. – terdon Nov 02 '20 at 17:14
  • But that's not the case. who does list one of the users, and it will list my own user ID as well if I also log in via SSH. – Chris Robison Nov 02 '20 at 17:15
  • Running getent passwd here, I only get local users, no LDAP users are listed. – Chris Robison Nov 02 '20 at 17:16
  • @ChrisRobison ah. Then I'm stumped, sorry. – terdon Nov 02 '20 at 17:17
  • I really appreciate the effort. For anyone else interested, I've verified that who also fails to list a local user ID that I've just created, when logged in graphically in the same way that I am. However, your ps-based solution does list that local user. – Chris Robison Nov 02 '20 at 17:23