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