2 * last.c Re-implementation of the 'last' command, this time
3 * for Linux. Yes I know there is BSD last, but I
4 * just felt like writing this. No thanks :-).
5 * Also, this version gives lots more info (especially with -x)
7 * Author: Miquel van Smoorenburg, miquels@cistron.nl
9 * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl
11 * This file is part of the sysvinit suite,
12 * Copyright 1991-2004 Miquel van Smoorenburg.
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version
17 * 2 of the License, or (at your option) any later version.
20 #include <sys/types.h>
22 #include <sys/fcntl.h>
34 #include <netinet/in.h>
36 #include <arpa/inet.h>
40 # define SHUTDOWN_TIME 254
43 char *Version = "@(#) last 2.85 31-Apr-2004 miquels";
45 #define CHOP_DOMAIN 0 /* Define to chop off local domainname. */
46 #define NEW_UTMP 1 /* Fancy & fast utmp read code. */
47 #define UCHUNKSIZE 16384 /* How much we read at once. */
49 /* Double linked list of struct utmp's */
52 struct utmplist *next;
53 struct utmplist *prev;
55 struct utmplist *utmplist = NULL;
57 /* Types of listing */
58 #define R_CRASH 1 /* No logout record, system boot in between */
59 #define R_DOWN 2 /* System brought down in decent way */
60 #define R_NORMAL 3 /* Normal */
61 #define R_NOW 4 /* Still logged in */
62 #define R_REBOOT 5 /* Reboot record. */
63 #define R_PHANTOM 6 /* No logout record but session is stale. */
64 #define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */
66 /* Global variables */
67 int maxrecs = 0; /* Maximum number of records to list. */
68 int recsdone = 0; /* Number of records listed */
69 int showhost = 1; /* Show hostname too? */
70 int altlist = 0; /* Show hostname at the end. */
71 int usedns = 0; /* Use DNS to lookup the hostname. */
72 int useip = 0; /* Print IP address in number format */
73 int fulltime = 0; /* Print full dates and times */
74 int oldfmt = 0; /* Use old libc5 format? */
75 char **show = NULL; /* What do they want us to show */
76 char *ufile; /* Filename of this file */
77 time_t lastdate; /* Last date we've seen */
78 char *progname; /* Name of this program */
80 char hostname[256]; /* For gethostbyname() */
81 char *domainname; /* Our domainname. */
85 * Convert old utmp format to new.
87 void uconv(struct oldutmp *oldut, struct utmp *utn)
89 memset(utn, 0, sizeof(struct utmp));
90 utn->ut_type = oldut->ut_type;
91 utn->ut_pid = oldut->ut_pid;
92 utn->ut_time = oldut->ut_oldtime;
93 utn->ut_addr = oldut->ut_oldaddr;
94 strncpy(utn->ut_line, oldut->ut_line, OLD_LINESIZE);
95 strncpy(utn->ut_user, oldut->ut_user, OLD_NAMESIZE);
96 strncpy(utn->ut_host, oldut->ut_host, OLD_HOSTSIZE);
101 * Read one utmp entry, return in new format.
102 * Automatically reposition file pointer.
104 int uread(FILE *fp, struct utmp *u, int *quit)
107 static char buf[UCHUNKSIZE];
115 if (quit == NULL && u != NULL) {
120 r = fread(&uto, sizeof(uto), 1, fp);
123 r = fread(u, sizeof(struct utmp), 1, fp);
129 * Initialize and position.
131 utsize = oldfmt ? sizeof(uto) : sizeof(struct utmp);
132 fseeko(fp, 0, SEEK_END);
136 o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
137 if (fseeko(fp, o, SEEK_SET) < 0) {
138 fprintf(stderr, "%s: seek failed!\n", progname);
141 bpos = (int)(fpos - o);
142 if (fread(buf, bpos, 1, fp) != 1) {
143 fprintf(stderr, "%s: read failed!\n", progname);
151 * Read one struct. From the buffer if possible.
156 uconv((struct oldutmp *)(buf + bpos), u);
158 memcpy(u, buf + bpos, sizeof(struct utmp));
163 * Oops we went "below" the buffer. We should be able to
164 * seek back UCHUNKSIZE bytes.
171 * Copy whatever is left in the buffer.
173 memcpy(tmp + (-bpos), buf, utsize + bpos);
174 if (fseeko(fp, fpos, SEEK_SET) < 0) {
180 * Read another UCHUNKSIZE bytes.
182 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
188 * The end of the UCHUNKSIZE byte buffer should be the first
189 * few bytes of the current struct utmp.
191 memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
195 uconv((struct oldutmp *)tmp, u);
197 memcpy(u, tmp, sizeof(struct utmp));
205 * Read one utmp entry, return in new format.
206 * Automatically reposition file pointer.
208 int uread(FILE *fp, struct utmp *u, int *quit)
214 r = oldfmt ? sizeof(struct oldutmp) : sizeof(struct utmp);
215 fseek(fp, -1 * r, SEEK_END);
220 r = fread(u, sizeof(struct utmp), 1, fp);
222 if (fseeko(fp, -2 * sizeof(struct utmp), SEEK_CUR) < 0)
227 r = fread(&uto, sizeof(struct oldutmp), 1, fp);
229 if (fseeko(fp, -2 * sizeof(struct oldutmp), SEEK_CUR) < 0)
239 * Try to be smart about the location of the BTMP file
242 #define BTMP_FILE getbtmp()
245 static char btmp[128];
248 strcpy(btmp, WTMP_FILE);
249 if ((p = strrchr(btmp, '/')) == NULL)
254 strcat(btmp, "btmp");
260 * Print a short date.
264 char *s = ctime(&lastdate);
274 printf("Interrupted %s\n", showdate());
283 printf("Interrupted %s\n", showdate());
284 signal(SIGQUIT, quit_handler);
288 * Get the basename of a filename
290 char *mybasename(char *s)
294 if ((p = strrchr(s, '/')) != NULL)
302 * Lookup a host with DNS.
304 int dns_lookup(char *result, int size, int useip, int32_t *a)
306 struct sockaddr_in sin;
307 struct sockaddr_in6 sin6;
310 unsigned int topnibble;
311 unsigned int azero = 0, sitelocal = 0;
314 flags = useip ? NI_NUMERICHOST : 0;
317 * IPv4 or IPv6 ? We use 2 heuristics:
318 * 1. Current IPv6 range uses 2000-3fff or fec0-feff.
319 * Outside of that is illegal and must be IPv4.
320 * 2. If last 3 bytes are 0, must be IPv4
321 * 3. If IPv6 in IPv4, handle as IPv4
325 if (a[0] == 0 && a[1] == 0 && a[2] == htonl (0xffff))
327 topnibble = ntohl((unsigned int)a[0]) >> 28;
329 azero = ntohl((unsigned int)a[0]) >> 16;
330 sitelocal = (azero >= 0xfec0 && azero <= 0xfeff) ? 1 : 0;
332 if (((topnibble < 2 || topnibble > 3) && (!sitelocal)) || mapped ||
333 (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
335 sin.sin_family = AF_INET;
337 sin.sin_addr.s_addr = mapped ? a[3] : a[0];
338 sa = (struct sockaddr *)&sin;
342 memset(&sin6, 0, sizeof(sin6));
343 sin6.sin6_family = AF_INET6;
345 memcpy(sin6.sin6_addr.s6_addr, a, 16);
346 sa = (struct sockaddr *)&sin6;
347 salen = sizeof(sin6);
350 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
354 * Show one line of information on screen
356 int list(struct utmp *p, time_t t, int what)
363 char utline[UT_LINESIZE+1];
366 int mins, hours, days;
370 * uucp and ftp have special-type entries
373 strncat(utline, p->ut_line, UT_LINESIZE);
374 if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
376 if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
380 * Is this something we wanna show?
383 for (walk = show; *walk; walk++) {
384 if (strncmp(p->ut_name, *walk, UT_NAMESIZE) == 0 ||
385 strcmp(utline, *walk) == 0 ||
386 (strncmp(utline, "tty", 3) == 0 &&
387 strcmp(utline + 3, *walk) == 0)) break;
389 if (*walk == NULL) return 0;
395 tmp = (time_t)p->ut_time;
396 strcpy(logintime, ctime(&tmp));
398 sprintf(logouttime, "- %s", ctime(&t));
401 sprintf(logouttime, "- %s", ctime(&t) + 11);
404 secs = t - p->ut_time;
405 mins = (secs / 60) % 60;
406 hours = (secs / 3600) % 24;
409 sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
411 sprintf(length, " (%02d:%02d)", hours, mins);
415 sprintf(logouttime, "- crash");
418 sprintf(logouttime, "- down ");
423 sprintf(logouttime, " still logged in");
425 sprintf(logouttime, " still");
426 sprintf(length, "logged in");
432 sprintf(logouttime, " gone - no logout");
434 sprintf(logouttime, " gone");
435 sprintf(length, "- no logout");
449 * Look up host with DNS if needed.
453 r = dns_lookup(domain, sizeof(domain), useip, p->ut_addr_v6);
456 if (len >= sizeof(domain)) len = sizeof(domain) - 1;
458 strncat(domain, p->ut_host, len);
464 * See if this is in our domain.
466 if (!usedns && (s = strchr(p->ut_host, '.')) != NULL &&
467 strcmp(s + 1, domainname) == 0) *s = 0;
470 snprintf(final, sizeof(final),
472 "%-8.8s %-12.12s %-16.16s %-24.24s %-26.26s %-12.12s\n" :
473 "%-8.8s %-12.12s %-16.16s %-16.16s %-7.7s %-12.12s\n",
475 domain, logintime, logouttime, length);
477 snprintf(final, sizeof(final),
479 "%-8.8s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
480 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
482 logintime, logouttime, length, domain);
485 snprintf(final, sizeof(final),
487 "%-8.8s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
488 "%-8.8s %-12.12s %-16.16s %-7.7s %-12.12s\n",
490 logintime, logouttime, length);
493 * Print out "final" string safely.
495 for (s = final; *s; s++) {
496 if (*s == '\n' || (*s >= 32 && (unsigned char)*s <= 126))
503 if (maxrecs && recsdone >= maxrecs)
515 fprintf(stderr, "Usage: %s [-num | -n num] [-f file] "
516 "[-t YYYYMMDDHHMMSS] "
517 "[-R] [-adioxF] [username..] [tty..]\n", s);
521 time_t parsetm(char *ts)
526 memset(&tm, 0, sizeof(tm));
528 if (sscanf(ts, "%4d%2d%2d%2d%2d%2d", &u.tm_year,
529 &u.tm_mon, &u.tm_mday, &u.tm_hour, &u.tm_min,
539 if ((tm = mktime(&u)) == (time_t)-1)
543 * Unfortunately mktime() is much more forgiving than
544 * it should be. For example, it'll gladly accept
545 * "30" as a valid month number. This behavior is by
546 * design, but we don't like it, so we want to detect
549 if (u.tm_year != origu.tm_year ||
550 u.tm_mon != origu.tm_mon ||
551 u.tm_mday != origu.tm_mday ||
552 u.tm_hour != origu.tm_hour ||
553 u.tm_min != origu.tm_min ||
554 u.tm_sec != origu.tm_sec)
560 int main(int argc, char **argv)
562 FILE *fp; /* Filepointer of wtmp file */
564 struct utmp ut; /* Current utmp entry */
565 struct utmp oldut; /* Old utmp entry to check for duplicates */
566 struct utmplist *p; /* Pointer into utmplist */
567 struct utmplist *next;/* Pointer into utmplist */
569 time_t lastboot = 0; /* Last boottime */
570 time_t lastrch = 0; /* Last run level change */
571 time_t lastdown; /* Last downtime */
572 time_t begintime; /* When wtmp begins */
573 int whydown = 0; /* Why we went down: crash or shutdown */
575 int c, x; /* Scratch */
576 struct stat st; /* To stat the [uw]tmp file */
577 int quit = 0; /* Flag */
578 int down = 0; /* Down flag */
579 int lastb = 0; /* Is this 'lastb' ? */
580 int extended = 0; /* Lots of info. */
581 char *altufile = NULL;/* Alternate wtmp */
583 time_t until = 0; /* at what time to stop parsing the file */
585 progname = mybasename(argv[0]);
587 /* Process the arguments. */
588 while((c = getopt(argc, argv, "f:n:RxadFiot:0123456789")) != EOF)
597 maxrecs = atoi(optarg);
603 if((altufile = malloc(strlen(optarg)+1)) == NULL) {
604 fprintf(stderr, "%s: out of memory\n",
608 strcpy(altufile, optarg);
623 if ((until = parsetm(optarg)) == (time_t)-1) {
624 fprintf(stderr, "%s: Invalid time value \"%s\"\n",
629 case '0': case '1': case '2': case '3': case '4':
630 case '5': case '6': case '7': case '8': case '9':
631 maxrecs = 10*maxrecs + c - '0';
637 if (optind < argc) show = argv + optind;
640 * Which file do we want to read?
642 if (strcmp(progname, "lastb") == 0) {
659 * Find out domainname.
661 * This doesn't work on modern systems, where only a DNS
662 * lookup of the result from hostname() will get you the domainname.
663 * Remember that domainname() is the NIS domainname, not DNS.
664 * So basically this whole piece of code is bullshit.
667 (void) gethostname(hostname, sizeof(hostname));
668 if ((domainname = strchr(hostname, '.')) != NULL) domainname++;
669 if (domainname == NULL || domainname[0] == 0) {
671 (void) getdomainname(hostname, sizeof(hostname));
672 hostname[sizeof(hostname) - 1] = 0;
673 domainname = hostname;
674 if (strcmp(domainname, "(none)") == 0 || domainname[0] == 0)
680 * Install signal handlers
682 signal(SIGINT, int_handler);
683 signal(SIGQUIT, quit_handler);
688 if ((fp = fopen(ufile, "r")) == NULL) {
690 fprintf(stderr, "%s: %s: %s\n", progname, ufile, strerror(errno));
691 if (altufile == NULL && x == ENOENT)
692 fprintf(stderr, "Perhaps this file was removed by the "
693 "operator to prevent logging %s info.\n", progname);
698 * Optimize the buffer size.
700 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
703 * Read first structure to capture the time field
705 if (uread(fp, &ut, NULL) == 1)
706 begintime = ut.ut_time;
708 fstat(fileno(fp), &st);
709 begintime = st.st_ctime;
714 * Go to end of file minus one structure
715 * and/or initialize utmp reading code.
717 uread(fp, NULL, NULL);
720 * Read struct after struct backwards from the file.
724 if (uread(fp, &ut, &quit) != 1)
727 if (until && until < ut.ut_time)
730 if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue;
731 memcpy(&oldut, &ut, sizeof(struct utmp));
732 lastdate = ut.ut_time;
735 quit = list(&ut, ut.ut_time, R_NORMAL);
740 * Set ut_type to the correct type.
742 if (strncmp(ut.ut_line, "~", 1) == 0) {
743 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
744 ut.ut_type = SHUTDOWN_TIME;
745 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
746 ut.ut_type = BOOT_TIME;
747 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
748 ut.ut_type = RUN_LVL;
752 * For stupid old applications that don't fill in
756 if (ut.ut_type != DEAD_PROCESS &&
757 ut.ut_name[0] && ut.ut_line[0] &&
758 strcmp(ut.ut_name, "LOGIN") != 0)
759 ut.ut_type = USER_PROCESS;
761 * Even worse, applications that write ghost
762 * entries: ut_type set to USER_PROCESS but
765 if (ut.ut_name[0] == 0)
766 ut.ut_type = DEAD_PROCESS;
771 if (strcmp(ut.ut_name, "date") == 0) {
772 if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
773 if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME;
778 switch (ut.ut_type) {
781 strcpy(ut.ut_line, "system down");
782 quit = list(&ut, lastboot, R_NORMAL);
784 lastdown = lastrch = ut.ut_time;
791 ut.ut_type == NEW_TIME ? "new time" :
793 quit = list(&ut, lastdown, R_TIMECHANGE);
797 strcpy(ut.ut_line, "system boot");
798 quit = list(&ut, lastdown, R_REBOOT);
799 lastboot = ut.ut_time;
805 sprintf(ut.ut_line, "(to lvl %c)", x);
806 quit = list(&ut, lastrch, R_NORMAL);
808 if (x == '0' || x == '6') {
809 lastdown = ut.ut_time;
811 ut.ut_type = SHUTDOWN_TIME;
813 lastrch = ut.ut_time;
818 * This was a login - show the first matching
819 * logout record and delete all records with
823 for (p = utmplist; p; p = next) {
825 if (strncmp(p->ut.ut_line, ut.ut_line,
829 quit = list(&ut, p->ut.ut_time,
833 if (p->next) p->next->prev = p->prev;
835 p->prev->next = p->next;
842 * Not found? Then crashed, down, still
843 * logged in, or missing logout record.
848 /* Is process still alive? */
850 kill(ut.ut_pid, 0) != 0 &&
855 quit = list(&ut, lastboot, c);
861 * Just store the data if it is
862 * interesting enough.
864 if (ut.ut_line[0] == 0)
866 if ((p = malloc(sizeof(struct utmplist))) == NULL) {
867 fprintf(stderr, "%s: out of memory\n",
871 memcpy(&p->ut, &ut, sizeof(struct utmp));
874 if (utmplist) utmplist->prev = p;
880 * If we saw a shutdown/reboot record we can remove
881 * the entire current utmplist.
884 lastboot = ut.ut_time;
885 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
886 for (p = utmplist; p; p = next) {
894 printf("\n%s begins %s", mybasename(ufile), ctime(&begintime));
899 * Should we free memory here? Nah. This is not NT :)