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