]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/last.c
libsmartcols: add lines
[thirdparty/util-linux.git] / login-utils / last.c
CommitLineData
6dbe3af9 1/*
4d1d1233 2 * last(1) from sysvinit project, merged into util-linux in Aug 2013.
6dbe3af9 3 *
4d1d1233
KZ
4 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
5 * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
77079432 6 * Karel Zak <kzak@redhat.com>
6dbe3af9 7 *
4d1d1233
KZ
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)
ce602720 11 *
ce602720 12 *
4d1d1233
KZ
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.
ba0c9030 17 *
4d1d1233
KZ
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.
ce602720 22 *
4d1d1233
KZ
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
ba0c9030 26 */
7eda085c 27#include <sys/types.h>
ce602720
OO
28#include <sys/stat.h>
29#include <sys/fcntl.h>
6dbe3af9 30#include <time.h>
6dbe3af9 31#include <stdio.h>
ce602720
OO
32#include <ctype.h>
33#include <utmp.h>
404fa3f9 34#include <pwd.h>
6dbe3af9
KZ
35#include <stdlib.h>
36#include <unistd.h>
ce602720
OO
37#include <string.h>
38#include <signal.h>
39#include <getopt.h>
7eda085c 40#include <netinet/in.h>
ce602720 41#include <netdb.h>
6dbe3af9 42#include <arpa/inet.h>
7eda085c 43
293e0c90
KZ
44#include "c.h"
45#include "nls.h"
2ea747eb 46#include "optutils.h"
4c7dfea7 47#include "pathnames.h"
a9f789e5
KZ
48#include "xalloc.h"
49#include "closestream.h"
2e58cd40 50#include "carefulputc.h"
1bd68aab 51#include "strutils.h"
60f25dea 52#include "timeutils.h"
293e0c90 53
cfa7fe89
SK
54#if defined(_HAVE_UT_TV)
55# define UL_UT_TIME ut_tv.tv_sec
56#else
57# define UL_UT_TIME ut_time
58#endif
59
ce602720 60#ifndef SHUTDOWN_TIME
4d1d1233 61# define SHUTDOWN_TIME 254
6dbe3af9 62#endif
ce602720 63
83484375
SK
64#ifndef LAST_LOGIN_LEN
65# define LAST_LOGIN_LEN 8
66#endif
67
68#ifndef LAST_DOMAIN_LEN
69# define LAST_DOMAIN_LEN 16
70#endif
71
45f11b6b
SK
72#ifndef LAST_TIMESTAMP_LEN
73# define LAST_TIMESTAMP_LEN 32
74#endif
75
ce602720
OO
76#define UCHUNKSIZE 16384 /* How much we read at once. */
77
83484375 78struct last_control {
ee24e58f
KZ
79 unsigned int lastb :1, /* Is this command 'lastb' */
80 extended :1, /* Lots of info */
81 showhost :1, /* Show hostname */
82 altlist :1, /* Hostname at the end */
83 usedns :1, /* Use DNS to lookup the hostname */
84 useip :1, /* Print IP address in number format */
85 fulltime :1; /* Print full dates and times */
83484375
SK
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 time_t since; /* at what time to start displaying the file */
98 time_t until; /* at what time to stop displaying the file */
99 time_t present; /* who where present at time_t */
45f11b6b 100 unsigned int time_fmt; /* time format */
83484375
SK
101};
102
ce602720
OO
103/* Double linked list of struct utmp's */
104struct utmplist {
4d1d1233
KZ
105 struct utmp ut;
106 struct utmplist *next;
107 struct utmplist *prev;
ce602720
OO
108};
109struct utmplist *utmplist = NULL;
110
111/* Types of listing */
e843d047
SK
112enum {
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};
ce602720 121
45f11b6b
SK
122enum {
123 LAST_TIMEFTM_NONE = 0,
124 LAST_TIMEFTM_SHORT_CTIME,
125 LAST_TIMEFTM_FULL_CTIME,
126 LAST_TIMEFTM_ISO8601
127};
128
c87e4fbb
KZ
129struct last_timefmt {
130 const char *name;
45f11b6b
SK
131 int in;
132 int out;
133};
134
c87e4fbb
KZ
135static 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}
45f11b6b
SK
140};
141
ce602720 142/* Global variables */
751947b6 143static unsigned int recsdone; /* Number of records listed */
77079432 144static time_t lastdate; /* Last date we've seen */
ce602720 145
45f11b6b
SK
146/* --time-format=option parser */
147static int which_time_format(const char *optarg)
148{
c87e4fbb
KZ
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 }
45f11b6b
SK
155 errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
156}
157
ce602720
OO
158/*
159 * Read one utmp entry, return in new format.
160 * Automatically reposition file pointer.
161 */
83484375
SK
162static int uread(const struct last_control *ctl, FILE *fp, struct utmp *u,
163 int *quit)
ce602720
OO
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) {
47481cbd 190 warn(_("seek on %s failed"), ctl->altv[ctl->alti]);
ce602720
OO
191 return 0;
192 }
193 bpos = (int)(fpos - o);
194 if (fread(buf, bpos, 1, fp) != 1) {
47481cbd 195 warn(_("cannot read %s"), ctl->altv[ctl->alti]);
ce602720
OO
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;
6dbe3af9 209 }
db797f24 210
ce602720
OO
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) {
47481cbd 224 warn(_("seek on %s failed"), ctl->altv[ctl->alti]);
ce602720
OO
225 return 0;
226 }
227
228 /*
229 * Read another UCHUNKSIZE bytes.
230 */
231 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
47481cbd 232 warn(_("cannot read %s"), ctl->altv[ctl->alti]);
ce602720
OO
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;
6dbe3af9
KZ
246}
247
248/*
ce602720 249 * Print a short date.
6dbe3af9 250 */
ce602720
OO
251static char *showdate(void)
252{
253 char *s = ctime(&lastdate);
254 s[16] = 0;
255 return s;
6dbe3af9
KZ
256}
257
258/*
ce602720 259 * SIGINT handler
6dbe3af9 260 */
ce602720
OO
261static void int_handler(int sig __attribute__((unused)))
262{
a9f789e5 263 errx(EXIT_FAILURE, _("Interrupted %s"), showdate());
ce602720 264}
8e5cf69f 265
ce602720
OO
266/*
267 * SIGQUIT handler
268 */
269static void quit_handler(int sig __attribute__((unused)))
270{
a9f789e5 271 warnx(_("Interrupted %s"), showdate());
ce602720
OO
272 signal(SIGQUIT, quit_handler);
273}
6dbe3af9 274
ce602720
OO
275/*
276 * Lookup a host with DNS.
277 */
278static 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;
6dbe3af9 285
ce602720 286 flags = useip ? NI_NUMERICHOST : 0;
6dbe3af9 287
ce602720
OO
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);
c07ebfa1 314 }
ce602720
OO
315
316 return getnameinfo(sa, salen, result, size, NULL, 0, flags);
6dbe3af9
KZ
317}
318
45f11b6b
SK
319static 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
97bbb5e4
SK
358/*
359 * Remove trailing spaces from a string.
360 */
361static 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
6dbe3af9 375/*
ce602720 376 * Show one line of information on screen
6dbe3af9 377 */
83484375 378static int list(const struct last_control *ctl, struct utmp *p, time_t t, int what)
ce602720 379{
31d28e09 380 time_t secs, tmp, epoch;
45f11b6b
SK
381 char logintime[LAST_TIMESTAMP_LEN];
382 char logouttime[LAST_TIMESTAMP_LEN];
383 char length[LAST_TIMESTAMP_LEN];
ce602720
OO
384 char final[512];
385 char utline[UT_LINESIZE+1];
386 char domain[256];
328734c0 387 char *s;
ce602720
OO
388 int mins, hours, days;
389 int r, len;
c87e4fbb 390 struct last_timefmt *fmt;
6dbe3af9 391
ce602720
OO
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 wanna show?
404 */
83484375 405 if (ctl->show) {
328734c0 406 char **walk;
83484375 407 for (walk = ctl->show; *walk; walk++) {
cfa7fe89 408 if (strncmp(p->ut_user, *walk, UT_NAMESIZE) == 0 ||
ce602720
OO
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;
7eda085c 414 }
ce602720
OO
415
416 /*
417 * Calculate times
418 */
cfa7fe89 419 tmp = p->UL_UT_TIME;
37f534a6 420
83484375 421 if (ctl->present && (ctl->present < tmp || (0 < t && t < ctl->present)))
37f534a6
SK
422 return 0;
423
45f11b6b
SK
424 if (time_formatter(ctl, &logintime[0], sizeof(logintime), &tmp, 0) < 0 ||
425 time_formatter(ctl, &logouttime[0], sizeof(logouttime), &t, 1) < 0)
426 errx(EXIT_FAILURE, _("preallocation size exceeded"));
427
cfa7fe89 428 secs = t - p->UL_UT_TIME;
ce602720
OO
429 mins = (secs / 60) % 60;
430 hours = (secs / 3600) % 24;
431 days = secs / 86400;
31d28e09
SK
432
433 epoch = time(NULL);
434 if (t == epoch) {
435 if (ctl->fulltime)
436 sprintf(logouttime, " still running");
437 else {
438 sprintf(logouttime, " still");
439 sprintf(length, "running");
440 }
441 } else if (days)
ce602720
OO
442 sprintf(length, "(%d+%02d:%02d)", days, hours, mins);
443 else
444 sprintf(length, " (%02d:%02d)", hours, mins);
445
446 switch(what) {
447 case R_CRASH:
448 sprintf(logouttime, "- crash");
449 break;
450 case R_DOWN:
451 sprintf(logouttime, "- down ");
452 break;
453 case R_NOW:
454 length[0] = 0;
83484375 455 if (ctl->fulltime)
ce602720
OO
456 sprintf(logouttime, " still logged in");
457 else {
458 sprintf(logouttime, " still");
459 sprintf(length, "logged in");
460 }
6dbe3af9 461 break;
ce602720
OO
462 case R_PHANTOM:
463 length[0] = 0;
83484375 464 if (ctl->fulltime)
ce602720
OO
465 sprintf(logouttime, " gone - no logout");
466 else {
467 sprintf(logouttime, " gone");
468 sprintf(length, "- no logout");
469 }
470 break;
471 case R_REBOOT:
6dbe3af9 472 break;
ce602720
OO
473 case R_TIMECHANGE:
474 logouttime[0] = 0;
475 length[0] = 0;
6dbe3af9 476 break;
ce602720 477 case R_NORMAL:
6dbe3af9 478 break;
5cae90f9
SK
479 default:
480 abort();
ce602720
OO
481 }
482
483 /*
484 * Look up host with DNS if needed.
485 */
486 r = -1;
83484375
SK
487 if (ctl->usedns || ctl->useip)
488 r = dns_lookup(domain, sizeof(domain), ctl->useip, p->ut_addr_v6);
ce602720
OO
489 if (r < 0) {
490 len = UT_HOSTSIZE;
491 if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
492 domain[0] = 0;
493 strncat(domain, p->ut_host, len);
494 }
495
c87e4fbb
KZ
496 fmt = &timefmts[ctl->time_fmt];
497
83484375
SK
498 if (ctl->showhost) {
499 if (!ctl->altlist) {
ce602720 500 len = snprintf(final, sizeof(final),
438269dc 501 "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
cfa7fe89 502 ctl->name_len, p->ut_user, utline,
45f11b6b 503 ctl->domain_len, domain,
c87e4fbb
KZ
504 fmt->in, fmt->in, logintime, fmt->out, fmt->out,
505 logouttime, length);
ce602720
OO
506 } else {
507 len = snprintf(final, sizeof(final),
45f11b6b 508 "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
cfa7fe89 509 ctl->name_len, p->ut_user, utline,
c87e4fbb
KZ
510 fmt->in, fmt->in, logintime, fmt->out, fmt->out,
511 logouttime, length, domain);
5818a4aa 512 }
ce602720
OO
513 } else
514 len = snprintf(final, sizeof(final),
438269dc 515 "%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
cfa7fe89 516 ctl->name_len, p->ut_user, utline,
c87e4fbb
KZ
517 fmt->in, fmt->in, logintime, fmt->out, fmt->out,
518 logouttime, length);
ce602720
OO
519
520#if defined(__GLIBC__)
521# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
522 final[sizeof(final)-1] = '\0';
523# endif
524#endif
6dbe3af9 525
97bbb5e4 526 trim_trailing_spaces(final);
ce602720
OO
527 /*
528 * Print out "final" string safely.
529 */
2e58cd40
SK
530 for (s = final; *s; s++)
531 carefulputc(*s, stdout, '*');
ce602720
OO
532
533 if (len < 0 || (size_t)len >= sizeof(final))
534 putchar('\n');
535
536 recsdone++;
83484375 537 if (ctl->maxrecs && ctl->maxrecs <= recsdone)
ce602720
OO
538 return 1;
539
540 return 0;
6dbe3af9
KZ
541}
542
ce602720 543
293e0c90 544static void __attribute__((__noreturn__)) usage(FILE *out)
ce602720 545{
293e0c90
KZ
546 fputs(USAGE_HEADER, out);
547 fprintf(out, _(
b8672754 548 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name);
293e0c90
KZ
549
550 fputs(USAGE_OPTIONS, out);
551 fputs(_(" -<number> how many lines to show\n"), out);
552 fputs(_(" -a, --hostlast display hostnames in the last column\n"), out);
553 fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out);
554 fprintf(out,
4c7dfea7 555 _(" -f, --file <file> use a specific file instead of %s\n"), _PATH_WTMP);
293e0c90
KZ
556 fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out);
557 fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out);
558 fputs(_(" -n, --limit <number> how many lines to show\n"), out);
559 fputs(_(" -R, --nohostname don't display the hostname field\n"), out);
dd50168b
SK
560 fputs(_(" -s, --since <time> display the lines since the specified time\n"), out);
561 fputs(_(" -t, --until <time> display the lines until the specified time\n"), out);
562 fputs(_(" -p, --present <time> display who where present at the specified time\n"), out);
293e0c90
KZ
563 fputs(_(" -w, --fullnames display full user and domain names\n"), out);
564 fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out);
45f11b6b
SK
565 fputs(_(" --time-format <format> show time stamp using format:\n"), out);
566 fputs(_(" [notime|short|full|iso]\n"), out);
293e0c90
KZ
567
568 fputs(USAGE_SEPARATOR, out);
569 fputs(USAGE_HELP, out);
570 fputs(USAGE_VERSION, out);
30faa202 571 fprintf(out, USAGE_MAN_TAIL("last(1)"));
293e0c90
KZ
572
573 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
6dbe3af9
KZ
574}
575
404fa3f9
SK
576static int is_phantom(struct utmp *ut)
577{
578 struct passwd *pw;
579 char path[32];
580 FILE *f;
581 unsigned int loginuid, ret = 0;
582
583 pw = getpwnam(ut->ut_name);
584 if (!pw)
585 return 1;
586 sprintf(path, "/proc/%u/loginuid", ut->ut_pid);
587 if (!(f = fopen(path, "r")))
588 return 1;
589 if (fscanf(f, "%u", &loginuid) != 1)
590 ret = 1;
591 fclose(f);
592 if (!ret && pw->pw_uid != loginuid)
593 return 1;
594 return ret;
595}
293e0c90 596
83484375 597static void process_wtmp_file(const struct last_control *ctl)
ce602720 598{
77079432
KZ
599 FILE *fp; /* Filepointer of wtmp file */
600
601 struct utmp ut; /* Current utmp entry */
602 struct utmplist *p; /* Pointer into utmplist */
603 struct utmplist *next; /* Pointer into utmplist */
604
605 time_t lastboot = 0; /* Last boottime */
606 time_t lastrch = 0; /* Last run level change */
607 time_t lastdown; /* Last downtime */
608 time_t begintime; /* When wtmp begins */
609 int whydown = 0; /* Why we went down: crash or shutdown */
610
611 int c, x; /* Scratch */
612 struct stat st; /* To stat the [uw]tmp file */
613 int quit = 0; /* Flag */
614 int down = 0; /* Down flag */
77079432
KZ
615
616 time(&lastdown);
617 lastrch = lastdown;
618
619 /*
620 * Fill in 'lastdate'
621 */
622 lastdate = lastdown;
ce602720 623
77079432
KZ
624 /*
625 * Install signal handlers
626 */
627 signal(SIGINT, int_handler);
628 signal(SIGQUIT, quit_handler);
ce602720 629
77079432
KZ
630 /*
631 * Open the utmp file
632 */
83484375
SK
633 if ((fp = fopen(ctl->altv[ctl->alti], "r")) == NULL)
634 err(EXIT_FAILURE, _("cannot open %s"), ctl->altv[ctl->alti]);
6dbe3af9
KZ
635
636 /*
77079432 637 * Optimize the buffer size.
ce602720 638 */
77079432
KZ
639 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
640
ce602720 641 /*
77079432 642 * Read first structure to capture the time field
6dbe3af9 643 */
83484375 644 if (uread(ctl, fp, &ut, NULL) == 1)
cfa7fe89 645 begintime = ut.UL_UT_TIME;
ce602720 646 else {
d879e052
KZ
647 if (fstat(fileno(fp), &st) != 0)
648 err(EXIT_FAILURE, _("stat failed %s"), ctl->altv[ctl->alti]);
77079432
KZ
649 begintime = st.st_ctime;
650 quit = 1;
651 }
652
653 /*
654 * Go to end of file minus one structure
655 * and/or initialize utmp reading code.
656 */
83484375 657 uread(ctl, fp, NULL, NULL);
77079432
KZ
658
659 /*
660 * Read struct after struct backwards from the file.
661 */
662 while (!quit) {
663
83484375 664 if (uread(ctl, fp, &ut, &quit) != 1)
77079432
KZ
665 break;
666
cfa7fe89 667 if (ctl->since && ut.UL_UT_TIME < ctl->since)
dd50168b
SK
668 continue;
669
cfa7fe89 670 if (ctl->until && ctl->until < ut.UL_UT_TIME)
77079432
KZ
671 continue;
672
cfa7fe89 673 lastdate = ut.UL_UT_TIME;
77079432 674
83484375 675 if (ctl->lastb) {
cfa7fe89 676 quit = list(ctl, &ut, ut.UL_UT_TIME, R_NORMAL);
77079432
KZ
677 continue;
678 }
679
ce602720 680 /*
77079432 681 * Set ut_type to the correct type.
ce602720 682 */
77079432
KZ
683 if (strncmp(ut.ut_line, "~", 1) == 0) {
684 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
685 ut.ut_type = SHUTDOWN_TIME;
686 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
687 ut.ut_type = BOOT_TIME;
688 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
689 ut.ut_type = RUN_LVL;
690 }
691#if 1 /*def COMPAT*/
ce602720 692 /*
77079432
KZ
693 * For stupid old applications that don't fill in
694 * ut_type correctly.
ce602720 695 */
77079432
KZ
696 else {
697 if (ut.ut_type != DEAD_PROCESS &&
cfa7fe89
SK
698 ut.ut_user[0] && ut.ut_line[0] &&
699 strcmp(ut.ut_user, "LOGIN") != 0)
77079432
KZ
700 ut.ut_type = USER_PROCESS;
701 /*
702 * Even worse, applications that write ghost
703 * entries: ut_type set to USER_PROCESS but
cfa7fe89 704 * empty ut_user...
77079432 705 */
cfa7fe89 706 if (ut.ut_user[0] == 0)
77079432
KZ
707 ut.ut_type = DEAD_PROCESS;
708
709 /*
710 * Clock changes.
711 */
cfa7fe89 712 if (strcmp(ut.ut_user, "date") == 0) {
77079432
KZ
713 if (ut.ut_line[0] == '|')
714 ut.ut_type = OLD_TIME;
715 if (ut.ut_line[0] == '{')
716 ut.ut_type = NEW_TIME;
717 }
6dbe3af9 718 }
ce602720 719#endif
77079432 720 switch (ut.ut_type) {
ce602720 721 case SHUTDOWN_TIME:
83484375 722 if (ctl->extended) {
ce602720 723 strcpy(ut.ut_line, "system down");
83484375 724 quit = list(ctl, &ut, lastboot, R_NORMAL);
ce602720 725 }
cfa7fe89 726 lastdown = lastrch = ut.UL_UT_TIME;
ce602720
OO
727 down = 1;
728 break;
729 case OLD_TIME:
730 case NEW_TIME:
83484375 731 if (ctl->extended) {
ce602720
OO
732 strcpy(ut.ut_line,
733 ut.ut_type == NEW_TIME ? "new time" :
734 "old time");
83484375 735 quit = list(ctl, &ut, lastdown, R_TIMECHANGE);
ce602720
OO
736 }
737 break;
738 case BOOT_TIME:
739 strcpy(ut.ut_line, "system boot");
83484375 740 quit = list(ctl, &ut, lastdown, R_REBOOT);
cfa7fe89 741 lastboot = ut.UL_UT_TIME;
ce602720
OO
742 down = 1;
743 break;
744 case RUN_LVL:
745 x = ut.ut_pid & 255;
83484375 746 if (ctl->extended) {
ce602720 747 sprintf(ut.ut_line, "(to lvl %c)", x);
83484375 748 quit = list(ctl, &ut, lastrch, R_NORMAL);
ce602720
OO
749 }
750 if (x == '0' || x == '6') {
cfa7fe89 751 lastdown = ut.UL_UT_TIME;
ce602720
OO
752 down = 1;
753 ut.ut_type = SHUTDOWN_TIME;
754 }
cfa7fe89 755 lastrch = ut.UL_UT_TIME;
ce602720 756 break;
6dbe3af9 757
ce602720
OO
758 case USER_PROCESS:
759 /*
77079432
KZ
760 * This was a login - show the first matching
761 * logout record and delete all records with
762 * the same ut_line.
ce602720
OO
763 */
764 c = 0;
765 for (p = utmplist; p; p = next) {
766 next = p->next;
767 if (strncmp(p->ut.ut_line, ut.ut_line,
768 UT_LINESIZE) == 0) {
769 /* Show it */
770 if (c == 0) {
cfa7fe89 771 quit = list(ctl, &ut, p->ut.UL_UT_TIME, R_NORMAL);
ce602720
OO
772 c = 1;
773 }
774 if (p->next) p->next->prev = p->prev;
775 if (p->prev)
776 p->prev->next = p->next;
777 else
778 utmplist = p->next;
779 free(p);
780 }
781 }
782 /*
77079432
KZ
783 * Not found? Then crashed, down, still
784 * logged in, or missing logout record.
ce602720
OO
785 */
786 if (c == 0) {
ee24e58f 787 if (!lastboot) {
ce602720
OO
788 c = R_NOW;
789 /* Is process still alive? */
404fa3f9 790 if (is_phantom(&ut))
ce602720
OO
791 c = R_PHANTOM;
792 } else
793 c = whydown;
83484375 794 quit = list(ctl, &ut, lastboot, c);
ce602720
OO
795 }
796 /* FALLTHRU */
797
798 case DEAD_PROCESS:
799 /*
77079432
KZ
800 * Just store the data if it is
801 * interesting enough.
ce602720
OO
802 */
803 if (ut.ut_line[0] == 0)
804 break;
a9f789e5 805 p = xmalloc(sizeof(struct utmplist));
ce602720
OO
806 memcpy(&p->ut, &ut, sizeof(struct utmp));
807 p->next = utmplist;
808 p->prev = NULL;
809 if (utmplist) utmplist->prev = p;
810 utmplist = p;
811 break;
812
5cae90f9
SK
813 case EMPTY:
814 case INIT_PROCESS:
815 case LOGIN_PROCESS:
816 case ACCOUNTING:
817 /* ignored ut_types */
818 break;
819
820 default:
821 warnx("unrecogized ut_type: %d", ut.ut_type);
ce602720 822 }
77079432
KZ
823
824 /*
825 * If we saw a shutdown/reboot record we can remove
826 * the entire current utmplist.
827 */
828 if (down) {
cfa7fe89 829 lastboot = ut.UL_UT_TIME;
77079432
KZ
830 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
831 for (p = utmplist; p; p = next) {
832 next = p->next;
833 free(p);
834 }
835 utmplist = NULL;
836 down = 0;
837 }
ce602720 838 }
ce602720 839
83484375 840 printf(_("\n%s begins %s"), basename(ctl->altv[ctl->alti]), ctime(&begintime));
77079432 841 fclose(fp);
bce5163b
SK
842 for (p = utmplist; p; p = next) {
843 next = p->next;
844 free(p);
845 }
7ec78229
SK
846}
847
848int main(int argc, char **argv)
849{
83484375
SK
850 struct last_control ctl = {
851 .showhost = TRUE,
852 .name_len = LAST_LOGIN_LEN,
45f11b6b 853 .time_fmt = LAST_TIMEFTM_SHORT_CTIME,
83484375
SK
854 .domain_len = LAST_DOMAIN_LEN
855 };
856 int c;
3a4ae395 857 usec_t p;
7ec78229 858
45f11b6b
SK
859 enum {
860 OPT_TIME_FORMAT = CHAR_MAX + 1
861 };
7ec78229
SK
862 static const struct option long_opts[] = {
863 { "limit", required_argument, NULL, 'n' },
864 { "help", no_argument, NULL, 'h' },
865 { "file", required_argument, NULL, 'f' },
866 { "nohostname", no_argument, NULL, 'R' },
867 { "version", no_argument, NULL, 'V' },
868 { "hostlast", no_argument, NULL, 'a' },
dd50168b 869 { "since", required_argument, NULL, 's' },
7ec78229 870 { "until", required_argument, NULL, 't' },
37f534a6 871 { "present", required_argument, NULL, 'p' },
7ec78229
SK
872 { "system", no_argument, NULL, 'x' },
873 { "dns", no_argument, NULL, 'd' },
874 { "ip", no_argument, NULL, 'i' },
875 { "fulltimes", no_argument, NULL, 'F' },
876 { "fullnames", no_argument, NULL, 'w' },
45f11b6b 877 { "time-format", required_argument, NULL, OPT_TIME_FORMAT },
7ec78229
SK
878 { NULL, 0, NULL, 0 }
879 };
2ea747eb
KZ
880 static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */
881 { 'F', OPT_TIME_FORMAT }, /* fulltime, time-format */
882 { 0 }
883 };
884 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
7ec78229
SK
885
886 setlocale(LC_ALL, "");
887 bindtextdomain(PACKAGE, LOCALEDIR);
888 textdomain(PACKAGE);
889 atexit(close_stdout);
890
891 while ((c = getopt_long(argc, argv,
dd50168b 892 "hVf:n:RxadFit:p:s:0123456789w", long_opts, NULL)) != -1) {
2ea747eb
KZ
893
894 err_exclusive_options(c, long_opts, excl, excl_st);
895
7ec78229
SK
896 switch(c) {
897 case 'h':
898 usage(stdout);
899 break;
900 case 'V':
901 printf(UTIL_LINUX_VERSION);
902 return EXIT_SUCCESS;
903 case 'R':
ee24e58f 904 ctl.showhost = 0;
7ec78229
SK
905 break;
906 case 'x':
ee24e58f 907 ctl.extended = 1;
7ec78229
SK
908 break;
909 case 'n':
83484375 910 ctl.maxrecs = strtos32_or_err(optarg, _("failed to parse number"));
7ec78229
SK
911 break;
912 case 'f':
83484375
SK
913 if (!ctl.altv)
914 ctl.altv = xmalloc(sizeof(char *) * argc);
915 ctl.altv[ctl.altc++] = xstrdup(optarg);
7ec78229
SK
916 break;
917 case 'd':
ee24e58f 918 ctl.usedns = 1;
7ec78229
SK
919 break;
920 case 'i':
ee24e58f 921 ctl.useip = 1;
7ec78229
SK
922 break;
923 case 'a':
ee24e58f 924 ctl.altlist = 1;
7ec78229
SK
925 break;
926 case 'F':
ee24e58f 927 ctl.fulltime = 1;
45f11b6b 928 ctl.time_fmt = LAST_TIMEFTM_FULL_CTIME;
7ec78229 929 break;
37f534a6 930 case 'p':
3a4ae395 931 if (parse_timestamp(optarg, &p) < 0)
37f534a6 932 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 933 ctl.present = (time_t) (p / 1000000);
37f534a6 934 break;
dd50168b 935 case 's':
dd50168b
SK
936 if (parse_timestamp(optarg, &p) < 0)
937 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 938 ctl.since = (time_t) (p / 1000000);
dd50168b 939 break;
7ec78229 940 case 't':
3a4ae395 941 if (parse_timestamp(optarg, &p) < 0)
7ec78229 942 errx(EXIT_FAILURE, _("invalid time value \"%s\""), optarg);
83484375 943 ctl.until = (time_t) (p / 1000000);
7ec78229
SK
944 break;
945 case 'w':
83484375
SK
946 if (ctl.name_len < UT_NAMESIZE)
947 ctl.name_len = UT_NAMESIZE;
948 if (ctl.domain_len < UT_HOSTSIZE)
949 ctl.domain_len = UT_HOSTSIZE;
7ec78229
SK
950 break;
951 case '0': case '1': case '2': case '3': case '4':
952 case '5': case '6': case '7': case '8': case '9':
83484375 953 ctl.maxrecs = 10 * ctl.maxrecs + c - '0';
7ec78229 954 break;
45f11b6b
SK
955 case OPT_TIME_FORMAT:
956 ctl.time_fmt = which_time_format(optarg);
957 if (ctl.time_fmt == LAST_TIMEFTM_ISO8601)
ee24e58f 958 ctl.fulltime = 1;
45f11b6b 959 break;
7ec78229
SK
960 default:
961 usage(stderr);
962 break;
963 }
964 }
965
966 if (optind < argc)
83484375 967 ctl.show = argv + optind;
7ec78229
SK
968
969 /*
970 * Which file do we want to read?
971 */
ee24e58f 972 ctl.lastb = strcmp(program_invocation_short_name, "lastb") == 0 ? 1 : 0;
83484375
SK
973 if (!ctl.altc) {
974 ctl.altv = xmalloc(sizeof(char *));
975 if (ctl.lastb)
976 ctl.altv[0] = xstrdup(_PATH_BTMP);
7ec78229 977 else
83484375
SK
978 ctl.altv[0] = xstrdup(_PATH_WTMP);
979 ctl.altc++;
7ec78229
SK
980 }
981
83484375
SK
982 for (; ctl.alti < ctl.altc; ctl.alti++) {
983 process_wtmp_file(&ctl);
984 free(ctl.altv[ctl.alti]);
7ec78229 985 }
83484375 986 free(ctl.altv);
77079432 987 return EXIT_SUCCESS;
6dbe3af9 988}