Include -Wunreachable-code when building.
[sysvinit.git] / src / last.c
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 <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
52 char *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 */
59 struct utmplist {
60 struct utmp ut;
61 struct utmplist *next;
62 struct utmplist *prev;
63 };
64 struct 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 */
76 int maxrecs = 0; /* Maximum number of records to list. */
77 int recsdone = 0; /* Number of records listed */
78 int showhost = 1; /* Show hostname too? */
79 int altlist = 0; /* Show hostname at the end. */
80 int usedns = 0; /* Use DNS to lookup the hostname. */
81 int useip = 0; /* Print IP address in number format */
82 int fulltime = 0; /* Print full dates and times */
83 int name_len = 8; /* Default print 8 characters of name */
84 int domain_len = 16; /* Default print 16 characters of domain */
85 int oldfmt = 0; /* Use old libc5 format? */
86 char **show = NULL; /* What do they want us to show */
87 char *ufile; /* Filename of this file */
88 time_t lastdate; /* Last date we've seen */
89 char *progname; /* Name of this program */
90 #if CHOP_DOMAIN
91 char hostname[256]; /* For gethostbyname() */
92 char *domainname; /* Our domainname. */
93 #endif
94
95 /*
96 * Convert old utmp format to new.
97 */
98 void 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 */
115 int 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 */
219 int 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()
254 char *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 */
273 char *showdate()
274 {
275 char *s = ctime(&lastdate);
276 s[16] = 0;
277 return s;
278 }
279
280 /*
281 * SIGINT handler
282 */
283 void int_handler()
284 {
285 printf("Interrupted %s\n", showdate());
286 exit(1);
287 }
288
289 /*
290 * SIGQUIT handler
291 */
292 void 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 */
301 char *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 */
315 int 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;
321 int mapped = 0;
322
323 flags = useip ? NI_NUMERICHOST : 0;
324
325 /*
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
330 *
331 * Ugly.
332 */
333 if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff))
334 mapped = 1;
335
336 if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
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 */
359 int 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];
365 char final[512];
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 strncpy(logintime, ctime(&tmp), sizeof(logintime));
400 logintime[sizeof(logintime)-1] = 0; /* enforce null termination */
401 if (fulltime)
402 sprintf(logouttime, "- %s", ctime(&t));
403 else {
404 logintime[16] = 0;
405 sprintf(logouttime, "- %s", ctime(&t) + 11);
406 logouttime[7] = 0;
407 }
408 secs = t - p->ut_time;
409 mins = (secs / 60) % 60;
410 hours = (secs / 3600) % 24;
411 days = secs / 86400;
412 if (days)
413 sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
414 else
415 sprintf(length, " (%02d:%02d)", hours, mins);
416
417 switch(what) {
418 case R_CRASH:
419 sprintf(logouttime, "- crash");
420 break;
421 case R_DOWN:
422 sprintf(logouttime, "- down ");
423 break;
424 case R_NOW:
425 length[0] = 0;
426 if (fulltime)
427 sprintf(logouttime, " still logged in");
428 else {
429 sprintf(logouttime, " still");
430 sprintf(length, "logged in");
431 }
432 break;
433 case R_PHANTOM:
434 length[0] = 0;
435 if (fulltime)
436 sprintf(logouttime, " gone - no logout");
437 else {
438 sprintf(logouttime, " gone");
439 sprintf(length, "- no logout");
440 }
441 break;
442 case R_REBOOT:
443 break;
444 case R_TIMECHANGE:
445 logouttime[0] = 0;
446 length[0] = 0;
447 break;
448 case R_NORMAL:
449 break;
450 }
451
452 /*
453 * Look up host with DNS if needed.
454 */
455 r = -1;
456 if (usedns || useip)
457 r = dns_lookup(domain, sizeof(domain), useip, p->ut_addr_v6);
458 if (r < 0) {
459 len = UT_HOSTSIZE;
460 if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
461 domain[0] = 0;
462 strncat(domain, p->ut_host, len);
463 }
464
465 if (showhost) {
466 #if CHOP_DOMAIN
467 /*
468 * See if this is in our domain.
469 */
470 if (!usedns && (s = strchr(p->ut_host, '.')) != NULL &&
471 strcmp(s + 1, domainname) == 0) *s = 0;
472 #endif
473 if (!altlist) {
474 len = snprintf(final, sizeof(final),
475 fulltime ?
476 "%-8.*s %-12.12s %-16.*s %-24.24s %-26.26s %-12.12s\n" :
477 "%-8.*s %-12.12s %-16.*s %-16.16s %-7.7s %-12.12s\n",
478 name_len, p->ut_name, utline,
479 domain_len, domain, logintime, logouttime, length);
480 } else {
481 len = snprintf(final, sizeof(final),
482 fulltime ?
483 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s %s\n" :
484 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s %s\n",
485 name_len, p->ut_name, utline,
486 logintime, logouttime, length, domain);
487 }
488 } else
489 len = snprintf(final, sizeof(final),
490 fulltime ?
491 "%-8.*s %-12.12s %-24.24s %-26.26s %-12.12s\n" :
492 "%-8.*s %-12.12s %-16.16s %-7.7s %-12.12s\n",
493 name_len, p->ut_name, utline,
494 logintime, logouttime, length);
495
496 #if defined(__GLIBC__)
497 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
498 final[sizeof(final)-1] = '\0';
499 # endif
500 #endif
501
502 /*
503 * Print out "final" string safely.
504 */
505 for (s = final; *s; s++) {
506 if (*s == '\n' || (*s >= 32 && (unsigned char)*s <= 126))
507 putchar(*s);
508 else
509 putchar('*');
510 }
511
512 if (len < 0 || (size_t)len >= sizeof(final))
513 putchar('\n');
514
515 recsdone++;
516 if (maxrecs && recsdone >= maxrecs)
517 return 1;
518
519 return 0;
520 }
521
522
523 /*
524 * show usage
525 */
526 void usage(char *s)
527 {
528 fprintf(stderr, "Usage: %s [-num | -n num] [-f file] "
529 "[-t YYYYMMDDHHMMSS] "
530 "[-R] [-adioxFw] [username..] [tty..]\n", s);
531 exit(1);
532 }
533
534 time_t parsetm(char *ts)
535 {
536 struct tm u, origu;
537 time_t tm;
538
539 memset(&tm, 0, sizeof(tm));
540
541 if (sscanf(ts, "%4d%2d%2d%2d%2d%2d", &u.tm_year,
542 &u.tm_mon, &u.tm_mday, &u.tm_hour, &u.tm_min,
543 &u.tm_sec) != 6)
544 return (time_t)-1;
545
546 u.tm_year -= 1900;
547 u.tm_mon -= 1;
548 u.tm_isdst = -1;
549
550 origu = u;
551
552 if ((tm = mktime(&u)) == (time_t)-1)
553 return tm;
554
555 /*
556 * Unfortunately mktime() is much more forgiving than
557 * it should be. For example, it'll gladly accept
558 * "30" as a valid month number. This behavior is by
559 * design, but we don't like it, so we want to detect
560 * it and complain.
561 */
562 if (u.tm_year != origu.tm_year ||
563 u.tm_mon != origu.tm_mon ||
564 u.tm_mday != origu.tm_mday ||
565 u.tm_hour != origu.tm_hour ||
566 u.tm_min != origu.tm_min ||
567 u.tm_sec != origu.tm_sec)
568 return (time_t)-1;
569
570 return tm;
571 }
572
573 int main(int argc, char **argv)
574 {
575 FILE *fp; /* Filepointer of wtmp file */
576
577 struct utmp ut; /* Current utmp entry */
578 struct utmp oldut; /* Old utmp entry to check for duplicates */
579 struct utmplist *p; /* Pointer into utmplist */
580 struct utmplist *next;/* Pointer into utmplist */
581
582 time_t lastboot = 0; /* Last boottime */
583 time_t lastrch = 0; /* Last run level change */
584 time_t lastdown; /* Last downtime */
585 time_t begintime; /* When wtmp begins */
586 int whydown = 0; /* Why we went down: crash or shutdown */
587
588 int c, x; /* Scratch */
589 struct stat st; /* To stat the [uw]tmp file */
590 int quit = 0; /* Flag */
591 int down = 0; /* Down flag */
592 int lastb = 0; /* Is this 'lastb' ? */
593 int extended = 0; /* Lots of info. */
594 char *altufile = NULL;/* Alternate wtmp */
595
596 time_t until = 0; /* at what time to stop parsing the file */
597
598 progname = mybasename(argv[0]);
599
600 /* Process the arguments. */
601 while((c = getopt(argc, argv, "f:n:RxadFiot:0123456789w")) != EOF)
602 switch(c) {
603 case 'R':
604 showhost = 0;
605 break;
606 case 'x':
607 extended = 1;
608 break;
609 case 'n':
610 maxrecs = atoi(optarg);
611 break;
612 case 'o':
613 oldfmt = 1;
614 break;
615 case 'f':
616 if((altufile = malloc(strlen(optarg)+1)) == NULL) {
617 fprintf(stderr, "%s: out of memory\n",
618 progname);
619 exit(1);
620 }
621 strcpy(altufile, optarg);
622 break;
623 case 'd':
624 usedns++;
625 break;
626 case 'i':
627 useip++;
628 break;
629 case 'a':
630 altlist++;
631 break;
632 case 'F':
633 fulltime++;
634 break;
635 case 't':
636 if ((until = parsetm(optarg)) == (time_t)-1) {
637 fprintf(stderr, "%s: Invalid time value \"%s\"\n",
638 progname, optarg);
639 usage(progname);
640 }
641 break;
642 case 'w':
643 if (UT_NAMESIZE > name_len)
644 name_len = UT_NAMESIZE;
645 if (UT_HOSTSIZE > domain_len)
646 domain_len = UT_HOSTSIZE;
647 break;
648 case '0': case '1': case '2': case '3': case '4':
649 case '5': case '6': case '7': case '8': case '9':
650 maxrecs = 10*maxrecs + c - '0';
651 break;
652 default:
653 usage(progname);
654 break;
655 }
656 if (optind < argc) show = argv + optind;
657
658 /*
659 * Which file do we want to read?
660 */
661 if (strcmp(progname, "lastb") == 0) {
662 ufile = BTMP_FILE;
663 lastb = 1;
664 } else
665 ufile = WTMP_FILE;
666 if (altufile)
667 ufile = altufile;
668 time(&lastdown);
669 lastrch = lastdown;
670
671 /*
672 * Fill in 'lastdate'
673 */
674 lastdate = lastdown;
675
676 #if CHOP_DOMAIN
677 /*
678 * Find out domainname.
679 *
680 * This doesn't work on modern systems, where only a DNS
681 * lookup of the result from hostname() will get you the domainname.
682 * Remember that domainname() is the NIS domainname, not DNS.
683 * So basically this whole piece of code is bullshit.
684 */
685 hostname[0] = 0;
686 (void) gethostname(hostname, sizeof(hostname));
687 if ((domainname = strchr(hostname, '.')) != NULL) domainname++;
688 if (domainname == NULL || domainname[0] == 0) {
689 hostname[0] = 0;
690 (void) getdomainname(hostname, sizeof(hostname));
691 hostname[sizeof(hostname) - 1] = 0;
692 domainname = hostname;
693 if (strcmp(domainname, "(none)") == 0 || domainname[0] == 0)
694 domainname = NULL;
695 }
696 #endif
697
698 /*
699 * Install signal handlers
700 */
701 signal(SIGINT, int_handler);
702 signal(SIGQUIT, quit_handler);
703
704 /*
705 * Open the utmp file
706 */
707 if ((fp = fopen(ufile, "r")) == NULL) {
708 x = errno;
709 fprintf(stderr, "%s: %s: %s\n", progname, ufile, strerror(errno));
710 if (altufile == NULL && x == ENOENT)
711 fprintf(stderr, "Perhaps this file was removed by the "
712 "operator to prevent logging %s info.\n", progname);
713 exit(1);
714 }
715
716 /*
717 * Optimize the buffer size.
718 */
719 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
720
721 /*
722 * Read first structure to capture the time field
723 */
724 if (uread(fp, &ut, NULL) == 1)
725 begintime = ut.ut_time;
726 else {
727 fstat(fileno(fp), &st);
728 begintime = st.st_ctime;
729 quit = 1;
730 }
731
732 /*
733 * Go to end of file minus one structure
734 * and/or initialize utmp reading code.
735 */
736 uread(fp, NULL, NULL);
737
738 /*
739 * Read struct after struct backwards from the file.
740 */
741 while(!quit) {
742
743 if (uread(fp, &ut, &quit) != 1)
744 break;
745
746 if (until && until < ut.ut_time)
747 continue;
748
749 if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue;
750 memcpy(&oldut, &ut, sizeof(struct utmp));
751 lastdate = ut.ut_time;
752
753 if (lastb) {
754 quit = list(&ut, ut.ut_time, R_NORMAL);
755 continue;
756 }
757
758 /*
759 * Set ut_type to the correct type.
760 */
761 if (strncmp(ut.ut_line, "~", 1) == 0) {
762 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
763 ut.ut_type = SHUTDOWN_TIME;
764 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
765 ut.ut_type = BOOT_TIME;
766 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
767 ut.ut_type = RUN_LVL;
768 }
769 #if 1 /*def COMPAT*/
770 /*
771 * For stupid old applications that don't fill in
772 * ut_type correctly.
773 */
774 else {
775 if (ut.ut_type != DEAD_PROCESS &&
776 ut.ut_name[0] && ut.ut_line[0] &&
777 strcmp(ut.ut_name, "LOGIN") != 0)
778 ut.ut_type = USER_PROCESS;
779 /*
780 * Even worse, applications that write ghost
781 * entries: ut_type set to USER_PROCESS but
782 * empty ut_name...
783 */
784 if (ut.ut_name[0] == 0)
785 ut.ut_type = DEAD_PROCESS;
786
787 /*
788 * Clock changes.
789 */
790 if (strcmp(ut.ut_name, "date") == 0) {
791 if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
792 if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME;
793 }
794 }
795 #endif
796
797 switch (ut.ut_type) {
798 case SHUTDOWN_TIME:
799 if (extended) {
800 strcpy(ut.ut_line, "system down");
801 quit = list(&ut, lastboot, R_NORMAL);
802 }
803 lastdown = lastrch = ut.ut_time;
804 down = 1;
805 break;
806 case OLD_TIME:
807 case NEW_TIME:
808 if (extended) {
809 strcpy(ut.ut_line,
810 ut.ut_type == NEW_TIME ? "new time" :
811 "old time");
812 quit = list(&ut, lastdown, R_TIMECHANGE);
813 }
814 break;
815 case BOOT_TIME:
816 strcpy(ut.ut_line, "system boot");
817 quit = list(&ut, lastdown, R_REBOOT);
818 lastboot = ut.ut_time;
819 down = 1;
820 break;
821 case RUN_LVL:
822 x = ut.ut_pid & 255;
823 if (extended) {
824 sprintf(ut.ut_line, "(to lvl %c)", x);
825 quit = list(&ut, lastrch, R_NORMAL);
826 }
827 if (x == '0' || x == '6') {
828 lastdown = ut.ut_time;
829 down = 1;
830 ut.ut_type = SHUTDOWN_TIME;
831 }
832 lastrch = ut.ut_time;
833 break;
834
835 case USER_PROCESS:
836 /*
837 * This was a login - show the first matching
838 * logout record and delete all records with
839 * the same ut_line.
840 */
841 c = 0;
842 for (p = utmplist; p; p = next) {
843 next = p->next;
844 if (strncmp(p->ut.ut_line, ut.ut_line,
845 UT_LINESIZE) == 0) {
846 /* Show it */
847 if (c == 0) {
848 quit = list(&ut, p->ut.ut_time,
849 R_NORMAL);
850 c = 1;
851 }
852 if (p->next) p->next->prev = p->prev;
853 if (p->prev)
854 p->prev->next = p->next;
855 else
856 utmplist = p->next;
857 free(p);
858 }
859 }
860 /*
861 * Not found? Then crashed, down, still
862 * logged in, or missing logout record.
863 */
864 if (c == 0) {
865 if (lastboot == 0) {
866 c = R_NOW;
867 /* Is process still alive? */
868 if (ut.ut_pid > 0 &&
869 kill(ut.ut_pid, 0) != 0 &&
870 errno == ESRCH)
871 c = R_PHANTOM;
872 } else
873 c = whydown;
874 quit = list(&ut, lastboot, c);
875 }
876 /* FALLTHRU */
877
878 case DEAD_PROCESS:
879 /*
880 * Just store the data if it is
881 * interesting enough.
882 */
883 if (ut.ut_line[0] == 0)
884 break;
885 if ((p = malloc(sizeof(struct utmplist))) == NULL) {
886 fprintf(stderr, "%s: out of memory\n",
887 progname);
888 exit(1);
889 }
890 memcpy(&p->ut, &ut, sizeof(struct utmp));
891 p->next = utmplist;
892 p->prev = NULL;
893 if (utmplist) utmplist->prev = p;
894 utmplist = p;
895 break;
896
897 }
898 /*
899 * If we saw a shutdown/reboot record we can remove
900 * the entire current utmplist.
901 */
902 if (down) {
903 lastboot = ut.ut_time;
904 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
905 for (p = utmplist; p; p = next) {
906 next = p->next;
907 free(p);
908 }
909 utmplist = NULL;
910 down = 0;
911 }
912 }
913 printf("\n%s begins %s", mybasename(ufile), ctime(&begintime));
914
915 fclose(fp);
916
917 /*
918 * Should we free memory here? Nah. This is not NT :)
919 */
920 return 0;
921 }