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