The following program prints information about users who are currently logged in.
#!/usr/bin/env python
import argparse
import sys
import time
from pathlib import Path
from cstruct import NATIVE_ORDER, MemCStruct, getdef, parse
DEFAULT_FILENAME = "/var/run/utmp"
parse(
"""
/* Values for ut_type field, below */
#define EMPTY 0 /* Record does not contain valid info
(formerly known as UT_UNKNOWN on Linux) */
#define RUN_LVL 1 /* Change in system run-level (see
init(1)) */
#define BOOT_TIME 2 /* Time of system boot (in ut_tv) */
#define NEW_TIME 3 /* Time after system clock change
(in ut_tv) */
#define OLD_TIME 4 /* Time before system clock change
(in ut_tv) */
#define INIT_PROCESS 5 /* Process spawned by init(1) */
#define LOGIN_PROCESS 6 /* Session leader process for user login */
#define USER_PROCESS 7 /* Normal process */
#define DEAD_PROCESS 8 /* Terminated process */
#define ACCOUNTING 9 /* Not implemented */
#define UT_LINESIZE 32
#define UT_NAMESIZE 32
#define UT_HOSTSIZE 256
typedef int pid_t;
typedef long time_t;
"""
)
class ExitStatus(MemCStruct):
__def__ = """
struct ExitStatus {
short e_termination; /* Process termination status. */
short e_exit; /* Process exit status. */
}
"""
class Timeval(MemCStruct):
__def__ = """
struct {
int32_t tv_sec; /* Seconds. */
int32_t tv_usec; /* Microseconds. */
}
"""
def str_from_c(string):
return string.decode().split("\0")[0]
class Utmp(MemCStruct):
__byte_order__ = NATIVE_ORDER
__def__ = """
typedef struct ExitStatus ExitStatus;
struct {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix, or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or kernel version for run-level messages */
ExitStatus ut_exit; /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init (1 */
int32_t ut_session; /* Session ID (getsid(2)), used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
int32_t ut_addr_v6[4]; /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
}
"""
@property
def user(self):
return str_from_c(self.ut_user)
@property
def line(self):
return str_from_c(self.ut_line)
@property
def time(self):
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.ut_tv.tv_sec))
@property
def host(self):
if str_from_c(self.ut_host):
host = str_from_c(self.ut_host)
return f"({host})"
elif self.ut_id:
ut_id = str_from_c(self.ut_id)
return f"id={ut_id}"
else:
return ""
def __str__(self):
return f"{self.user:<10s} {self.line:<12s} {self.time:<15s} {self.ut_pid:>15} {self.host:<8s}"
def print_info(self, show_all):
if show_all or self.ut_type in (getdef('LOGIN_PROCESS'), getdef('USER_PROCESS')):
print(self)
def main():
parser = argparse.ArgumentParser(description="Print information about users who are currently logged in.")
parser.add_argument("-a", "--all", action="store_true", dest="show_all", help="show all enties")
parser.add_argument("file", nargs="?", help="if FILE is not specified use /var/run/utmp", default=DEFAULT_FILENAME)
args = parser.parse_args()
utmp = Utmp()
try:
with Path(args.file).open("rb") as f:
while utmp.unpack(f):
utmp.print_info(args.show_all)
except (IOError, OSError) as ex:
print(ex)
sys.exit(1)
if __name__ == "__main__":
main()