Improve message printed when signaling processes to stop. Patch from Matias A. Fonzo...
[sysvinit.git] / src / last.c
CommitLineData
a74aeac6
PR
1/*
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)
6 *
7 * Author: Miquel van Smoorenburg, miquels@cistron.nl
8 *
9 * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl
10 *
11 * This file is part of the sysvinit suite,
12 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 */
28
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <sys/fcntl.h>
32#include <time.h>
33#include <stdio.h>
34#include <ctype.h>
35#include <utmp.h>
36#include <errno.h>
37#include <malloc.h>
38#include <stdlib.h>
39#include <unistd.h>
40#include <string.h>
41#include <signal.h>
42#include <getopt.h>
43#include <netinet/in.h>
44#include <netdb.h>
45#include <arpa/inet.h>
46#include "oldutmp.h"
47
48#ifndef SHUTDOWN_TIME
49# define SHUTDOWN_TIME 254
50#endif
51
52char *Version = "@(#) last 2.85 31-Apr-2004 miquels";
53
54#define CHOP_DOMAIN 0 /* Define to chop off local domainname. */
55#define NEW_UTMP 1 /* Fancy & fast utmp read code. */
56#define UCHUNKSIZE 16384 /* How much we read at once. */
57
58/* Double linked list of struct utmp's */
59struct utmplist {
60 struct utmp ut;
61 struct utmplist *next;
62 struct utmplist *prev;
63};
64struct utmplist *utmplist = NULL;
65
66/* Types of listing */
67#define R_CRASH 1 /* No logout record, system boot in between */
68#define R_DOWN 2 /* System brought down in decent way */
69#define R_NORMAL 3 /* Normal */
70#define R_NOW 4 /* Still logged in */
71#define R_REBOOT 5 /* Reboot record. */
72#define R_PHANTOM 6 /* No logout record but session is stale. */
73#define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */
74
75/* Global variables */
76int maxrecs = 0; /* Maximum number of records to list. */
77int recsdone = 0; /* Number of records listed */
78int showhost = 1; /* Show hostname too? */
79int altlist = 0; /* Show hostname at the end. */
80int usedns = 0; /* Use DNS to lookup the hostname. */
81int useip = 0; /* Print IP address in number format */
82int fulltime = 0; /* Print full dates and times */
1414572e
DWF
83int name_len = 8; /* Default print 8 characters of name */
84int domain_len = 16; /* Default print 16 characters of domain */
a74aeac6
PR
85int oldfmt = 0; /* Use old libc5 format? */
86char **show = NULL; /* What do they want us to show */
87char *ufile; /* Filename of this file */
88time_t lastdate; /* Last date we've seen */
89char *progname; /* Name of this program */
90#if CHOP_DOMAIN
91char hostname[256]; /* For gethostbyname() */
92char *domainname; /* Our domainname. */
93#endif
94
95/*
96 * Convert old utmp format to new.
97 */
98void uconv(struct oldutmp *oldut, struct utmp *utn)
99{
100 memset(utn, 0, sizeof(struct utmp));
101 utn->ut_type = oldut->ut_type;
102 utn->ut_pid = oldut->ut_pid;
103 utn->ut_time = oldut->ut_oldtime;
104 utn->ut_addr = oldut->ut_oldaddr;
105 strncpy(utn->ut_line, oldut->ut_line, OLD_LINESIZE);
106 strncpy(utn->ut_user, oldut->ut_user, OLD_NAMESIZE);
107 strncpy(utn->ut_host, oldut->ut_host, OLD_HOSTSIZE);
108}
109
110#if NEW_UTMP
111/*
112 * Read one utmp entry, return in new format.
113 * Automatically reposition file pointer.
114 */
115int uread(FILE *fp, struct utmp *u, int *quit)
116{
117 static int utsize;
118 static char buf[UCHUNKSIZE];
119 char tmp[1024];
120 static off_t fpos;
121 static int bpos;
122 struct oldutmp uto;
123 int r;
124 off_t o;
125
126 if (quit == NULL && u != NULL) {
127 /*
128 * Normal read.
129 */
130 if (oldfmt) {
131 r = fread(&uto, sizeof(uto), 1, fp);
132 uconv(&uto, u);
133 } else
134 r = fread(u, sizeof(struct utmp), 1, fp);
135 return r;
136 }
137
138 if (u == NULL) {
139 /*
140 * Initialize and position.
141 */
142 utsize = oldfmt ? sizeof(uto) : sizeof(struct utmp);
143 fseeko(fp, 0, SEEK_END);
144 fpos = ftello(fp);
145 if (fpos == 0)
146 return 0;
147 o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
148 if (fseeko(fp, o, SEEK_SET) < 0) {
149 fprintf(stderr, "%s: seek failed!\n", progname);
150 return 0;
151 }
152 bpos = (int)(fpos - o);
153 if (fread(buf, bpos, 1, fp) != 1) {
154 fprintf(stderr, "%s: read failed!\n", progname);
155 return 0;
156 }
157 fpos = o;
158 return 1;
159 }
160
161 /*
162 * Read one struct. From the buffer if possible.
163 */
164 bpos -= utsize;
165 if (bpos >= 0) {
166 if (oldfmt)
167 uconv((struct oldutmp *)(buf + bpos), u);
168 else
169 memcpy(u, buf + bpos, sizeof(struct utmp));
170 return 1;
171 }
172
173 /*
174 * Oops we went "below" the buffer. We should be able to
175 * seek back UCHUNKSIZE bytes.
176 */
177 fpos -= UCHUNKSIZE;
178 if (fpos < 0)
179 return 0;
180
181 /*
182 * Copy whatever is left in the buffer.
183 */
184 memcpy(tmp + (-bpos), buf, utsize + bpos);
185 if (fseeko(fp, fpos, SEEK_SET) < 0) {
186 perror("fseek");
187 return 0;
188 }
189
190 /*
191 * Read another UCHUNKSIZE bytes.
192 */
193 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
194 perror("fread");
195 return 0;
196 }
197
198 /*
199 * The end of the UCHUNKSIZE byte buffer should be the first
200 * few bytes of the current struct utmp.
201 */
202 memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
203 bpos += UCHUNKSIZE;
204
205 if (oldfmt)
206 uconv((struct oldutmp *)tmp, u);
207 else
208 memcpy(u, tmp, sizeof(struct utmp));
209
210 return 1;
211}
212
213#else /* NEW_UTMP */
214
215/*
216 * Read one utmp entry, return in new format.
217 * Automatically reposition file pointer.
218 */
219int uread(FILE *fp, struct utmp *u, int *quit)
220{
221 struct oldutmp uto;
222 off_t r;
223
224 if (u == NULL) {
225 r = oldfmt ? sizeof(struct oldutmp) : sizeof(struct utmp);
226 fseek(fp, -1 * r, SEEK_END);
227 return 1;
228 }
229
230 if (!oldfmt) {
231 r = fread(u, sizeof(struct utmp), 1, fp);
232 if (r == 1) {
233 if (fseeko(fp, -2 * sizeof(struct utmp), SEEK_CUR) < 0)
234 if (quit) *quit = 1;
235 }
236 return r;
237 }
238 r = fread(&uto, sizeof(struct oldutmp), 1, fp);
239 if (r == 1) {
240 if (fseeko(fp, -2 * sizeof(struct oldutmp), SEEK_CUR) < 0)
241 if (quit) *quit = 1;
242 uconv(&uto, u);
243 }
244
245 return r;
246}
247#endif
248
249/*
250 * Try to be smart about the location of the BTMP file
251 */
252#ifndef BTMP_FILE
253#define BTMP_FILE getbtmp()
254char *getbtmp()
255{
256 static char btmp[128];
257 char *p;
258
259 strcpy(btmp, WTMP_FILE);
260 if ((p = strrchr(btmp, '/')) == NULL)
261 p = btmp;
262 else
263 p++;
264 *p = 0;
265 strcat(btmp, "btmp");
266 return btmp;
267}
268#endif
269
270/*
271 * Print a short date.
272 */
273char *showdate()
274{
275 char *s = ctime(&lastdate);
276 s[16] = 0;
277 return s;
278}
279
280/*
281 * SIGINT handler
282 */
283void int_handler()
284{
285 printf("Interrupted %s\n", showdate());
286 exit(1);
287}
288
289/*
290 * SIGQUIT handler
291 */
292void quit_handler()
293{
294 printf("Interrupted %s\n", showdate());
295 signal(SIGQUIT, quit_handler);
296}
297
298/*
299 * Get the basename of a filename
300 */
301char *mybasename(char *s)
302{
303 char *p;
304
305 if ((p = strrchr(s, '/')) != NULL)
306 p++;
307 else
308 p = s;
309 return p;
310}
311
312/*
313 * Lookup a host with DNS.
314 */
315int dns_lookup(char *result, int size, int useip, int32_t *a)
316{
317 struct sockaddr_in sin;
318 struct sockaddr_in6 sin6;
319 struct sockaddr *sa;
320 int salen, flags;
a74aeac6
PR
321 int mapped = 0;
322
323 flags = useip ? NI_NUMERICHOST : 0;
324
325 /*
82111852
DWF
326 * IPv4 or IPv6 ?
327 * 1. If last 3 4bytes are 0, must be IPv4
328 * 2. If IPv6 in IPv4, handle as IPv4
329 * 3. Anything else is IPv6
a74aeac6
PR
330 *
331 * Ugly.
332 */
192c4567 333 if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff))
a74aeac6 334 mapped = 1;
a74aeac6 335
82111852 336 if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
a74aeac6
PR
337 /* IPv4 */
338 sin.sin_family = AF_INET;
339 sin.sin_port = 0;
340 sin.sin_addr.s_addr = mapped ? a[3] : a[0];
341 sa = (struct sockaddr *)&sin;
342 salen = sizeof(sin);
343 } else {
344 /* IPv6 */
345 memset(&sin6, 0, sizeof(sin6));
346 sin6.sin6_family = AF_INET6;
347 sin6.sin6_port = 0;
348 memcpy(sin6.sin6_addr.s6_addr, a, 16);
349 sa = (struct sockaddr *)&sin6;
350 salen = sizeof(sin6);
351 }
352
353 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
354}
355
356/*
357 * Show one line of information on screen
358 */
359int list(struct utmp *p, time_t t, int what)
360{
361 time_t secs, tmp;
362 char logintime[32];
363 char logouttime[32];
364 char length[32];
1414572e 365 char final[512];
a74aeac6
PR
366 char utline[UT_LINESIZE+1];
367 char domain[256];
368 char *s, **walk;
369 int mins, hours, days;
370 int r, len;
371
372 /*
373 * uucp and ftp have special-type entries
374 */
375 utline[0] = 0;
376 strncat(utline, p->ut_line, UT_LINESIZE);
377 if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
378 utline[3] = 0;
379 if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
380 utline[4] = 0;
381
382 /*
383 * Is this something we wanna show?
384 */
385 if (show) {
386 for (walk = show; *walk; walk++) {
387 if (strncmp(p->ut_name, *walk, UT_NAMESIZE) == 0 ||
388 strcmp(utline, *walk) == 0 ||
389 (strncmp(utline, "tty", 3) == 0 &&
390 strcmp(utline + 3, *walk) == 0)) break;
391 }
392 if (*walk == NULL) return 0;
393 }
394
395 /*
396 * Calculate times
397 */
398 tmp = (time_t)p->ut_time;
399 strcpy(logintime, ctime(&tmp));
400 if (fulltime)
401 sprintf(logouttime, "- %s", ctime(&t));
402 else {
403 logintime[16] = 0;
404 sprintf(logouttime, "- %s", ctime(&t) + 11);
405 logouttime[7] = 0;
406 }
407 secs = t - p->ut_time;
408 mins = (secs / 60) % 60;
409 hours = (secs / 3600) % 24;
410 days = secs / 86400;
411 if (days)
412 sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
413 else
414 sprintf(length, " (%02d:%02d)", hours, mins);
415
416 switch(what) {
417 case R_CRASH:
418 sprintf(logouttime, "- crash");
419 break;
420 case R_DOWN:
421 sprintf(logouttime, "- down ");
422 break;
423 case R_NOW:
424 length[0] = 0;
425 if (fulltime)
426 sprintf(logouttime, " still logged in");
427 else {
428 sprintf(logouttime, " still");
429 sprintf(length, "logged in");
430 }
431 break;
432 case R_PHANTOM:
433 length[0] = 0;
434 if (fulltime)
435 sprintf(logouttime, " gone - no logout");
436 else {
437 sprintf(logouttime, " gone");
438 sprintf(length, "- no logout");
439 }
440 break;
441 case R_REBOOT:
442 break;
443 case R_TIMECHANGE:
444 logouttime[0] = 0;
445 length[0] = 0;
446 break;
447 case R_NORMAL:
448 break;
449 }
450
451 /*
452 * Look up host with DNS if needed.
453 */
454 r = -1;
455 if (usedns || useip)
456 r = dns_lookup(domain, sizeof(domain), useip, p->ut_addr_v6);
457 if (r < 0) {
458 len = UT_HOSTSIZE;
192c4567 459 if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
a74aeac6
PR
460 domain[0] = 0;
461 strncat(domain, p->ut_host, len);
462 }
463
464 if (showhost) {
465#if CHOP_DOMAIN
466 /*
467 * See if this is in our domain.
468 */
469 if (!usedns && (s = strchr(p->ut_host, '.')) != NULL &&
470 strcmp(s + 1, domainname) == 0) *s = 0;
471#endif
472 if (!altlist) {
63a2c981 473 len = snprintf(final, sizeof(final),
a74aeac6 474 fulltime ?
1414572e
DWF
475 "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
476 "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
477 name_len, p->ut_name, utline,
478 domain_len, domain, logintime, logouttime, length);
a74aeac6 479 } else {
63a2c981 480 len = snprintf(final, sizeof(final),
a74aeac6 481 fulltime ?
1414572e
DWF
482 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
483 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
484 name_len, p->ut_name, utline,
a74aeac6
PR
485 logintime, logouttime, length, domain);
486 }
487 } else
63a2c981 488 len = snprintf(final, sizeof(final),
a74aeac6 489 fulltime ?
1414572e
DWF
490 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
491 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
492 name_len, p->ut_name, utline,
a74aeac6
PR
493 logintime, logouttime, length);
494
63a2c981
DWF
495#if defined(__GLIBC__)
496# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
497 final[sizeof(final)-1] = '\0';
498# endif
499#endif
500
a74aeac6
PR
501 /*
502 * Print out "final" string safely.
503 */
504 for (s = final; *s; s++) {
505 if (*s == '\n' || (*s >= 32 && (unsigned char)*s <= 126))
506 putchar(*s);
507 else
508 putchar('*');
509 }
510
1414572e 511 if (len < 0 || (size_t)len >= sizeof(final))
63a2c981
DWF
512 putchar('\n');
513
a74aeac6
PR
514 recsdone++;
515 if (maxrecs && recsdone >= maxrecs)
516 return 1;
517
518 return 0;
519}
520
521
522/*
523 * show usage
524 */
525void usage(char *s)
526{
527 fprintf(stderr, "Usage: %s [-num | -n num] [-f file] "
528 "[-t YYYYMMDDHHMMSS] "
1414572e 529 "[-R] [-adioxFw] [username..] [tty..]\n", s);
a74aeac6
PR
530 exit(1);
531}
532
533time_t parsetm(char *ts)
534{
535 struct tm u, origu;
536 time_t tm;
537
538 memset(&tm, 0, sizeof(tm));
539
540 if (sscanf(ts, "%4d%2d%2d%2d%2d%2d", &u.tm_year,
541 &u.tm_mon, &u.tm_mday, &u.tm_hour, &u.tm_min,
542 &u.tm_sec) != 6)
543 return (time_t)-1;
544
545 u.tm_year -= 1900;
546 u.tm_mon -= 1;
547 u.tm_isdst = -1;
548
549 origu = u;
550
551 if ((tm = mktime(&u)) == (time_t)-1)
552 return tm;
553
554 /*
555 * Unfortunately mktime() is much more forgiving than
556 * it should be. For example, it'll gladly accept
557 * "30" as a valid month number. This behavior is by
558 * design, but we don't like it, so we want to detect
559 * it and complain.
560 */
561 if (u.tm_year != origu.tm_year ||
562 u.tm_mon != origu.tm_mon ||
563 u.tm_mday != origu.tm_mday ||
564 u.tm_hour != origu.tm_hour ||
565 u.tm_min != origu.tm_min ||
566 u.tm_sec != origu.tm_sec)
567 return (time_t)-1;
568
569 return tm;
570}
571
572int main(int argc, char **argv)
573{
574 FILE *fp; /* Filepointer of wtmp file */
575
576 struct utmp ut; /* Current utmp entry */
577 struct utmp oldut; /* Old utmp entry to check for duplicates */
578 struct utmplist *p; /* Pointer into utmplist */
579 struct utmplist *next;/* Pointer into utmplist */
580
581 time_t lastboot = 0; /* Last boottime */
582 time_t lastrch = 0; /* Last run level change */
583 time_t lastdown; /* Last downtime */
584 time_t begintime; /* When wtmp begins */
585 int whydown = 0; /* Why we went down: crash or shutdown */
586
587 int c, x; /* Scratch */
588 struct stat st; /* To stat the [uw]tmp file */
589 int quit = 0; /* Flag */
590 int down = 0; /* Down flag */
591 int lastb = 0; /* Is this 'lastb' ? */
592 int extended = 0; /* Lots of info. */
593 char *altufile = NULL;/* Alternate wtmp */
594
595 time_t until = 0; /* at what time to stop parsing the file */
596
597 progname = mybasename(argv[0]);
598
599 /* Process the arguments. */
1414572e 600 while((c = getopt(argc, argv, "f:n:RxadFiot:0123456789w")) != EOF)
a74aeac6
PR
601 switch(c) {
602 case 'R':
603 showhost = 0;
604 break;
605 case 'x':
606 extended = 1;
607 break;
608 case 'n':
609 maxrecs = atoi(optarg);
610 break;
611 case 'o':
612 oldfmt = 1;
613 break;
614 case 'f':
615 if((altufile = malloc(strlen(optarg)+1)) == NULL) {
616 fprintf(stderr, "%s: out of memory\n",
617 progname);
618 exit(1);
619 }
620 strcpy(altufile, optarg);
621 break;
622 case 'd':
623 usedns++;
624 break;
625 case 'i':
626 useip++;
627 break;
628 case 'a':
629 altlist++;
630 break;
631 case 'F':
632 fulltime++;
633 break;
634 case 't':
635 if ((until = parsetm(optarg)) == (time_t)-1) {
636 fprintf(stderr, "%s: Invalid time value \"%s\"\n",
637 progname, optarg);
638 usage(progname);
639 }
640 break;
1414572e
DWF
641 case 'w':
642 if (UT_NAMESIZE > name_len)
643 name_len = UT_NAMESIZE;
644 if (UT_HOSTSIZE > domain_len)
645 domain_len = UT_HOSTSIZE;
646 break;
a74aeac6
PR
647 case '0': case '1': case '2': case '3': case '4':
648 case '5': case '6': case '7': case '8': case '9':
649 maxrecs = 10*maxrecs + c - '0';
650 break;
651 default:
652 usage(progname);
653 break;
654 }
655 if (optind < argc) show = argv + optind;
656
657 /*
658 * Which file do we want to read?
659 */
660 if (strcmp(progname, "lastb") == 0) {
661 ufile = BTMP_FILE;
662 lastb = 1;
663 } else
664 ufile = WTMP_FILE;
665 if (altufile)
666 ufile = altufile;
667 time(&lastdown);
668 lastrch = lastdown;
669
670 /*
671 * Fill in 'lastdate'
672 */
673 lastdate = lastdown;
674
675#if CHOP_DOMAIN
676 /*
677 * Find out domainname.
678 *
679 * This doesn't work on modern systems, where only a DNS
680 * lookup of the result from hostname() will get you the domainname.
681 * Remember that domainname() is the NIS domainname, not DNS.
682 * So basically this whole piece of code is bullshit.
683 */
684 hostname[0] = 0;
685 (void) gethostname(hostname, sizeof(hostname));
686 if ((domainname = strchr(hostname, '.')) != NULL) domainname++;
687 if (domainname == NULL || domainname[0] == 0) {
688 hostname[0] = 0;
689 (void) getdomainname(hostname, sizeof(hostname));
690 hostname[sizeof(hostname) - 1] = 0;
691 domainname = hostname;
692 if (strcmp(domainname, "(none)") == 0 || domainname[0] == 0)
693 domainname = NULL;
694 }
695#endif
696
697 /*
698 * Install signal handlers
699 */
700 signal(SIGINT, int_handler);
701 signal(SIGQUIT, quit_handler);
702
703 /*
704 * Open the utmp file
705 */
706 if ((fp = fopen(ufile, "r")) == NULL) {
707 x = errno;
708 fprintf(stderr, "%s: %s: %s\n", progname, ufile, strerror(errno));
709 if (altufile == NULL && x == ENOENT)
710 fprintf(stderr, "Perhaps this file was removed by the "
711 "operator to prevent logging %s info.\n", progname);
712 exit(1);
713 }
714
715 /*
716 * Optimize the buffer size.
717 */
718 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
719
720 /*
721 * Read first structure to capture the time field
722 */
723 if (uread(fp, &ut, NULL) == 1)
724 begintime = ut.ut_time;
725 else {
726 fstat(fileno(fp), &st);
727 begintime = st.st_ctime;
728 quit = 1;
729 }
730
731 /*
732 * Go to end of file minus one structure
733 * and/or initialize utmp reading code.
734 */
735 uread(fp, NULL, NULL);
736
737 /*
738 * Read struct after struct backwards from the file.
739 */
740 while(!quit) {
741
742 if (uread(fp, &ut, &quit) != 1)
743 break;
744
745 if (until && until < ut.ut_time)
746 continue;
747
748 if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue;
749 memcpy(&oldut, &ut, sizeof(struct utmp));
750 lastdate = ut.ut_time;
751
752 if (lastb) {
753 quit = list(&ut, ut.ut_time, R_NORMAL);
754 continue;
755 }
756
757 /*
758 * Set ut_type to the correct type.
759 */
760 if (strncmp(ut.ut_line, "~", 1) == 0) {
761 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
762 ut.ut_type = SHUTDOWN_TIME;
763 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
764 ut.ut_type = BOOT_TIME;
765 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
766 ut.ut_type = RUN_LVL;
767 }
768#if 1 /*def COMPAT*/
769 /*
770 * For stupid old applications that don't fill in
771 * ut_type correctly.
772 */
773 else {
774 if (ut.ut_type != DEAD_PROCESS &&
775 ut.ut_name[0] && ut.ut_line[0] &&
776 strcmp(ut.ut_name, "LOGIN") != 0)
777 ut.ut_type = USER_PROCESS;
778 /*
779 * Even worse, applications that write ghost
780 * entries: ut_type set to USER_PROCESS but
781 * empty ut_name...
782 */
783 if (ut.ut_name[0] == 0)
784 ut.ut_type = DEAD_PROCESS;
785
786 /*
787 * Clock changes.
788 */
789 if (strcmp(ut.ut_name, "date") == 0) {
790 if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
791 if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME;
792 }
793 }
794#endif
795
796 switch (ut.ut_type) {
797 case SHUTDOWN_TIME:
798 if (extended) {
799 strcpy(ut.ut_line, "system down");
800 quit = list(&ut, lastboot, R_NORMAL);
801 }
802 lastdown = lastrch = ut.ut_time;
803 down = 1;
804 break;
805 case OLD_TIME:
806 case NEW_TIME:
807 if (extended) {
808 strcpy(ut.ut_line,
809 ut.ut_type == NEW_TIME ? "new time" :
810 "old time");
811 quit = list(&ut, lastdown, R_TIMECHANGE);
812 }
813 break;
814 case BOOT_TIME:
815 strcpy(ut.ut_line, "system boot");
816 quit = list(&ut, lastdown, R_REBOOT);
817 lastboot = ut.ut_time;
818 down = 1;
819 break;
820 case RUN_LVL:
821 x = ut.ut_pid & 255;
822 if (extended) {
823 sprintf(ut.ut_line, "(to lvl %c)", x);
824 quit = list(&ut, lastrch, R_NORMAL);
825 }
826 if (x == '0' || x == '6') {
827 lastdown = ut.ut_time;
828 down = 1;
829 ut.ut_type = SHUTDOWN_TIME;
830 }
831 lastrch = ut.ut_time;
832 break;
833
834 case USER_PROCESS:
835 /*
836 * This was a login - show the first matching
837 * logout record and delete all records with
838 * the same ut_line.
839 */
840 c = 0;
841 for (p = utmplist; p; p = next) {
842 next = p->next;
843 if (strncmp(p->ut.ut_line, ut.ut_line,
844 UT_LINESIZE) == 0) {
845 /* Show it */
846 if (c == 0) {
847 quit = list(&ut, p->ut.ut_time,
848 R_NORMAL);
849 c = 1;
850 }
851 if (p->next) p->next->prev = p->prev;
852 if (p->prev)
853 p->prev->next = p->next;
854 else
855 utmplist = p->next;
856 free(p);
857 }
858 }
859 /*
860 * Not found? Then crashed, down, still
861 * logged in, or missing logout record.
862 */
863 if (c == 0) {
864 if (lastboot == 0) {
865 c = R_NOW;
866 /* Is process still alive? */
867 if (ut.ut_pid > 0 &&
868 kill(ut.ut_pid, 0) != 0 &&
869 errno == ESRCH)
870 c = R_PHANTOM;
871 } else
872 c = whydown;
873 quit = list(&ut, lastboot, c);
874 }
875 /* FALLTHRU */
876
877 case DEAD_PROCESS:
878 /*
879 * Just store the data if it is
880 * interesting enough.
881 */
882 if (ut.ut_line[0] == 0)
883 break;
884 if ((p = malloc(sizeof(struct utmplist))) == NULL) {
885 fprintf(stderr, "%s: out of memory\n",
886 progname);
887 exit(1);
888 }
889 memcpy(&p->ut, &ut, sizeof(struct utmp));
890 p->next = utmplist;
891 p->prev = NULL;
892 if (utmplist) utmplist->prev = p;
893 utmplist = p;
894 break;
895
896 }
897 /*
898 * If we saw a shutdown/reboot record we can remove
899 * the entire current utmplist.
900 */
901 if (down) {
902 lastboot = ut.ut_time;
903 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
904 for (p = utmplist; p; p = next) {
905 next = p->next;
906 free(p);
907 }
908 utmplist = NULL;
909 down = 0;
910 }
911 }
912 printf("\n%s begins %s", mybasename(ufile), ctime(&begintime));
913
914 fclose(fp);
915
916 /*
917 * Should we free memory here? Nah. This is not NT :)
918 */
919 return 0;
920}