diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:42:50 +0000 |
commit | 8cb83eee5a58b1fad74c34094ce3afb9e430b5a4 (patch) | |
tree | a9b2e7baeca1be40eb734371e3c8b11b02294497 /login-utils/last.c | |
parent | Initial commit. (diff) | |
download | util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.tar.xz util-linux-8cb83eee5a58b1fad74c34094ce3afb9e430b5a4.zip |
Adding upstream version 2.33.1.upstream/2.33.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'login-utils/last.c')
-rw-r--r-- | login-utils/last.c | 1032 |
1 files changed, 1032 insertions, 0 deletions
diff --git a/login-utils/last.c b/login-utils/last.c new file mode 100644 index 0000000..6b25efd --- /dev/null +++ b/login-utils/last.c @@ -0,0 +1,1032 @@ +/* + * last(1) from sysvinit project, merged into util-linux in Aug 2013. + * + * Copyright (C) 1991-2004 Miquel van Smoorenburg. + * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com> + * Karel Zak <kzak@redhat.com> + * + * Re-implementation of the 'last' command, this time for Linux. Yes I know + * there is BSD last, but I just felt like writing this. No thanks :-). Also, + * this version gives lots more info (especially with -x) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> +#include <stdio.h> +#include <ctype.h> +#include <utmpx.h> +#include <pwd.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <getopt.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <libgen.h> + +#include "c.h" +#include "nls.h" +#include "optutils.h" +#include "pathnames.h" +#include "xalloc.h" +#include "closestream.h" +#include "carefulputc.h" +#include "strutils.h" +#include "timeutils.h" +#include "monotonic.h" + +#ifndef SHUTDOWN_TIME +# define SHUTDOWN_TIME 254 +#endif + +#ifndef LAST_LOGIN_LEN +# define LAST_LOGIN_LEN 8 +#endif + +#ifndef LAST_DOMAIN_LEN +# define LAST_DOMAIN_LEN 16 +#endif + +#ifndef LAST_TIMESTAMP_LEN +# define LAST_TIMESTAMP_LEN 32 +#endif + +#define UCHUNKSIZE 16384 /* How much we read at once. */ + +struct last_control { + unsigned int lastb :1, /* Is this command 'lastb' */ + extended :1, /* Lots of info */ + showhost :1, /* Show hostname */ + altlist :1, /* Hostname at the end */ + usedns :1, /* Use DNS to lookup the hostname */ + useip :1; /* Print IP address in number format */ + + unsigned int name_len; /* Number of login name characters to print */ + unsigned int domain_len; /* Number of domain name characters to print */ + unsigned int maxrecs; /* Maximum number of records to list */ + + char **show; /* Match search list */ + + struct timeval boot_time; /* system boot time */ + time_t since; /* at what time to start displaying the file */ + time_t until; /* at what time to stop displaying the file */ + time_t present; /* who where present at time_t */ + unsigned int time_fmt; /* time format */ +}; + +/* Double linked list of struct utmp's */ +struct utmplist { + struct utmpx ut; + struct utmplist *next; + struct utmplist *prev; +}; + +/* Types of listing */ +enum { + R_CRASH = 1, /* No logout record, system boot in between */ + R_DOWN, /* System brought down in decent way */ + R_NORMAL, /* Normal */ + R_NOW, /* Still logged in */ + R_REBOOT, /* Reboot record. */ + R_PHANTOM, /* No logout record but session is stale. */ + R_TIMECHANGE /* NEW_TIME or OLD_TIME */ +}; + +enum { + LAST_TIMEFTM_NONE = 0, + LAST_TIMEFTM_SHORT, + LAST_TIMEFTM_CTIME, + LAST_TIMEFTM_ISO8601, + + LAST_TIMEFTM_HHMM, /* non-public */ +}; + +struct last_timefmt { + const char *name; + int in_len; /* log-in */ + int in_fmt; + int out_len; /* log-out */ + int out_fmt; +}; + +static struct last_timefmt timefmts[] = { + [LAST_TIMEFTM_NONE] = { .name = "notime" }, + [LAST_TIMEFTM_SHORT] = { + .name = "short", + .in_len = 16, + .out_len = 7, + .in_fmt = LAST_TIMEFTM_CTIME, + .out_fmt = LAST_TIMEFTM_HHMM + }, + [LAST_TIMEFTM_CTIME] = { + .name = "full", + .in_len = 24, + .out_len = 26, + .in_fmt = LAST_TIMEFTM_CTIME, + .out_fmt = LAST_TIMEFTM_CTIME + }, + [LAST_TIMEFTM_ISO8601] = { + .name = "iso", + .in_len = 25, + .out_len = 27, + .in_fmt = LAST_TIMEFTM_ISO8601, + .out_fmt = LAST_TIMEFTM_ISO8601 + } +}; + +/* Global variables */ +static unsigned int recsdone; /* Number of records listed */ +static time_t lastdate; /* Last date we've seen */ +static time_t currentdate; /* date when we started processing the file */ + +/* --time-format=option parser */ +static int which_time_format(const char *s) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(timefmts); i++) { + if (strcmp(timefmts[i].name, s) == 0) + return i; + } + errx(EXIT_FAILURE, _("unknown time format: %s"), s); +} + +/* + * Read one utmp entry, return in new format. + * Automatically reposition file pointer. + */ +static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename) +{ + static int utsize; + static char buf[UCHUNKSIZE]; + char tmp[1024]; + static off_t fpos; + static int bpos; + off_t o; + + if (quit == NULL && u != NULL) { + /* + * Normal read. + */ + return fread(u, sizeof(struct utmpx), 1, fp); + } + + if (u == NULL) { + /* + * Initialize and position. + */ + utsize = sizeof(struct utmpx); + fseeko(fp, 0, SEEK_END); + fpos = ftello(fp); + if (fpos == 0) + return 0; + o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE; + if (fseeko(fp, o, SEEK_SET) < 0) { + warn(_("seek on %s failed"), filename); + return 0; + } + bpos = (int)(fpos - o); + if (fread(buf, bpos, 1, fp) != 1) { + warn(_("cannot read %s"), filename); + return 0; + } + fpos = o; + return 1; + } + + /* + * Read one struct. From the buffer if possible. + */ + bpos -= utsize; + if (bpos >= 0) { + memcpy(u, buf + bpos, sizeof(struct utmpx)); + return 1; + } + + /* + * Oops we went "below" the buffer. We should be able to + * seek back UCHUNKSIZE bytes. + */ + fpos -= UCHUNKSIZE; + if (fpos < 0) + return 0; + + /* + * Copy whatever is left in the buffer. + */ + memcpy(tmp + (-bpos), buf, utsize + bpos); + if (fseeko(fp, fpos, SEEK_SET) < 0) { + warn(_("seek on %s failed"), filename); + return 0; + } + + /* + * Read another UCHUNKSIZE bytes. + */ + if (fread(buf, UCHUNKSIZE, 1, fp) != 1) { + warn(_("cannot read %s"), filename); + return 0; + } + + /* + * The end of the UCHUNKSIZE byte buffer should be the first + * few bytes of the current struct utmp. + */ + memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos); + bpos += UCHUNKSIZE; + + memcpy(u, tmp, sizeof(struct utmpx)); + + return 1; +} + +/* + * Print a short date. + */ +static char *showdate(void) +{ + char *s = ctime(&lastdate); + s[16] = 0; + return s; +} + +/* + * SIGINT handler + */ +static void int_handler(int sig __attribute__((unused))) +{ + errx(EXIT_FAILURE, _("Interrupted %s"), showdate()); +} + +/* + * SIGQUIT handler + */ +static void quit_handler(int sig __attribute__((unused))) +{ + warnx(_("Interrupted %s"), showdate()); + signal(SIGQUIT, quit_handler); +} + +/* + * Lookup a host with DNS. + */ +static int dns_lookup(char *result, int size, int useip, int32_t *a) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr *sa; + int salen, flags; + int mapped = 0; + + flags = useip ? NI_NUMERICHOST : 0; + + /* + * IPv4 or IPv6 ? + * 1. If last 3 4bytes are 0, must be IPv4 + * 2. If IPv6 in IPv4, handle as IPv4 + * 3. Anything else is IPv6 + * + * Ugly. + */ + if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff)) + mapped = 1; + + if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) { + /* IPv4 */ + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = mapped ? a[3] : a[0]; + sa = (struct sockaddr *)&sin; + salen = sizeof(sin); + } else { + /* IPv6 */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = 0; + memcpy(sin6.sin6_addr.s6_addr, a, 16); + sa = (struct sockaddr *)&sin6; + salen = sizeof(sin6); + } + + return getnameinfo(sa, salen, result, size, NULL, 0, flags); +} + +static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when) +{ + int ret = 0; + + switch (fmt) { + case LAST_TIMEFTM_NONE: + *dst = 0; + break; + case LAST_TIMEFTM_HHMM: + { + struct tm *tm = localtime(when); + if (!snprintf(dst, dlen, "%02d:%02d", tm->tm_hour, tm->tm_min)) + ret = -1; + break; + } + case LAST_TIMEFTM_CTIME: + snprintf(dst, dlen, "%s", ctime(when)); + ret = rtrim_whitespace((unsigned char *) dst); + break; + case LAST_TIMEFTM_ISO8601: + ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen); + break; + default: + abort(); + } + return ret; +} + +/* + * Remove trailing spaces from a string. + */ +static void trim_trailing_spaces(char *s) +{ + char *p; + + for (p = s; *p; ++p) + continue; + while (p > s && isspace(*--p)) + continue; + if (p > s) + ++p; + *p++ = '\n'; + *p = '\0'; +} + +/* + * Show one line of information on screen + */ +static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what) +{ + time_t secs, utmp_time; + char logintime[LAST_TIMESTAMP_LEN]; + char logouttime[LAST_TIMESTAMP_LEN]; + char length[LAST_TIMESTAMP_LEN]; + char final[512]; + char utline[sizeof(p->ut_line) + 1]; + char domain[256]; + char *s; + int mins, hours, days; + int r, len; + struct last_timefmt *fmt; + + /* + * uucp and ftp have special-type entries + */ + utline[0] = 0; + strncat(utline, p->ut_line, sizeof(utline) - 1); + if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3])) + utline[3] = 0; + if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4])) + utline[4] = 0; + + /* + * Is this something we want to show? + */ + if (ctl->show) { + char **walk; + for (walk = ctl->show; *walk; walk++) { + if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 || + strcmp(utline, *walk) == 0 || + (strncmp(utline, "tty", 3) == 0 && + strcmp(utline + 3, *walk) == 0)) break; + } + if (*walk == NULL) return 0; + } + + /* + * Calculate times + */ + fmt = &timefmts[ctl->time_fmt]; + + utmp_time = p->ut_tv.tv_sec; + + if (ctl->present) { + if (ctl->present < utmp_time) + return 0; + if (0 < logout_time && logout_time < ctl->present) + return 0; + } + + /* log-in time */ + if (time_formatter(fmt->in_fmt, logintime, + sizeof(logintime), &utmp_time) < 0) + errx(EXIT_FAILURE, _("preallocation size exceeded")); + + /* log-out time */ + secs = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */ + mins = (secs / 60) % 60; + hours = (secs / 3600) % 24; + days = secs / 86400; + + strcpy(logouttime, "- "); + if (time_formatter(fmt->out_fmt, logouttime + 2, + sizeof(logouttime) - 2, &logout_time) < 0) + errx(EXIT_FAILURE, _("preallocation size exceeded")); + + if (logout_time == currentdate) { + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + sprintf(logouttime, " still running"); + length[0] = 0; + } else { + sprintf(logouttime, " still"); + sprintf(length, "running"); + } + } else if (days) { + sprintf(length, "(%d+%02d:%02d)", days, abs(hours), abs(mins)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } else if (hours) { + sprintf(length, " (%02d:%02d)", hours, abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } else if (secs >= 0) { + sprintf(length, " (%02d:%02d)", hours, mins); + } else { + sprintf(length, " (-00:%02d)", abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } + + switch(what) { + case R_CRASH: + sprintf(logouttime, "- crash"); + break; + case R_DOWN: + sprintf(logouttime, "- down "); + break; + case R_NOW: + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + sprintf(logouttime, " still logged in"); + length[0] = 0; + } else { + sprintf(logouttime, " still"); + sprintf(length, "logged in"); + } + break; + case R_PHANTOM: + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + sprintf(logouttime, " gone - no logout"); + length[0] = 0; + } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) { + sprintf(logouttime, " gone"); + sprintf(length, "- no logout"); + } else { + logouttime[0] = 0; + sprintf(length, "no logout"); + } + break; + case R_TIMECHANGE: + logouttime[0] = 0; + length[0] = 0; + break; + case R_NORMAL: + case R_REBOOT: + break; + default: + abort(); + } + + /* + * Look up host with DNS if needed. + */ + r = -1; + if (ctl->usedns || ctl->useip) + r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6); + if (r < 0) + mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain)); + + if (ctl->showhost) { + if (!ctl->altlist) { + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n", + ctl->name_len, p->ut_user, utline, + ctl->domain_len, domain, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length); + } else { + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n", + ctl->name_len, p->ut_user, utline, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length, domain); + } + } else + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-*.*s %-*.*s %s\n", + ctl->name_len, p->ut_user, utline, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length); + +#if defined(__GLIBC__) +# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0) + final[sizeof(final)-1] = '\0'; +# endif +#endif + + trim_trailing_spaces(final); + /* + * Print out "final" string safely. + */ + for (s = final; *s; s++) + fputc_careful(*s, stdout, '*'); + + if (len < 0 || (size_t)len >= sizeof(final)) + putchar('\n'); + + recsdone++; + if (ctl->maxrecs && ctl->maxrecs <= recsdone) + return 1; + + return 0; +} + + +static void __attribute__((__noreturn__)) usage(const struct last_control *ctl) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _( + " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Show a listing of last logged in users.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -<number> how many lines to show\n"), out); + fputs(_(" -a, --hostlast display hostnames in the last column\n"), out); + fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out); + fprintf(out, + _(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP); + fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out); + fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out); + fputs(_(" -n, --limit <number> how many lines to show\n"), out); + fputs(_(" -R, --nohostname don't display the hostname field\n"), out); + fputs(_(" -s, --since <time> display the lines since the specified time\n"), out); + fputs(_(" -t, --until <time> display the lines until the specified time\n"), out); + fputs(_(" -p, --present <time> display who were present at the specified time\n"), out); + fputs(_(" -w, --fullnames display full user and domain names\n"), out); + fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out); + fputs(_(" --time-format <format> show timestamps in the specified <format>:\n" + " notime|short|full|iso\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(22)); + printf(USAGE_MAN_TAIL("last(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static int is_phantom(const struct last_control *ctl, struct utmpx *ut) +{ + struct passwd *pw; + char path[sizeof(ut->ut_line) + 16]; + int ret = 0; + + if (ut->ut_tv.tv_sec < ctl->boot_time.tv_sec) + return 1; + pw = getpwnam(ut->ut_user); + if (!pw) + return 1; + sprintf(path, "/proc/%u/loginuid", ut->ut_pid); + if (access(path, R_OK) == 0) { + unsigned int loginuid; + FILE *f = NULL; + + if (!(f = fopen(path, "r"))) + return 1; + if (fscanf(f, "%u", &loginuid) != 1) + ret = 1; + fclose(f); + if (!ret && pw->pw_uid != loginuid) + return 1; + } else { + struct stat st; + + sprintf(path, "/dev/%s", ut->ut_line); + if (stat(path, &st)) + return 1; + if (pw->pw_uid != st.st_uid) + return 1; + } + return ret; +} + +static void process_wtmp_file(const struct last_control *ctl, + const char *filename) +{ + FILE *fp; /* File pointer of wtmp file */ + + struct utmpx ut; /* Current utmp entry */ + struct utmplist *ulist = NULL; /* All entries */ + struct utmplist *p; /* Pointer into utmplist */ + struct utmplist *next; /* Pointer into utmplist */ + + time_t lastboot = 0; /* Last boottime */ + time_t lastrch = 0; /* Last run level change */ + time_t lastdown; /* Last downtime */ + time_t begintime; /* When wtmp begins */ + int whydown = 0; /* Why we went down: crash or shutdown */ + + int c, x; /* Scratch */ + struct stat st; /* To stat the [uw]tmp file */ + int quit = 0; /* Flag */ + int down = 0; /* Down flag */ + + time(&lastdown); + /* + * Fill in 'lastdate' + */ + lastdate = currentdate = lastrch = lastdown; + + /* + * Install signal handlers + */ + signal(SIGINT, int_handler); + signal(SIGQUIT, quit_handler); + + /* + * Open the utmp file + */ + if ((fp = fopen(filename, "r")) == NULL) + err(EXIT_FAILURE, _("cannot open %s"), filename); + + /* + * Optimize the buffer size. + */ + setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE); + + /* + * Read first structure to capture the time field + */ + if (uread(fp, &ut, NULL, filename) == 1) + begintime = ut.ut_tv.tv_sec; + else { + if (fstat(fileno(fp), &st) != 0) + err(EXIT_FAILURE, _("stat of %s failed"), filename); + begintime = st.st_ctime; + quit = 1; + } + + /* + * Go to end of file minus one structure + * and/or initialize utmp reading code. + */ + uread(fp, NULL, NULL, filename); + + /* + * Read struct after struct backwards from the file. + */ + while (!quit) { + + if (uread(fp, &ut, &quit, filename) != 1) + break; + + if (ctl->since && ut.ut_tv.tv_sec < ctl->since) + continue; + + if (ctl->until && ctl->until < ut.ut_tv.tv_sec) + continue; + + lastdate = ut.ut_tv.tv_sec; + + if (ctl->lastb) { + quit = list(ctl, &ut, ut.ut_tv.tv_sec, R_NORMAL); + continue; + } + + /* + * Set ut_type to the correct type. + */ + if (strncmp(ut.ut_line, "~", 1) == 0) { + if (strncmp(ut.ut_user, "shutdown", 8) == 0) + ut.ut_type = SHUTDOWN_TIME; + else if (strncmp(ut.ut_user, "reboot", 6) == 0) + ut.ut_type = BOOT_TIME; + else if (strncmp(ut.ut_user, "runlevel", 8) == 0) + ut.ut_type = RUN_LVL; + } +#if 1 /*def COMPAT*/ + /* + * For stupid old applications that don't fill in + * ut_type correctly. + */ + else { + if (ut.ut_type != DEAD_PROCESS && + ut.ut_user[0] && ut.ut_line[0] && + strcmp(ut.ut_user, "LOGIN") != 0) + ut.ut_type = USER_PROCESS; + /* + * Even worse, applications that write ghost + * entries: ut_type set to USER_PROCESS but + * empty ut_user... + */ + if (ut.ut_user[0] == 0) + ut.ut_type = DEAD_PROCESS; + + /* + * Clock changes. + */ + if (strcmp(ut.ut_user, "date") == 0) { + if (ut.ut_line[0] == '|') + ut.ut_type = OLD_TIME; + if (ut.ut_line[0] == '{') + ut.ut_type = NEW_TIME; + } + } +#endif + switch (ut.ut_type) { + case SHUTDOWN_TIME: + if (ctl->extended) { + strcpy(ut.ut_line, "system down"); + quit = list(ctl, &ut, lastboot, R_NORMAL); + } + lastdown = lastrch = ut.ut_tv.tv_sec; + down = 1; + break; + case OLD_TIME: + case NEW_TIME: + if (ctl->extended) { + strcpy(ut.ut_line, + ut.ut_type == NEW_TIME ? "new time" : + "old time"); + quit = list(ctl, &ut, lastdown, R_TIMECHANGE); + } + break; + case BOOT_TIME: + strcpy(ut.ut_line, "system boot"); + quit = list(ctl, &ut, lastdown, R_REBOOT); + lastboot = ut.ut_tv.tv_sec; + down = 1; + break; + case RUN_LVL: + x = ut.ut_pid & 255; + if (ctl->extended) { + sprintf(ut.ut_line, "(to lvl %c)", x); + quit = list(ctl, &ut, lastrch, R_NORMAL); + } + if (x == '0' || x == '6') { + lastdown = ut.ut_tv.tv_sec; + down = 1; + ut.ut_type = SHUTDOWN_TIME; + } + lastrch = ut.ut_tv.tv_sec; + break; + + case USER_PROCESS: + /* + * This was a login - show the first matching + * logout record and delete all records with + * the same ut_line. + */ + c = 0; + for (p = ulist; p; p = next) { + next = p->next; + if (strncmp(p->ut.ut_line, ut.ut_line, + sizeof(ut.ut_line)) == 0) { + /* Show it */ + if (c == 0) { + quit = list(ctl, &ut, p->ut.ut_tv.tv_sec, R_NORMAL); + c = 1; + } + if (p->next) + p->next->prev = p->prev; + if (p->prev) + p->prev->next = p->next; + else + ulist = p->next; + free(p); + } + } + /* + * Not found? Then crashed, down, still + * logged in, or missing logout record. + */ + if (c == 0) { + if (!lastboot) { + c = R_NOW; + /* Is process still alive? */ + if (is_phantom(ctl, &ut)) + c = R_PHANTOM; + } else + c = whydown; + quit = list(ctl, &ut, lastboot, c); + } + /* fallthrough */ + + case DEAD_PROCESS: + /* + * Just store the data if it is + * interesting enough. + */ + if (ut.ut_line[0] == 0) + break; + p = xmalloc(sizeof(struct utmplist)); + memcpy(&p->ut, &ut, sizeof(struct utmpx)); + p->next = ulist; + p->prev = NULL; + if (ulist) + ulist->prev = p; + ulist = p; + break; + + case EMPTY: + case INIT_PROCESS: + case LOGIN_PROCESS: +#ifdef ACCOUNTING + case ACCOUNTING: +#endif + /* ignored ut_types */ + break; + + default: + warnx("unrecognized ut_type: %d", ut.ut_type); + } + + /* + * If we saw a shutdown/reboot record we can remove + * the entire current ulist. + */ + if (down) { + lastboot = ut.ut_tv.tv_sec; + whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH; + for (p = ulist; p; p = next) { + next = p->next; + free(p); + } + ulist = NULL; + down = 0; + } + } + + if (ctl->time_fmt != LAST_TIMEFTM_NONE) { + struct last_timefmt *fmt; + char timestr[LAST_TIMESTAMP_LEN]; + char *tmp = xstrdup(filename); + + fmt = &timefmts[ctl->time_fmt]; + if (time_formatter(fmt->in_fmt, timestr, + sizeof(timestr), &begintime) < 0) + errx(EXIT_FAILURE, _("preallocation size exceeded")); + printf(_("\n%s begins %s\n"), basename(tmp), timestr); + free(tmp); + } + + fclose(fp); + + for (p = ulist; p; p = next) { + next = p->next; + free(p); + } +} + +int main(int argc, char **argv) +{ + struct last_control ctl = { + .showhost = TRUE, + .name_len = LAST_LOGIN_LEN, + .time_fmt = LAST_TIMEFTM_SHORT, + .domain_len = LAST_DOMAIN_LEN + }; + char **files = NULL; + size_t i, nfiles = 0; + int c; + usec_t p; + + enum { + OPT_TIME_FORMAT = CHAR_MAX + 1 + }; + static const struct option long_opts[] = { + { "limit", required_argument, NULL, 'n' }, + { "help", no_argument, NULL, 'h' }, + { "file", required_argument, NULL, 'f' }, + { "nohostname", no_argument, NULL, 'R' }, + { "version", no_argument, NULL, 'V' }, + { "hostlast", no_argument, NULL, 'a' }, + { "since", required_argument, NULL, 's' }, + { "until", required_argument, NULL, 't' }, + { "present", required_argument, NULL, 'p' }, + { "system", no_argument, NULL, 'x' }, + { "dns", no_argument, NULL, 'd' }, + { "ip", no_argument, NULL, 'i' }, + { "fulltimes", no_argument, NULL, 'F' }, + { "fullnames", no_argument, NULL, 'w' }, + { "time-format", required_argument, NULL, OPT_TIME_FORMAT }, + { NULL, 0, NULL, 0 } + }; + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */ + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + /* + * Which file do we want to read? + */ + ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0; + while ((c = getopt_long(argc, argv, + "hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) { + + err_exclusive_options(c, long_opts, excl, excl_st); + + switch(c) { + case 'h': + usage(&ctl); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'R': + ctl.showhost = 0; + break; + case 'x': + ctl.extended = 1; + break; + case 'n': + ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number")); + break; + case 'f': + if (!files) + files = xmalloc(sizeof(char *) * argc); + files[nfiles++] = xstrdup(optarg); + break; + case 'd': + ctl.usedns = 1; + break; + case 'i': + ctl.useip = 1; + break; + case 'a': + ctl.altlist = 1; + break; + case 'F': + ctl.time_fmt = LAST_TIMEFTM_CTIME; + break; + case 'p': + if (parse_timestamp(optarg, &p) < 0) + errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg); + ctl.present = (time_t) (p / 1000000); + break; + case 's': + if (parse_timestamp(optarg, &p) < 0) + errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg); + ctl.since = (time_t) (p / 1000000); + break; + case 't': + if (parse_timestamp(optarg, &p) < 0) + errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg); + ctl.until = (time_t) (p / 1000000); + break; + case 'w': + if (ctl.name_len < sizeof(((struct utmpx *) 0)->ut_user)) + ctl.name_len = sizeof(((struct utmpx *) 0)->ut_user); + if (ctl.domain_len < sizeof(((struct utmpx *) 0)->ut_host)) + ctl.domain_len = sizeof(((struct utmpx *) 0)->ut_host); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ctl.maxrecs = 10 * ctl.maxrecs + c - '0'; + break; + case OPT_TIME_FORMAT: + ctl.time_fmt = which_time_format(optarg); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (optind < argc) + ctl.show = argv + optind; + + if (!files) { + files = xmalloc(sizeof(char *)); + files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP); + } + + for (i = 0; i < nfiles; i++) { + get_boot_time(&ctl.boot_time); + process_wtmp_file(&ctl, files[i]); + free(files[i]); + } + free(files); + return EXIT_SUCCESS; +} |