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