/* * last(1) from sysvinit project, merged into util-linux in Aug 2013. * * Copyright (C) 1991-2004 Miquel van Smoorenburg. * Copyright (C) 2013 Ondrej Oprala * Karel Zak * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" #if defined(_HAVE_UT_TV) # define UL_UT_TIME ut_tv.tv_sec #else # define UL_UT_TIME ut_time #endif #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 */ fulltime :1; /* Print full dates and times */ 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 */ char **altv; /* Alternate wtmp files */ unsigned int altc; /* Number of alternative files */ unsigned int alti; /* Index number of the alternative file */ 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 utmp ut; struct utmplist *next; struct utmplist *prev; }; struct utmplist *utmplist = NULL; /* 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_CTIME, LAST_TIMEFTM_FULL_CTIME, LAST_TIMEFTM_ISO8601 }; struct last_timefmt { const char *name; int in; int out; }; static struct last_timefmt timefmts[] = { [LAST_TIMEFTM_NONE] = { "notime", 0, 0 }, [LAST_TIMEFTM_SHORT_CTIME] = { "short", 16, 7}, [LAST_TIMEFTM_FULL_CTIME] = { "full", 24, 26}, [LAST_TIMEFTM_ISO8601] = { "iso", 24, 26} }; /* Global variables */ static unsigned int recsdone; /* Number of records listed */ static time_t lastdate; /* Last date we've seen */ /* --time-format=option parser */ static int which_time_format(const char *optarg) { size_t i; for (i = 0; i < ARRAY_SIZE(timefmts); i++) { if (strcmp(timefmts[i].name, optarg) == 0) return i; } errx(EXIT_FAILURE, _("unknown time format: %s"), optarg); } /* * Read one utmp entry, return in new format. * Automatically reposition file pointer. */ static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u, int *quit) { 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 utmp), 1, fp); } if (u == NULL) { /* * Initialize and position. */ utsize = sizeof(struct utmp); 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 failed: %s"), ctl->altv[ctl->alti]); return 0; } bpos = (int)(fpos - o); if (fread(buf, bpos, 1, fp) != 1) { warn(_("read failed: %s"), ctl->altv[ctl->alti]); 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 utmp)); 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 failed: %s"), ctl->altv[ctl->alti]); return 0; } /* * Read another UCHUNKSIZE bytes. */ if (fread(buf, UCHUNKSIZE, 1, fp) != 1) { warn(_("read failed: %s"), ctl->altv[ctl->alti]); 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 utmp)); 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(const struct last_control *ctl, char *dst, size_t dlen, time_t *when, int pos) { struct tm *tm; int ret = 0; switch (ctl->time_fmt) { case LAST_TIMEFTM_NONE: *dst = 0; break; case LAST_TIMEFTM_SHORT_CTIME: if (pos == 0) ret = sprintf(dst, "%s", ctime(when)); else { tm = localtime(when); if (!strftime(dst, dlen, "- %H:%M", tm)) ret = -1; } break; case LAST_TIMEFTM_FULL_CTIME: if (pos == 0) ret = sprintf(dst, "%s", ctime(when)); else ret = sprintf(dst, "- %s", ctime(when)); break; case LAST_TIMEFTM_ISO8601: tm = localtime(when); if (pos == 0) { if (!strftime(dst, dlen, "%Y-%m-%dT%H:%M:%S%z", tm)) ret = -1; } else if (!strftime(dst, dlen, "- %Y-%m-%dT%H:%M:%S%z", tm)) ret = -1; 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 utmp *p, time_t t, int what) { time_t secs, tmp, epoch; char logintime[LAST_TIMESTAMP_LEN]; char logouttime[LAST_TIMESTAMP_LEN]; char length[LAST_TIMESTAMP_LEN]; char final[512]; char utline[UT_LINESIZE+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, UT_LINESIZE); 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 wanna show? */ if (ctl->show) { char **walk; for (walk = ctl->show; *walk; walk++) { if (strncmp(p->ut_user, *walk, UT_NAMESIZE) == 0 || strcmp(utline, *walk) == 0 || (strncmp(utline, "tty", 3) == 0 && strcmp(utline + 3, *walk) == 0)) break; } if (*walk == NULL) return 0; } /* * Calculate times */ tmp = p->UL_UT_TIME; if (ctl->present && (ctl->present < tmp || (0 < t && t < ctl->present))) return 0; if (time_formatter(ctl, &logintime[0], sizeof(logintime), &tmp, 0) < 0 || time_formatter(ctl, &logouttime[0], sizeof(logouttime), &t, 1) < 0) errx(EXIT_FAILURE, _("preallocation size exceeded")); secs = t - p->UL_UT_TIME; mins = (secs / 60) % 60; hours = (secs / 3600) % 24; days = secs / 86400; epoch = time(NULL); if (t == epoch) { if (ctl->fulltime) sprintf(logouttime, " still running"); else { sprintf(logouttime, " still"); sprintf(length, "running"); } } else if (days) sprintf(length, "(%d+%02d:%02d)", days, hours, mins); else sprintf(length, " (%02d:%02d)", hours, mins); switch(what) { case R_CRASH: sprintf(logouttime, "- crash"); break; case R_DOWN: sprintf(logouttime, "- down "); break; case R_NOW: length[0] = 0; if (ctl->fulltime) sprintf(logouttime, " still logged in"); else { sprintf(logouttime, " still"); sprintf(length, "logged in"); } break; case R_PHANTOM: length[0] = 0; if (ctl->fulltime) sprintf(logouttime, " gone - no logout"); else { sprintf(logouttime, " gone"); sprintf(length, "- no logout"); } break; case R_REBOOT: break; case R_TIMECHANGE: logouttime[0] = 0; length[0] = 0; break; case R_NORMAL: 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, p->ut_addr_v6); if (r < 0) { len = UT_HOSTSIZE; if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1; domain[0] = 0; strncat(domain, p->ut_host, len); } fmt = &timefmts[ctl->time_fmt]; 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, fmt->in, logintime, fmt->out, fmt->out, 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, fmt->in, logintime, fmt->out, fmt->out, 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, fmt->in, logintime, fmt->out, fmt->out, 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++) carefulputc(*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(FILE *out) { fputs(USAGE_HEADER, out); fprintf(out, _( " %s [options] [...] [...]\n"), program_invocation_short_name); fputs(USAGE_OPTIONS, out); fputs(_(" - 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 use a specific file instead of %s\n"), _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 how many lines to show\n"), out); fputs(_(" -R, --nohostname don't display the hostname field\n"), out); fputs(_(" -s, --since