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