]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/last.c
last: fix logout time
[thirdparty/util-linux.git] / login-utils / last.c
1 /*
2 * last(1) from sysvinit project, merged into util-linux in Aug 2013.
3 *
4 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
5 * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
6 * Karel Zak <kzak@redhat.com>
7 *
8 * Re-implementation of the 'last' command, this time for Linux. Yes I know
9 * there is BSD last, but I just felt like writing this. No thanks :-). Also,
10 * this version gives lots more info (especially with -x)
11 *
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 */
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <utmp.h>
34 #include <pwd.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <signal.h>
39 #include <getopt.h>
40 #include <netinet/in.h>
41 #include <netdb.h>
42 #include <arpa/inet.h>
43
44 #include "c.h"
45 #include "nls.h"
46 #include "optutils.h"
47 #include "pathnames.h"
48 #include "xalloc.h"
49 #include "closestream.h"
50 #include "carefulputc.h"
51 #include "strutils.h"
52 #include "timeutils.h"
53 #include "monotonic.h"
54
55 #if defined(_HAVE_UT_TV)
56 # define UL_UT_TIME ut_tv.tv_sec
57 #else
58 # define UL_UT_TIME ut_time
59 #endif
60
61 #ifndef SHUTDOWN_TIME
62 # define SHUTDOWN_TIME 254
63 #endif
64
65 #ifndef LAST_LOGIN_LEN
66 # define LAST_LOGIN_LEN 8
67 #endif
68
69 #ifndef LAST_DOMAIN_LEN
70 # define LAST_DOMAIN_LEN 16
71 #endif
72
73 #ifndef LAST_TIMESTAMP_LEN
74 # define LAST_TIMESTAMP_LEN 32
75 #endif
76
77 #define UCHUNKSIZE 16384 /* How much we read at once. */
78
79 struct last_control {
80 unsigned int lastb :1, /* Is this command 'lastb' */
81 extended :1, /* Lots of info */
82 showhost :1, /* Show hostname */
83 altlist :1, /* Hostname at the end */
84 usedns :1, /* Use DNS to lookup the hostname */
85 useip :1; /* Print IP address in number format */
86
87 unsigned int name_len; /* Number of login name characters to print */
88 unsigned int domain_len; /* Number of domain name characters to print */
89 unsigned int maxrecs; /* Maximum number of records to list */
90
91 char **show; /* Match search list */
92
93 struct timeval boot_time; /* system boot time */
94 time_t since; /* at what time to start displaying the file */
95 time_t until; /* at what time to stop displaying the file */
96 time_t present; /* who where present at time_t */
97 unsigned int time_fmt; /* time format */
98 };
99
100 /* Double linked list of struct utmp's */
101 struct utmplist {
102 struct utmp ut;
103 struct utmplist *next;
104 struct utmplist *prev;
105 };
106
107 /* Types of listing */
108 enum {
109 R_CRASH = 1, /* No logout record, system boot in between */
110 R_DOWN, /* System brought down in decent way */
111 R_NORMAL, /* Normal */
112 R_NOW, /* Still logged in */
113 R_REBOOT, /* Reboot record. */
114 R_PHANTOM, /* No logout record but session is stale. */
115 R_TIMECHANGE /* NEW_TIME or OLD_TIME */
116 };
117
118 enum {
119 LAST_TIMEFTM_NONE = 0,
120 LAST_TIMEFTM_SHORT,
121 LAST_TIMEFTM_CTIME,
122 LAST_TIMEFTM_ISO8601,
123
124 LAST_TIMEFTM_HHMM, /* non-public */
125 };
126
127 struct last_timefmt {
128 const char *name;
129 int in_len; /* log-in */
130 int in_fmt;
131 int out_len; /* log-out */
132 int out_fmt;
133 };
134
135 static struct last_timefmt timefmts[] = {
136 [LAST_TIMEFTM_NONE] = { .name = "notime" },
137 [LAST_TIMEFTM_SHORT] = {
138 .name = "short",
139 .in_len = 16,
140 .out_len = 7,
141 .in_fmt = LAST_TIMEFTM_CTIME,
142 .out_fmt = LAST_TIMEFTM_HHMM
143 },
144 [LAST_TIMEFTM_CTIME] = {
145 .name = "full",
146 .in_len = 24,
147 .out_len = 26,
148 .in_fmt = LAST_TIMEFTM_CTIME,
149 .out_fmt = LAST_TIMEFTM_CTIME
150 },
151 [LAST_TIMEFTM_ISO8601] = {
152 .name = "iso",
153 .in_len = 24,
154 .out_len = 26,
155 .in_fmt = LAST_TIMEFTM_ISO8601,
156 .out_fmt = LAST_TIMEFTM_ISO8601
157 }
158 };
159
160 /* Global variables */
161 static unsigned int recsdone; /* Number of records listed */
162 static time_t lastdate; /* Last date we've seen */
163 static time_t currentdate; /* date when we started processing the file */
164
165 /* --time-format=option parser */
166 static int which_time_format(const char *optarg)
167 {
168 size_t i;
169
170 for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
171 if (strcmp(timefmts[i].name, optarg) == 0)
172 return i;
173 }
174 errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
175 }
176
177 /*
178 * Read one utmp entry, return in new format.
179 * Automatically reposition file pointer.
180 */
181 static int uread(FILE *fp, struct utmp *u, int *quit, const char *filename)
182 {
183 static int utsize;
184 static char buf[UCHUNKSIZE];
185 char tmp[1024];
186 static off_t fpos;
187 static int bpos;
188 off_t o;
189
190 if (quit == NULL && u != NULL) {
191 /*
192 * Normal read.
193 */
194 return fread(u, sizeof(struct utmp), 1, fp);
195 }
196
197 if (u == NULL) {
198 /*
199 * Initialize and position.
200 */
201 utsize = sizeof(struct utmp);
202 fseeko(fp, 0, SEEK_END);
203 fpos = ftello(fp);
204 if (fpos == 0)
205 return 0;
206 o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
207 if (fseeko(fp, o, SEEK_SET) < 0) {
208 warn(_("seek on %s failed"), filename);
209 return 0;
210 }
211 bpos = (int)(fpos - o);
212 if (fread(buf, bpos, 1, fp) != 1) {
213 warn(_("cannot read %s"), filename);
214 return 0;
215 }
216 fpos = o;
217 return 1;
218 }
219
220 /*
221 * Read one struct. From the buffer if possible.
222 */
223 bpos -= utsize;
224 if (bpos >= 0) {
225 memcpy(u, buf + bpos, sizeof(struct utmp));
226 return 1;
227 }
228
229 /*
230 * Oops we went "below" the buffer. We should be able to
231 * seek back UCHUNKSIZE bytes.
232 */
233 fpos -= UCHUNKSIZE;
234 if (fpos < 0)
235 return 0;
236
237 /*
238 * Copy whatever is left in the buffer.
239 */
240 memcpy(tmp + (-bpos), buf, utsize + bpos);
241 if (fseeko(fp, fpos, SEEK_SET) < 0) {
242 warn(_("seek on %s failed"), filename);
243 return 0;
244 }
245
246 /*
247 * Read another UCHUNKSIZE bytes.
248 */
249 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
250 warn(_("cannot read %s"), filename);
251 return 0;
252 }
253
254 /*
255 * The end of the UCHUNKSIZE byte buffer should be the first
256 * few bytes of the current struct utmp.
257 */
258 memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
259 bpos += UCHUNKSIZE;
260
261 memcpy(u, tmp, sizeof(struct utmp));
262
263 return 1;
264 }
265
266 /*
267 * Print a short date.
268 */
269 static char *showdate(void)
270 {
271 char *s = ctime(&lastdate);
272 s[16] = 0;
273 return s;
274 }
275
276 /*
277 * SIGINT handler
278 */
279 static void int_handler(int sig __attribute__((unused)))
280 {
281 errx(EXIT_FAILURE, _("Interrupted %s"), showdate());
282 }
283
284 /*
285 * SIGQUIT handler
286 */
287 static void quit_handler(int sig __attribute__((unused)))
288 {
289 warnx(_("Interrupted %s"), showdate());
290 signal(SIGQUIT, quit_handler);
291 }
292
293 /*
294 * Lookup a host with DNS.
295 */
296 static int dns_lookup(char *result, int size, int useip, int32_t *a)
297 {
298 struct sockaddr_in sin;
299 struct sockaddr_in6 sin6;
300 struct sockaddr *sa;
301 int salen, flags;
302 int mapped = 0;
303
304 flags = useip ? NI_NUMERICHOST : 0;
305
306 /*
307 * IPv4 or IPv6 ?
308 * 1. If last 3 4bytes are 0, must be IPv4
309 * 2. If IPv6 in IPv4, handle as IPv4
310 * 3. Anything else is IPv6
311 *
312 * Ugly.
313 */
314 if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff))
315 mapped = 1;
316
317 if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) {
318 /* IPv4 */
319 sin.sin_family = AF_INET;
320 sin.sin_port = 0;
321 sin.sin_addr.s_addr = mapped ? a[3] : a[0];
322 sa = (struct sockaddr *)&sin;
323 salen = sizeof(sin);
324 } else {
325 /* IPv6 */
326 memset(&sin6, 0, sizeof(sin6));
327 sin6.sin6_family = AF_INET6;
328 sin6.sin6_port = 0;
329 memcpy(sin6.sin6_addr.s6_addr, a, 16);
330 sa = (struct sockaddr *)&sin6;
331 salen = sizeof(sin6);
332 }
333
334 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
335 }
336
337 static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when)
338 {
339 int ret = 0;
340
341 switch (fmt) {
342 case LAST_TIMEFTM_NONE:
343 *dst = 0;
344 break;
345 case LAST_TIMEFTM_HHMM:
346 {
347 struct tm *tm = localtime(when);
348 if (!snprintf(dst, dlen, "%02d:%02d", tm->tm_hour, tm->tm_min))
349 ret = -1;
350 break;
351 }
352 case LAST_TIMEFTM_CTIME:
353 ret = snprintf(dst, dlen, "%s", ctime(when));
354 break;
355 case LAST_TIMEFTM_ISO8601:
356 ret = strtime_iso(when, ISO_8601_DATE|ISO_8601_TIME|ISO_8601_TIMEZONE, dst, dlen);
357 break;
358 default:
359 abort();
360 }
361 return ret;
362 }
363
364 /*
365 * Remove trailing spaces from a string.
366 */
367 static void trim_trailing_spaces(char *s)
368 {
369 char *p;
370
371 for (p = s; *p; ++p)
372 continue;
373 while (p > s && isspace(*--p))
374 continue;
375 if (p > s)
376 ++p;
377 *p++ = '\n';
378 *p = '\0';
379 }
380
381 /*
382 * Show one line of information on screen
383 */
384 static int list(const struct last_control *ctl, struct utmp *p, time_t logout_time, int what)
385 {
386 time_t secs, utmp_time;
387 char logintime[LAST_TIMESTAMP_LEN];
388 char logouttime[LAST_TIMESTAMP_LEN];
389 char length[LAST_TIMESTAMP_LEN];
390 char final[512];
391 char utline[UT_LINESIZE+1];
392 char domain[256];
393 char *s;
394 int mins, hours, days;
395 int r, len;
396 struct last_timefmt *fmt;
397
398 /*
399 * uucp and ftp have special-type entries
400 */
401 utline[0] = 0;
402 strncat(utline, p->ut_line, UT_LINESIZE);
403 if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
404 utline[3] = 0;
405 if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
406 utline[4] = 0;
407
408 /*
409 * Is this something we want to show?
410 */
411 if (ctl->show) {
412 char **walk;
413 for (walk = ctl->show; *walk; walk++) {
414 if (strncmp(p->ut_user, *walk, UT_NAMESIZE) == 0 ||
415 strcmp(utline, *walk) == 0 ||
416 (strncmp(utline, "tty", 3) == 0 &&
417 strcmp(utline + 3, *walk) == 0)) break;
418 }
419 if (*walk == NULL) return 0;
420 }
421
422 /*
423 * Calculate times
424 */
425 fmt = &timefmts[ctl->time_fmt];
426
427 utmp_time = p->UL_UT_TIME;
428
429 if (ctl->present) {
430 if (ctl->present < utmp_time)
431 return 0;
432 if (0 < logout_time && logout_time < ctl->present)
433 return 0;
434 }
435
436 /* log-in time */
437 if (time_formatter(fmt->in_fmt, logintime,
438 sizeof(logintime), &utmp_time) < 0)
439 errx(EXIT_FAILURE, _("preallocation size exceeded"));
440
441 /* log-out time */
442 secs = logout_time - utmp_time;
443 mins = (secs / 60) % 60;
444 hours = (secs / 3600) % 24;
445 days = secs / 86400;
446
447 strcpy(logouttime, "- ");
448 if (time_formatter(fmt->out_fmt, logouttime + 2,
449 sizeof(logouttime) - 2, &logout_time) < 0)
450 errx(EXIT_FAILURE, _("preallocation size exceeded"));
451
452 if (logout_time == currentdate) {
453 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
454 sprintf(logouttime, " still running");
455 length[0] = 0;
456 } else {
457 sprintf(logouttime, " still");
458 sprintf(length, "running");
459 }
460 } else if (days) {
461 sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
462 } else {
463 sprintf(length, " (%02d:%02d)", hours, mins);
464 }
465
466 switch(what) {
467 case R_CRASH:
468 sprintf(logouttime, "- crash");
469 break;
470 case R_DOWN:
471 sprintf(logouttime, "- down ");
472 break;
473 case R_NOW:
474 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
475 sprintf(logouttime, " still logged in");
476 length[0] = 0;
477 } else {
478 sprintf(logouttime, " still");
479 sprintf(length, "logged in");
480 }
481 break;
482 case R_PHANTOM:
483 if (ctl->time_fmt > LAST_TIMEFTM_SHORT) {
484 sprintf(logouttime, " gone - no logout");
485 length[0] = 0;
486 } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) {
487 sprintf(logouttime, " gone");
488 sprintf(length, "- no logout");
489 } else {
490 logouttime[0] = 0;
491 sprintf(length, "no logout");
492 }
493 break;
494 case R_TIMECHANGE:
495 logouttime[0] = 0;
496 length[0] = 0;
497 break;
498 case R_NORMAL:
499 case R_REBOOT:
500 break;
501 default:
502 abort();
503 }
504
505 /*
506 * Look up host with DNS if needed.
507 */
508 r = -1;
509 if (ctl->usedns || ctl->useip)
510 r = dns_lookup(domain, sizeof(domain), ctl->useip, p->ut_addr_v6);
511 if (r < 0) {
512 len = UT_HOSTSIZE;
513 if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
514 domain[0] = 0;
515 strncat(domain, p->ut_host, len);
516 }
517
518
519 if (ctl->showhost) {
520 if (!ctl->altlist) {
521 len = snprintf(final, sizeof(final),
522 "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
523 ctl->name_len, p->ut_user, utline,
524 ctl->domain_len, domain,
525 fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
526 logouttime, length);
527 } else {
528 len = snprintf(final, sizeof(final),
529 "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
530 ctl->name_len, p->ut_user, utline,
531 fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
532 logouttime, length, domain);
533 }
534 } else
535 len = snprintf(final, sizeof(final),
536 "%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
537 ctl->name_len, p->ut_user, utline,
538 fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len,
539 logouttime, length);
540
541 #if defined(__GLIBC__)
542 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
543 final[sizeof(final)-1] = '\0';
544 # endif
545 #endif
546
547 trim_trailing_spaces(final);
548 /*
549 * Print out "final" string safely.
550 */
551 for (s = final; *s; s++)
552 fputc_careful(*s, stdout, '*');
553
554 if (len < 0 || (size_t)len >= sizeof(final))
555 putchar('\n');
556
557 recsdone++;
558 if (ctl->maxrecs && ctl->maxrecs <= recsdone)
559 return 1;
560
561 return 0;
562 }
563
564
565 static void __attribute__((__noreturn__)) usage(const struct last_control *ctl, FILE *out)
566 {
567 fputs(USAGE_HEADER, out);
568 fprintf(out, _(
569 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
570
571 fputs(USAGE_SEPARATOR, out);
572 fputs(_("Show a listing of last logged in users.\n"), out);
573
574 fputs(USAGE_OPTIONS, out);
575 fputs(_(" -<number> how many lines to show\n"), out);
576 fputs(_(" -a, --hostlast display hostnames in the last column\n"), out);
577 fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out);
578 fprintf(out,
579 _(" -f, --file <file> use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP);
580 fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out);
581 fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out);
582 fputs(_(" -n, --limit <number> how many lines to show\n"), out);
583 fputs(_(" -R, --nohostname don't display the hostname field\n"), out);
584 fputs(_(" -s, --since <time> display the lines since the specified time\n"), out);
585 fputs(_(" -t, --until <time> display the lines until the specified time\n"), out);
586 fputs(_(" -p, --present <time> display who were present at the specified time\n"), out);
587 fputs(_(" -w, --fullnames display full user and domain names\n"), out);
588 fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out);
589 fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
590 " notime|short|full|iso\n"), out);
591
592 fputs(USAGE_SEPARATOR, out);
593 fputs(USAGE_HELP, out);
594 fputs(USAGE_VERSION, out);
595 fprintf(out, USAGE_MAN_TAIL("last(1)"));
596
597 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
598 }
599
600 static int is_phantom(const struct last_control *ctl, struct utmp *ut)
601 {
602 struct passwd *pw;
603 char path[32];
604 int ret = 0;
605
606 if (ut->UL_UT_TIME < ctl->boot_time.tv_sec)
607 return 1;
608 pw = getpwnam(ut->ut_user);
609 if (!pw)
610 return 1;
611 sprintf(path, "/proc/%u/loginuid", ut->ut_pid);
612 if (access(path, R_OK) == 0) {
613 unsigned int loginuid;
614 FILE *f = NULL;
615
616 if (!(f = fopen(path, "r")))
617 return 1;
618 if (fscanf(f, "%u", &loginuid) != 1)
619 ret = 1;
620 fclose(f);
621 if (!ret && pw->pw_uid != loginuid)
622 return 1;
623 } else {
624 struct stat st;
625
626 sprintf(path, "/dev/%s", ut->ut_line);
627 if (stat(path, &st))
628 return 1;
629 if (pw->pw_uid != st.st_uid)
630 return 1;
631 }
632 return ret;
633 }
634
635 static void process_wtmp_file(const struct last_control *ctl,
636 const char *filename)
637 {
638 FILE *fp; /* Filepointer of wtmp file */
639
640 struct utmp ut; /* Current utmp entry */
641 struct utmplist *ulist = NULL; /* All entries */
642 struct utmplist *p; /* Pointer into utmplist */
643 struct utmplist *next; /* Pointer into utmplist */
644
645 time_t lastboot = 0; /* Last boottime */
646 time_t lastrch = 0; /* Last run level change */
647 time_t lastdown; /* Last downtime */
648 time_t begintime; /* When wtmp begins */
649 int whydown = 0; /* Why we went down: crash or shutdown */
650
651 int c, x; /* Scratch */
652 struct stat st; /* To stat the [uw]tmp file */
653 int quit = 0; /* Flag */
654 int down = 0; /* Down flag */
655
656 time(&lastdown);
657 /*
658 * Fill in 'lastdate'
659 */
660 lastdate = currentdate = lastrch = lastdown;
661
662 /*
663 * Install signal handlers
664 */
665 signal(SIGINT, int_handler);
666 signal(SIGQUIT, quit_handler);
667
668 /*
669 * Open the utmp file
670 */
671 if ((fp = fopen(filename, "r")) == NULL)
672 err(EXIT_FAILURE, _("cannot open %s"), filename);
673
674 /*
675 * Optimize the buffer size.
676 */
677 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
678
679 /*
680 * Read first structure to capture the time field
681 */
682 if (uread(fp, &ut, NULL, filename) == 1)
683 begintime = ut.UL_UT_TIME;
684 else {
685 if (fstat(fileno(fp), &st) != 0)
686 err(EXIT_FAILURE, _("stat of %s failed"), filename);
687 begintime = st.st_ctime;
688 quit = 1;
689 }
690
691 /*
692 * Go to end of file minus one structure
693 * and/or initialize utmp reading code.
694 */
695 uread(fp, NULL, NULL, filename);
696
697 /*
698 * Read struct after struct backwards from the file.
699 */
700 while (!quit) {
701
702 if (uread(fp, &ut, &quit, filename) != 1)
703 break;
704
705 if (ctl->since && ut.UL_UT_TIME < ctl->since)
706 continue;
707
708 if (ctl->until && ctl->until < ut.UL_UT_TIME)
709 continue;
710
711 lastdate = ut.UL_UT_TIME;
712
713 if (ctl->lastb) {
714 quit = list(ctl, &ut, ut.UL_UT_TIME, R_NORMAL);
715 continue;
716 }
717
718 /*
719 * Set ut_type to the correct type.
720 */
721 if (strncmp(ut.ut_line, "~", 1) == 0) {
722 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
723 ut.ut_type = SHUTDOWN_TIME;
724 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
725 ut.ut_type = BOOT_TIME;
726 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
727 ut.ut_type = RUN_LVL;
728 }
729 #if 1 /*def COMPAT*/
730 /*
731 * For stupid old applications that don't fill in
732 * ut_type correctly.
733 */
734 else {
735 if (ut.ut_type != DEAD_PROCESS &&
736 ut.ut_user[0] && ut.ut_line[0] &&
737 strcmp(ut.ut_user, "LOGIN") != 0)
738 ut.ut_type = USER_PROCESS;
739 /*
740 * Even worse, applications that write ghost
741 * entries: ut_type set to USER_PROCESS but
742 * empty ut_user...
743 */
744 if (ut.ut_user[0] == 0)
745 ut.ut_type = DEAD_PROCESS;
746
747 /*
748 * Clock changes.
749 */
750 if (strcmp(ut.ut_user, "date") == 0) {
751 if (ut.ut_line[0] == '|')
752 ut.ut_type = OLD_TIME;
753 if (ut.ut_line[0] == '{')
754 ut.ut_type = NEW_TIME;
755 }
756 }
757 #endif
758 switch (ut.ut_type) {
759 case SHUTDOWN_TIME:
760 if (ctl->extended) {
761 strcpy(ut.ut_line, "system down");
762 quit = list(ctl, &ut, lastboot, R_NORMAL);
763 }
764 lastdown = lastrch = ut.UL_UT_TIME;
765 down = 1;
766 break;
767 case OLD_TIME:
768 case NEW_TIME:
769 if (ctl->extended) {
770 strcpy(ut.ut_line,
771 ut.ut_type == NEW_TIME ? "new time" :
772 "old time");
773 quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
774 }
775 break;
776 case BOOT_TIME:
777 strcpy(ut.ut_line, "system boot");
778 quit = list(ctl, &ut, lastdown, R_REBOOT);
779 lastboot = ut.UL_UT_TIME;
780 down = 1;
781 break;
782 case RUN_LVL:
783 x = ut.ut_pid & 255;
784 if (ctl->extended) {
785 sprintf(ut.ut_line, "(to lvl %c)", x);
786 quit = list(ctl, &ut, lastrch, R_NORMAL);
787 }
788 if (x == '0' || x == '6') {
789 lastdown = ut.UL_UT_TIME;
790 down = 1;
791 ut.ut_type = SHUTDOWN_TIME;
792 }
793 lastrch = ut.UL_UT_TIME;
794 break;
795
796 case USER_PROCESS:
797 /*
798 * This was a login - show the first matching
799 * logout record and delete all records with
800 * the same ut_line.
801 */
802 c = 0;
803 for (p = ulist; p; p = next) {
804 next = p->next;
805 if (strncmp(p->ut.ut_line, ut.ut_line,
806 UT_LINESIZE) == 0) {
807 /* Show it */
808 if (c == 0) {
809 quit = list(ctl, &ut, p->ut.UL_UT_TIME, R_NORMAL);
810 c = 1;
811 }
812 if (p->next)
813 p->next->prev = p->prev;
814 if (p->prev)
815 p->prev->next = p->next;
816 else
817 ulist = p->next;
818 free(p);
819 }
820 }
821 /*
822 * Not found? Then crashed, down, still
823 * logged in, or missing logout record.
824 */
825 if (c == 0) {
826 if (!lastboot) {
827 c = R_NOW;
828 /* Is process still alive? */
829 if (is_phantom(ctl, &ut))
830 c = R_PHANTOM;
831 } else
832 c = whydown;
833 quit = list(ctl, &ut, lastboot, c);
834 }
835 /* FALLTHRU */
836
837 case DEAD_PROCESS:
838 /*
839 * Just store the data if it is
840 * interesting enough.
841 */
842 if (ut.ut_line[0] == 0)
843 break;
844 p = xmalloc(sizeof(struct utmplist));
845 memcpy(&p->ut, &ut, sizeof(struct utmp));
846 p->next = ulist;
847 p->prev = NULL;
848 if (ulist)
849 ulist->prev = p;
850 ulist = p;
851 break;
852
853 case EMPTY:
854 case INIT_PROCESS:
855 case LOGIN_PROCESS:
856 case ACCOUNTING:
857 /* ignored ut_types */
858 break;
859
860 default:
861 warnx("unrecogized ut_type: %d", ut.ut_type);
862 }
863
864 /*
865 * If we saw a shutdown/reboot record we can remove
866 * the entire current ulist.
867 */
868 if (down) {
869 lastboot = ut.UL_UT_TIME;
870 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
871 for (p = ulist; p; p = next) {
872 next = p->next;
873 free(p);
874 }
875 ulist = NULL;
876 down = 0;
877 }
878 }
879
880 printf(_("\n%s begins %s"), basename(filename), ctime(&begintime));
881 fclose(fp);
882
883 for (p = ulist; p; p = next) {
884 next = p->next;
885 free(p);
886 }
887 }
888
889 int main(int argc, char **argv)
890 {
891 struct last_control ctl = {
892 .showhost = TRUE,
893 .name_len = LAST_LOGIN_LEN,
894 .time_fmt = LAST_TIMEFTM_SHORT,
895 .domain_len = LAST_DOMAIN_LEN
896 };
897 char **files = NULL;
898 size_t i, nfiles = 0;
899 int c;
900 usec_t p;
901
902 enum {
903 OPT_TIME_FORMAT = CHAR_MAX + 1
904 };
905 static const struct option long_opts[] = {
906 { "limit", required_argument, NULL, 'n' },
907 { "help", no_argument, NULL, 'h' },
908 { "file", required_argument, NULL, 'f' },
909 { "nohostname", no_argument, NULL, 'R' },
910 { "version", no_argument, NULL, 'V' },
911 { "hostlast", no_argument, NULL, 'a' },
912 { "since", required_argument, NULL, 's' },
913 { "until", required_argument, NULL, 't' },
914 { "present", required_argument, NULL, 'p' },
915 { "system", no_argument, NULL, 'x' },
916 { "dns", no_argument, NULL, 'd' },
917 { "ip", no_argument, NULL, 'i' },
918 { "fulltimes", no_argument, NULL, 'F' },
919 { "fullnames", no_argument, NULL, 'w' },
920 { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
921 { NULL, 0, NULL, 0 }
922 };
923 static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */
924 { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
925 { 0 }
926 };
927 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
928
929 setlocale(LC_ALL, "");
930 bindtextdomain(PACKAGE, LOCALEDIR);
931 textdomain(PACKAGE);
932 atexit(close_stdout);
933 /*
934 * Which file do we want to read?
935 */
936 ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
937 while ((c = getopt_long(argc, argv,
938 "hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) {
939
940 err_exclusive_options(c, long_opts, excl, excl_st);
941
942 switch(c) {
943 case 'h':
944 usage(&ctl, stdout);
945 break;
946 case 'V':
947 printf(UTIL_LINUX_VERSION);
948 return EXIT_SUCCESS;
949 case 'R':
950 ctl.showhost = 0;
951 break;
952 case 'x':
953 ctl.extended = 1;
954 break;
955 case 'n':
956 ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
957 break;
958 case 'f':
959 if (!files)
960 files = xmalloc(sizeof(char *) * argc);
961 files[nfiles++] = xstrdup(optarg);
962 break;
963 case 'd':
964 ctl.usedns = 1;
965 break;
966 case 'i':
967 ctl.useip = 1;
968 break;
969 case 'a':
970 ctl.altlist = 1;
971 break;
972 case 'F':
973 ctl.time_fmt = LAST_TIMEFTM_CTIME;
974 break;
975 case 'p':
976 if (parse_timestamp(optarg, &p) < 0)
977 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
978 ctl.present = (time_t) (p / 1000000);
979 break;
980 case 's':
981 if (parse_timestamp(optarg, &p) < 0)
982 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
983 ctl.since = (time_t) (p / 1000000);
984 break;
985 case 't':
986 if (parse_timestamp(optarg, &p) < 0)
987 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
988 ctl.until = (time_t) (p / 1000000);
989 break;
990 case 'w':
991 if (ctl.name_len < UT_NAMESIZE)
992 ctl.name_len = UT_NAMESIZE;
993 if (ctl.domain_len < UT_HOSTSIZE)
994 ctl.domain_len = UT_HOSTSIZE;
995 break;
996 case '0': case '1': case '2': case '3': case '4':
997 case '5': case '6': case '7': case '8': case '9':
998 ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
999 break;
1000 case OPT_TIME_FORMAT:
1001 ctl.time_fmt = which_time_format(optarg);
1002 break;
1003 default:
1004 usage(&ctl, stderr);
1005 break;
1006 }
1007 }
1008
1009 if (optind < argc)
1010 ctl.show = argv + optind;
1011
1012 if (!files) {
1013 files = xmalloc(sizeof(char *));
1014 files[nfiles++] = xstrdup(ctl.lastb ? _PATH_BTMP : _PATH_WTMP);
1015 }
1016
1017 for (i = 0; i < nfiles; i++) {
1018 get_boot_time(&ctl.boot_time);
1019 process_wtmp_file(&ctl, files[i]);
1020 free(files[i]);
1021 }
1022 free(files);
1023 return EXIT_SUCCESS;
1024 }