/
niceSOFT
/
procps
Обзор
Документация
Войти
/
niceSOFT
/
procps
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/w.c
1 219 строк
35 KB
Barry Brown
Fix TTY detection and idle time reporting
07 июл 2026, 14:15
07 июл 2026, 14:15
1e08a09
Код
Авторство
О чём код?
/* * w - show what logged in users are doing. * * Copyright © 2009-2026 Craig Small <csmall@dropbear.xyz> * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi> * Copyright © 2002-2006 Albert Cahalan * Copyright © 1996 Charles Blake * * Rewritten, older version: * Copyright © 1993 Larry Greenfield * with some fixes by Michael K. Johnson. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <limits.h> #include <locale.h> #include <pwd.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <termios.h> #include <time.h> #include <unistd.h> #ifdef HAVE_UTMPX_H #include <utmpx.h> #ifndef HAVE_UT_HOSTSIZE_IN_UTMPX #include <utmp.h> #endif #else # include <utmp.h> #endif #include <arpa/inet.h> #ifdef WITH_SYSTEMD # include <systemd/sd-login.h> # include <systemd/sd-daemon.h> #endif #ifdef WITH_ELOGIND # include <elogind/sd-login.h> # include <elogind/sd-daemon.h> #endif #include "c.h" #include "fileutils.h" #include "nls.h" #include "misc.h" #include "pids.h" #include "stat.h" static int ignoreuser = 0; /* for '-u' */ static int oldstyle = 0; /* for '-o' */ #ifdef HAVE_UTMPX_H typedef struct utmpx utmp_t; #else typedef struct utmp utmp_t; #endif #ifdef __GLIBC__ #if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE) # define UT_HOSTSIZE __UT_HOSTSIZE # define UT_LINESIZE __UT_LINESIZE # define UT_NAMESIZE __UT_NAMESIZE #endif #endif #ifdef W_SHOWFROM # define FROM_STRING "on" #else # define FROM_STRING "off" #endif #define MAX_CMD_WIDTH 512 #define MIN_CMD_WIDTH 7 /* Must match items in cache_pids */ enum rel_items { EU_PID, EU_PPID, EU_TGID, EU_START, EU_EUID, EU_RUID, EU_TPGID, EU_PGRP, EU_TTY, EU_TTY_NAME, EU_TICS_ALL, EU_CMDLINE}; /* * This routine is careful since some programs leave utmp strings * unprintable. Always outputs at least 16 chars padded with * spaces on the right if necessary. */ static void print_host(const char *restrict host, int len, const int fromlen) { const char *last; int width = 0; if (len > fromlen) len = fromlen; last = host + len; for (; host < last; host++) { if (*host == '\0') break; if (isprint(*host) && *host != ' ') { fputc(*host, stdout); ++width; } else { fputc('-', stdout); ++width; break; } } /* * space-fill, and a '-' too if needed to ensure the * column exists */ if (!width) { fputc('-', stdout); ++width; } while (width++ < fromlen) fputc(' ', stdout); } /* This routine prints the display part of the host or IPv6 link address interface */ static void print_display_or_interface(const char *restrict host, int len, int restlen) { const char *const end = host + (len > 0 ? len : 0); const char *disp, *tmp; if (restlen <= 0) return; /* not enough space for printing anything */ /* search for a collon (might be a display) */ disp = host; while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++; /* colon found */ if (disp < end && *disp == ':') { /* detect multiple colons -> IPv6 in the host (not a display) */ tmp = disp+1; while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++; if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */ /* number of chars till the end of the input field */ len -= (disp - host); /* if it is still longer than the rest of the output field, then cut it */ if (len > restlen) len = restlen; /* print the display */ while ((len > 0) && isprint(*disp) && (*disp != ' ')) { len--; restlen--; fputc(*disp, stdout); disp++; } if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */ restlen--; fputc('-', stdout); } } else { /* multiple colons found - it's an IPv6 address */ /* search for % (interface separator in case of IPv6 link address) */ while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++; if (tmp < end && *tmp == '%') { /* interface separator found */ /* number of chars till the end of the input field */ len -= (tmp - host); /* if it is still longer than the rest of the output field, then cut it */ if (len > restlen) len = restlen; /* print the interface */ while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) { len--; restlen--; fputc(*tmp, stdout); tmp++; } if ((len > 0) && (*tmp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */ restlen--; fputc('-', stdout); } } } } /* padding with spaces */ while (restlen > 0) { fputc(' ', stdout); restlen--; } } /* This routine prints either the hostname or the IP address of the remote */ static void print_from( const char *session, const utmp_t *restrict const u, const int ip_addresses, const int fromlen) { #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (session) { char *host = NULL; int r; r = sd_session_get_remote_host(session, &host); if (r < 0 || host == NULL) print_host("", 0, fromlen); else { print_host(host, strlen(host), fromlen); free(host); } } else { #endif char buf[fromlen + 1]; char buf_ipv6[INET6_ADDRSTRLEN]; int len; if (!u) { /* Both systemd session and utmp not available */ print_host("", 0, fromlen); return; } #ifndef __CYGWIN__ int32_t ut_addr_v6[4]; /* IP address of the remote host */ if (ip_addresses) { /* -i switch used */ memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6)); if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) { /* map back */ ut_addr_v6[0] = ut_addr_v6[3]; ut_addr_v6[1] = 0; ut_addr_v6[2] = 0; ut_addr_v6[3] = 0; } if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) { /* IPv6 */ if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) { strcpy(buf, ""); /* invalid address, clean the buffer */ } else { strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */ } } else { /* IPv4 */ if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) { strcpy(buf, ""); /* invalid address, clean the buffer */ } } buf[fromlen] = '\0'; len = strlen(buf); if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */ fputs(buf, stdout); /* show the display part of the host or IPv6 link addr. interface, if present */ print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len); } else { /* IP address is empty, print the host instead */ print_host(u->ut_host, UT_HOSTSIZE, fromlen); } } else { /* -i switch NOT used */ print_host(u->ut_host, UT_HOSTSIZE, fromlen); } #else print_host(u->ut_host, UT_HOSTSIZE, fromlen); #endif #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) } #endif } /* compact 7 char format for time intervals (belongs in libproc?) */ static void print_time_ival7(time_t t, int centi_sec, FILE * fout) { if ((long)t < (long)0) { /* system clock changed? */ printf(" ? "); return; } if (oldstyle) { if (t >= 48 * 60 * 60) /* > 2 days */ fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60)); else if (t >= 60 * 60) /* > 1 hour */ /* Translation Hint: Hours:Minutes */ fprintf(fout, " %2llu:%02u ", (unsigned long long)t / (60 * 60), (unsigned)((t / 60) % 60)); else if (t > 60) /* > 1 minute */ /* Translation Hint: Minutes:Seconds */ fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / 60, (unsigned)t % 60); else fprintf(fout, " "); } else { if (t >= 48 * 60 * 60) /* 2 days or more */ fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60)); else if (t >= 60 * 60) /* 1 hour or more */ /* Translation Hint: Hours:Minutes */ fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / (60 * 60), (unsigned)((t / 60) % 60)); else if (t > 60) /* 1 minute or more */ /* Translation Hint: Minutes:Seconds */ fprintf(fout, " %2llu:%02u ", (unsigned long long)t / 60, (unsigned)t % 60); else /* Translation Hint: Seconds:Centiseconds */ fprintf(fout, _(" %2llu.%02us"), (unsigned long long)t, centi_sec); } } /* stat the device file to get an idle time */ static time_t idletime(const char *restrict const tty) { char *ttypath=NULL; struct stat sbuf; if (asprintf(&ttypath, "/dev/%s", tty) < 0) return 0; if (stat(ttypath, &sbuf) != 0) { free(ttypath); return 0; } free(ttypath); return time(NULL) - (sbuf.st_atime > sbuf.st_mtime ? sbuf.st_atime : sbuf.st_mtime); } /* 7 character formatted login time */ static void print_logintime(time_t logt, FILE * fout) { /* Abbreviated of weekday can be longer than 3 characters, * see for instance hu_HU. Using 16 is few bytes more than * enough. */ char time_str[16]; time_t curt; struct tm *logtm, *curtm; int today; curt = time(NULL); curtm = localtime(&curt); /* localtime returns a pointer to static memory */ today = curtm->tm_yday; logtm = localtime(&logt); if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) { if (curt - logt > 6 * 24 * 60 * 60) { strftime(time_str, sizeof(time_str), "%b", logtm); fprintf(fout, " %02d%3s%02d", logtm->tm_mday, time_str, logtm->tm_year % 100); } else { strftime(time_str, sizeof(time_str), "%a", logtm); fprintf(fout, " %3s%02d ", time_str, logtm->tm_hour); } } else { fprintf(fout, " %02d:%02d ", logtm->tm_hour, logtm->tm_min); } } /* * Get the Device ID of the given TTY */ static int get_tty_device(const char *restrict const name) { struct stat st; static char buf[32]; char *dev_paths[] = { "/dev/%s", "/dev/tty%s", "/dev/pts/%s", NULL}; int i; if (name[0] == '/' && stat(name, &st) == 0) return st.st_rdev; for (i=0; dev_paths[i] != NULL; i++) { snprintf(buf, 32, dev_paths[i], name); if (stat(buf, &st) == 0 && (st.st_mode & S_IFMT) == S_IFCHR) return st.st_rdev; } return -1; } static struct pids_fetch *cache_pids(struct pids_info **info) { struct pids_fetch *reap; *info = NULL; enum pids_item items[] = { PIDS_ID_PID, PIDS_ID_PPID, PIDS_ID_TGID, PIDS_TICS_BEGAN, PIDS_ID_EUID, PIDS_ID_RUID, PIDS_ID_TPGID, PIDS_ID_PGRP, PIDS_TTY, PIDS_TTY_NAME, PIDS_TICS_ALL, PIDS_CMDLINE}; #define ITEMS_COUNT (sizeof items / sizeof *items) if (procps_pids_new(info, items, ITEMS_COUNT) < 0) errx(EXIT_FAILURE, _("Unable to create pid info structure")); if ((reap = procps_pids_reap(*info, PIDS_FETCH_TASKS_ONLY)) == NULL) errx(EXIT_FAILURE, _("Unable to load process information")); return reap; } /* * This function scans the process table accumulating total cpu * times for any processes "associated" with this login session. * It also searches for the "best" process to report as "(w)hat" * the user for that login session is doing currently. This the * essential core of 'w'. */ static int find_best_proc( #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) const char *session, #endif const utmp_t * restrict const u, const char *restrict const tty, unsigned long long *restrict const jcpu, unsigned long long *restrict const pcpu, char *cmdline, pid_t *pid, struct pids_fetch *reap) { #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i]) #define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, reap->stacks[i]) #define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, reap->stacks[i]) #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i]) unsigned uid = ~0U; pid_t ut_pid = -1; int found_utpid = 0; int i, total_procs, line; unsigned long long best_time = 0; unsigned long long secondbest_time = 0; *jcpu = 0; *pcpu = 0; if (!ignoreuser) { #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (session) { if (sd_session_get_uid(session, &uid) < 0) return 0; } else { #endif char buf[UT_NAMESIZE + 1]; struct passwd *passwd_data; strncpy(buf, u->ut_user, UT_NAMESIZE); buf[UT_NAMESIZE] = '\0'; if ((passwd_data = getpwnam(buf)) == NULL) return 0; uid = passwd_data->pw_uid; /* OK to have passwd_data go out of scope here */ #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) } #endif } line = get_tty_device(tty); total_procs = reap->counts->total; if (u) ut_pid = u->ut_pid; #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) else sd_session_get_leader(session, &ut_pid); #endif for (i=0; i < total_procs; i++) { /* is this the login process? */ if (PIDS_GETINT(TGID) == ut_pid) { found_utpid = 1; if (!best_time) { best_time = PIDS_GETULL(START); strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); *pid = PIDS_GETULL(PID); *pcpu = PIDS_GETULL(TICS_ALL); } } if (PIDS_GETINT(TTY) != line) continue; (*jcpu) += PIDS_VAL(EU_TICS_ALL, ull_int, reap->stacks[i]); if (!(secondbest_time && PIDS_GETULL(START) <= secondbest_time)) { secondbest_time = PIDS_GETULL(START); if (cmdline[0] == '-' && cmdline[1] == '\0') { strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); *pid = PIDS_GETULL(PID); *pcpu = PIDS_GETULL(TICS_ALL); } } if ( (!ignoreuser && uid != PIDS_GETUNT(EUID) && uid != PIDS_GETUNT(RUID)) || (PIDS_GETINT(PGRP) != PIDS_GETINT(TPGID)) || (PIDS_GETULL(START) <= best_time) ) continue; best_time = PIDS_GETULL(START); strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); *pid = PIDS_GETULL(PID); *pcpu = PIDS_GETULL(TICS_ALL); } return found_utpid; #undef PIDS_GETINT #undef PIDS_GETUNT #undef PIDS_GETULL #undef PIDS_GETSTR } static void show_uptime( int container) { double uptime_secs=0; char buf[100]; if ( (getenv("PROCPS_CONTAINER") != NULL) || container) { if (procps_container_uptime(&uptime_secs) < 0) err(EXIT_FAILURE, _("Cannot get container uptime")); } else { if (procps_uptime(&uptime_secs, NULL) < 0) err(EXIT_FAILURE, _("Cannot get system uptime")); } if (procps_uptime_snprint(buf, 100, uptime_secs, 0) < 0) err(EXIT_FAILURE, _("Cannot format uptime")); printf("%s\n", buf); } /* * Try to get the TTY of the session using various means */ #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i]) #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i]) #define PIDS_GETINT2(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[j]) #define PIDS_GETSTR2(e) PIDS_VAL(EU_ ## e, str, reap->stacks[j]) static void get_session_tty( char *tty, const char *session, utmp_t *u, struct pids_fetch *reap) { int i, j, total_procs; pid_t leader_pid=-1; /* First method - use systemd */ #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (session) { char *sd_tty; if (sd_session_get_tty(session, &sd_tty) >= 0) { for (i = 0; i < UT_LINESIZE; i++) { if (sd_tty[i] == '\0') break; /* clean up tty if garbled */ if (isalnum(sd_tty[i]) || (sd_tty[i] == '/')) tty[i] = sd_tty[i]; else tty[i] = '\0'; } free(sd_tty); return; /* found tty via systemd */ } sd_session_get_leader(session, &leader_pid); } #endif /* Second method - use utmp */ if (u) { for (i = 0; i < UT_LINESIZE; i++) { if (u->ut_line[i] == '\0') break; /* clean up tty if garbled */ if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/')) tty[i] = u->ut_line[i]; else tty[i] = '\0'; } tty[i] = '\0'; if (tty[0] != '\0') return; /* found via utmp */ if (leader_pid == -1) leader_pid = u->ut_pid; } /* Third method - scan processes to find the tty, this is at most two down * from the session leader */ if (leader_pid == -1) return; total_procs = reap->counts->total; for (i=0; i < total_procs; i++) { if (PIDS_GETINT(PPID) != leader_pid) continue; if (PIDS_GETINT(TTY) != 0) { strncpy(tty, PIDS_GETSTR(TTY_NAME), UT_NAMESIZE); return; /* found via top scan */ } for (j=i; j < total_procs; j++) { if (PIDS_GETINT2(PPID) != PIDS_GETINT(PID)) continue; if (PIDS_GETINT2(TTY) != 0) { strncpy(tty, PIDS_GETSTR2(TTY_NAME), UT_NAMESIZE); return; /* found via second scan */ } } } } #undef PIDS_GETINT #undef PIDS_GETSTR #undef PIDS_GETINT2 #undef PIDS_GETSTR2 static void showinfo( const char *session, const char *name, utmp_t * u, const int longform, int maxcmd, int from, const int userlen, const int fromlen, const int ip_addresses, const int pids, struct pids_fetch *reap) { unsigned long long jcpu, pcpu; char uname[UT_NAMESIZE + 1] = "", tty[UT_LINESIZE + 1] = ""; long hertz; char cmdline[MAX_CMD_WIDTH + 1]; pid_t best_pid = -1; int pids_length = 0; strcpy(cmdline, "-"); hertz = procps_hertz_get(); get_session_tty(tty, session, u, reap); if (find_best_proc( #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) session, #endif u, tty, &jcpu, &pcpu, cmdline, &best_pid, reap) == 0) /* * just skip if stale utmp entry (i.e. login proc doesn't * exist). If there is a desire a cmdline flag could be * added to optionally show it with a prefix of (stale) * in front of cmd or something like that. */ return; if (name) strncpy(uname, name, UT_NAMESIZE); /* force NUL term for printf */ uname[UT_NAMESIZE] = '\0'; printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, tty); if (from) print_from(session, u, ip_addresses, fromlen); /* login time */ if (longform) { #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (session) { uint64_t ltime; sd_session_get_start_time(session, <ime); print_logintime(ltime/((uint64_t) 1000000ULL), stdout); } else { #endif #ifdef HAVE_UTMPX_H print_logintime(u->ut_tv.tv_sec, stdout); #else print_logintime(u->ut_time, stdout); #endif #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) } #endif } /* idle */ if (u && *u->ut_line == ':') /* idle unknown for xdm logins */ printf(" ?xdm? "); else if (tty[0]) print_time_ival7(idletime(tty), 0, stdout); else printf(" "); /* jpcpu/pcpu */ if (longform) { print_time_ival7(jcpu / hertz, (jcpu % hertz) * (100. / hertz), stdout); if (pcpu > 0) print_time_ival7(pcpu / hertz, (pcpu % hertz) * (100. / hertz), stdout); else printf(" ? "); } /* what */ if (pids) { pid_t ut_pid = -1; if (u) ut_pid = u->ut_pid; #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) else sd_session_get_leader(session, &ut_pid); #endif pids_length = printf(" %d/%d", ut_pid, best_pid); if (pids_length > maxcmd) { maxcmd = 0; } else if (pids_length > 0) { maxcmd -= pids_length; } } printf(" %.*s\n", maxcmd, cmdline); } static void __attribute__ ((__noreturn__)) usage(FILE * out) { fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options] [user]\n"), program_invocation_short_name); fputs(USAGE_OPTIONS, out); fputs(_(" -c, --container show container uptime\n"),out); fputs(_(" -h, --no-header do not print header\n"),out); fputs(_(" -u, --no-current ignore current process username\n"),out); fputs(_(" -s, --short short format\n"),out); fputs(_(" -t, --terminal show terminals\n"),out); fprintf(out, _(" -f, --from toggle remote hostname field (default: %s)\n"), FROM_STRING); fputs(_(" -o, --old-style old style output\n"),out); fputs(_(" -i, --ip-addr display IP address instead of hostname (if possible)\n"), out); fputs(_(" -p, --pids show the PID(s) of processes in WHAT\n"), out); fputs(USAGE_SEPARATOR, out); fputs(_(" --help display this help and exit\n"), out); fputs(USAGE_VERSION, out); fprintf(out, USAGE_MAN_TAIL("w(1)")); exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); } /* * print_logintime expects a time of seconds since epoch * libproc returns a process start time in tics since * boot. This function converts the start time to * seconds since epoch */ time_t get_starttime( unsigned long long proc_start, long hertz) { static unsigned long long boot_time = 0; enum stat_item stat_items[] = { STAT_SYS_TIME_OF_BOOT }; if (boot_time == 0) { struct stat_info *stat_info = NULL; struct stat_stack *stat_stack; if (procps_stat_new(&stat_info) < 0) errx(EXIT_FAILURE, _("Unable to create system stat structure")); if (!(stat_stack = procps_stat_select(stat_info, stat_items, 1))) errx(EXIT_FAILURE, _("Unable to select stat information")); boot_time = STAT_VAL(0, ul_int, stat_stack); } return proc_start / hertz + boot_time; } void print_terminal_user( const int longform, int maxcmd, const int from, const int userlen, const int fromlen, const int ip_addresses, const int show_pids, const unsigned long long first_start, const pid_t first_pid, const pid_t last_pid, const char *ttyname, const unsigned long long jcpu, const unsigned long long pcpu, const char *cmdline) { long hertz; char *session = NULL; char *username = NULL; char ttypath[5 + UT_LINESIZE + 1] = "/dev/"; char uname[UT_NAMESIZE + 1] = "?"; int pids_length = 0; utmp_t *u = NULL; // unnecessary, just avoid -Wmaybe-uninitialized hertz = procps_hertz_get(); strncpy(ttypath + 5, ttyname, UT_LINESIZE); #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (sd_pid_get_session(last_pid, &session) >= 0) { if ( sd_session_get_username(session, &username) >= 0) { strncpy(uname, username, UT_NAMESIZE); uname[UT_NAMESIZE] = '\0'; free(username); } } else { // We get the utmp entry for this tty device utmp_t search; strncpy(search.ut_line, ttyname, UT_NAMESIZE-1); #ifdef HAVE_UTMPX_H setutxent(); u = getutxline(&search); endutxent(); #else utmpname(UTMP_FILE); setutent(); u = getutline(&search); endutent(); #endif } #endif // SYSTEMD // Find username of device if (uname[0] == '?') { struct stat st; struct passwd *pw; if (stat(ttypath, &st) == 0) { if ((pw = getpwuid(st.st_uid)) != NULL) strncpy(uname, pw->pw_name, UT_NAMESIZE); else snprintf(uname, UT_NAMESIZE, "%d", st.st_uid); } } printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, ttyname); if (from) print_from(session, u, ip_addresses, fromlen); /* login time */ if (longform) { #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) if (session) { uint64_t ltime; sd_session_get_start_time(session, <ime); print_logintime(ltime/((uint64_t) 1000000ULL), stdout); } else { #endif // Different to main w as we use process start time rather than unreliable utmp print_logintime(get_starttime(first_start, hertz), stdout); #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) } #endif } print_time_ival7(idletime(ttyname), 0, stdout); /* jpcpu/pcpu */ if (longform) { print_time_ival7(jcpu / hertz, (jcpu % hertz) * (100. / hertz), stdout); if (pcpu > 0) print_time_ival7(pcpu / hertz, (pcpu % hertz) * (100. / hertz), stdout); else printf(" ? "); } if (show_pids) { pid_t ut_pid = -1; #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) sd_session_get_leader(session, &ut_pid); #endif if (ut_pid == -1 && u != NULL) ut_pid = u->ut_pid; pids_length = printf(" %6d/%6d", ut_pid, last_pid); if (pids_length > maxcmd) { maxcmd = 0; } else if (pids_length > 0) { maxcmd -= pids_length; } } /* what */ printf(" %.*s\n", maxcmd, cmdline); free(session); (void)first_pid; } /* * Instead of going through the systemd sessions or utmp entries * scan all processes and find those that have TTY defined, skipping * over the gettys which have PID 0/1 as parent */ #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i]) #define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, reap->stacks[i]) #define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, reap->stacks[i]) #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i]) void print_user_terminals( const int longform, int maxcmd, const int from, const int userlen, const int fromlen, const int ip_addresses, const int show_pids, struct pids_info *pids_info, struct pids_fetch *reap) { int i; int total_procs = reap->counts->total; int current_tty = -1; pid_t first_pid; unsigned long long last_start = 0; unsigned long long first_start = 0; pid_t last_pid; char cmdline[MAX_CMD_WIDTH+1]; char ttyname[UT_NAMESIZE+1]; unsigned long long jcpu = 0; unsigned long long pcpu = 0; if (!procps_pids_sort(pids_info, reap->stacks, total_procs, PIDS_TTY, PIDS_SORT_ASCEND)) errx(EXIT_FAILURE, _("Unable to sort processes by TTY")); for (i=0; i < total_procs; i++) { /* Skip if: * The process has no TTY * PPID is 0 or 1 which is a getty */ if (PIDS_GETINT(TTY) == 0 || PIDS_GETINT(PPID) == 1 || PIDS_GETINT(PPID) == 0) continue; if (current_tty == PIDS_GETINT(TTY)) { if (last_start == 0 || last_start < PIDS_GETULL(START)) { last_pid = PIDS_GETINT(TGID); last_start = PIDS_GETULL(START); strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); } jcpu += (PIDS_GETULL(TICS_ALL)); } else { // Changed TTY if (current_tty != -1) { // We have data print_terminal_user(longform, maxcmd, from, userlen, fromlen, ip_addresses, show_pids, first_start, first_pid, last_pid, ttyname, jcpu, pcpu, cmdline); } // Reset and get ready for next round current_tty = PIDS_GETINT(TTY); first_pid = PIDS_GETINT(TGID); first_start = PIDS_GETULL(START); strncpy(ttyname, PIDS_GETSTR(TTY_NAME), UT_NAMESIZE); pcpu = PIDS_GETULL(TICS_ALL); jcpu = (PIDS_GETULL(TICS_ALL)); /* With one process, first is last */ last_pid = PIDS_GETINT(TGID); last_start = PIDS_GETULL(START); strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH); } } if (current_tty != -1) { print_terminal_user(longform, maxcmd, from, userlen, fromlen, ip_addresses, show_pids, first_start, first_pid, last_pid, ttyname, jcpu, pcpu, cmdline); } } #undef PIDS_GETINT #undef PIDS_GETUNT #undef PIDS_GETULL #undef PIDS_GETSTR int main(int argc, char **argv) { char *match_user = NULL, *p; utmp_t *u; struct winsize win; int ch; int maxcmd = 80; int userlen = 8; int fromlen = 16; char *env_var; /* switches (defaults) */ int container = 0; int header = 1; int longform = 1; int from = 1; int ip_addresses = 0; int pids = 0; bool term_mode = false; struct pids_info *info = NULL; struct pids_fetch *pids_cache = NULL; enum { HELP_OPTION = CHAR_MAX + 1 }; static const struct option longopts[] = { {"container", no_argument, NULL, 'c'}, {"no-header", no_argument, NULL, 'h'}, {"no-current", no_argument, NULL, 'u'}, {"short", no_argument, NULL, 's'}, {"terminal", no_argument, NULL, 't'}, {"from", no_argument, NULL, 'f'}, {"old-style", no_argument, NULL, 'o'}, {"ip-addr", no_argument, NULL, 'i'}, {"pids", no_argument, NULL, 'p'}, {"help", no_argument, NULL, HELP_OPTION}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; #ifdef HAVE_PROGRAM_INVOCATION_NAME program_invocation_name = program_invocation_short_name; #endif setlocale (LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); atexit(close_stdout); #ifndef W_SHOWFROM from = 0; #endif while ((ch = getopt_long(argc, argv, "chustfoVip", longopts, NULL)) != -1) switch (ch) { case 'c': container = 1; break; case 'h': header = 0; break; case 's': longform = 0; break; case 't': term_mode = true; break; case 'f': from = !from; break; case 'V': printf(PROCPS_NG_VERSION); exit(0); case 'u': ignoreuser = 1; break; case 'o': oldstyle = 1; break; case 'i': ip_addresses = 1; from = 1; break; case 'p': pids = 1; break; case HELP_OPTION: usage(stdout); default: usage(stderr); } if ((argv[optind])) match_user = (argv[optind]); /* Get user field length from environment */ if ((env_var = getenv("PROCPS_USERLEN")) != NULL) { int ut_namesize = UT_NAMESIZE; userlen = atoi(env_var); if (userlen < 8 || ut_namesize < userlen) { warnx (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"), ut_namesize); userlen = 8; } } /* Get from field length from environment */ if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) { fromlen = atoi(env_var); if (fromlen < 8 || UT_HOSTSIZE < fromlen) { warnx (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"), UT_HOSTSIZE); fromlen = 16; } } if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0) maxcmd = win.ws_col; else if ((p = getenv("COLUMNS"))) maxcmd = atoi(p); else maxcmd = MAX_CMD_WIDTH; #define CLAMP_CMD_WIDTH(cw) do { \ if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \ if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \ } while (0) CLAMP_CMD_WIDTH(maxcmd); maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0); CLAMP_CMD_WIDTH(maxcmd); #undef CLAMP_CMD_WIDTH if ( (pids_cache = cache_pids(&info)) == NULL) { return(EXIT_FAILURE); } if (!procps_pids_sort(info, pids_cache->stacks, pids_cache->counts->total, PIDS_TICS_BEGAN, PIDS_SORT_ASCEND)) errx(EXIT_FAILURE, _("Unable to sort processes by PID")); if (header) { /* print uptime and headers */ show_uptime(container); /* Translation Hint: Following five uppercase messages are * headers. Try to keep alignment intact. */ printf(_("%-*s TTY "), userlen, _("USER")); if (from) printf("%-*s", fromlen, _("FROM")); if (longform) printf(_(" LOGIN@ IDLE JCPU PCPU WHAT\n")); else printf(_(" IDLE WHAT\n")); } if (term_mode) { print_user_terminals(longform, maxcmd, from, userlen, fromlen, ip_addresses, pids, info, pids_cache); } else { #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) char **sessions_list; int sessions = 0; sessions = sd_get_sessions (&sessions_list); if (sessions < 0 && ( sessions != -ENOENT && sessions != -EPERM)) error(EXIT_FAILURE, -sessions, _("error getting sessions")); if (sessions > 0) { //int i; for (int i = 0; i < sessions; i++) { char *class, *name; int r; if ((r = sd_session_get_class(sessions_list[i], &class)) < 0) error(EXIT_FAILURE, -r, _("session get class failed")); if (strncmp(class, "user", 4) != 0) { // user, user-early, user-incomplete free(class); continue; } if ((r = sd_session_get_username(sessions_list[i], &name)) < 0) error(EXIT_FAILURE, -r, _("get user name failed")); if (!match_user || (0 == strcmp(name, match_user))) showinfo(sessions_list[i], name, NULL, longform, maxcmd, from, userlen, fromlen, ip_addresses, pids, pids_cache); free(class); free(name); free(sessions_list[i]); } free(sessions_list); } else { #endif #ifdef HAVE_UTMPX_H setutxent(); #else utmpname(UTMP_FILE); setutent(); #endif for (;;) { #ifdef HAVE_UTMPX_H u = getutxent(); #else u = getutent(); #endif if (!u) break; if (u->ut_type != USER_PROCESS || ('\0' == u->ut_user[0])) continue; if (!match_user || (0 == strncmp(u->ut_user, match_user, UT_NAMESIZE))) showinfo( NULL, u->ut_user, u, longform, maxcmd, from, userlen, fromlen, ip_addresses, pids, pids_cache); } #ifdef HAVE_UTMPX_H endutxent(); #else endutent(); #endif #if (defined(WITH_SYSTEMD) || defined(WITH_ELOGIND)) && defined(HAVE_SD_SESSION_GET_LEADER) } #endif } procps_pids_unref(&info); return EXIT_SUCCESS; }