2 * last(1) from sysvinit project, merged into util-linux in Aug 2013.
4 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
5 * Copyright (C) 2013 Ondrej Oprala <ooprala@redhat.com>
6 * Karel Zak <kzak@redhat.com>
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)
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.
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.
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://gnu.org/licenses/>.
26 #include <sys/types.h>
39 #include <netinet/in.h>
41 #include <arpa/inet.h>
47 #include "pathnames.h"
49 #include "closestream.h"
50 #include "carefulputc.h"
52 #include "timeutils.h"
53 #include "monotonic.h"
54 #include "fileutils.h"
61 # define SHUTDOWN_TIME 254
64 #ifndef LAST_LOGIN_LEN
65 # define LAST_LOGIN_LEN 8
68 #ifndef LAST_DOMAIN_LEN
69 # define LAST_DOMAIN_LEN 16
72 #ifndef LAST_TIMESTAMP_LEN
73 # define LAST_TIMESTAMP_LEN 32
76 #define UCHUNKSIZE 16384 /* How much we read at once. */
79 bool lastb
, /* Is this command 'lastb' */
80 extended
, /* Lots of info */
81 showhost
, /* Show hostname */
82 altlist
, /* Hostname at the end */
83 usedns
, /* Use DNS to lookup the hostname */
84 useip
; /* Print IP address in number format */
86 unsigned int name_len
; /* Number of login name characters to print */
87 unsigned int domain_len
; /* Number of domain name characters to print */
88 unsigned int maxrecs
; /* Maximum number of records to list */
90 char **show
; /* Match search list */
92 struct timeval boot_time
; /* system boot time */
93 time_t since
; /* at what time to start displaying the file */
94 time_t until
; /* at what time to stop displaying the file */
95 time_t present
; /* who where present at time_t */
96 unsigned int time_fmt
; /* time format */
97 char separator
; /* output separator */
102 /* Double linked list of struct utmp's */
105 struct utmplist
*next
;
106 struct utmplist
*prev
;
109 /* Types of listing */
111 R_CRASH
= 1, /* No logout record, system boot in between */
112 R_DOWN
, /* System brought down in decent way */
113 R_NORMAL
, /* Normal */
114 R_NOW
, /* Still logged in */
115 R_REBOOT
, /* Reboot record. */
116 R_REBOOT_CRASH
, /* Reboot record without matching shutdown */
117 R_PHANTOM
, /* No logout record but session is stale. */
118 R_TIMECHANGE
/* NEW_TIME or OLD_TIME */
122 LAST_TIMEFTM_NONE
= 0,
125 LAST_TIMEFTM_ISO8601
,
127 LAST_TIMEFTM_HHMM
, /* non-public */
130 struct last_timefmt
{
132 int in_len
; /* log-in */
134 int out_len
; /* log-out */
138 static struct last_timefmt timefmts
[] = {
139 [LAST_TIMEFTM_NONE
] = { .name
= "notime" },
140 [LAST_TIMEFTM_SHORT
] = {
144 .in_fmt
= LAST_TIMEFTM_CTIME
,
145 .out_fmt
= LAST_TIMEFTM_HHMM
147 [LAST_TIMEFTM_CTIME
] = {
151 .in_fmt
= LAST_TIMEFTM_CTIME
,
152 .out_fmt
= LAST_TIMEFTM_CTIME
154 [LAST_TIMEFTM_ISO8601
] = {
158 .in_fmt
= LAST_TIMEFTM_ISO8601
,
159 .out_fmt
= LAST_TIMEFTM_ISO8601
163 /* Global variables */
164 static unsigned int recsdone
; /* Number of records listed */
165 static time_t lastdate
; /* Last date we've seen */
166 static time_t currentdate
; /* date when we started processing the file */
169 /* --time-format=option parser */
170 static int which_time_format(const char *s
)
174 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
175 if (strcmp(timefmts
[i
].name
, s
) == 0)
178 errx(EXIT_FAILURE
, _("unknown time format: %s"), s
);
183 * Read one utmp entry, return in new format.
184 * Automatically reposition file pointer.
186 static int uread(FILE *fp
, struct utmpx
*u
, int *quit
, const char *filename
)
189 static char buf
[UCHUNKSIZE
];
195 if (quit
== NULL
&& u
!= NULL
) {
199 return fread(u
, sizeof(struct utmpx
), 1, fp
);
204 * Initialize and position.
206 utsize
= sizeof(struct utmpx
);
207 fseeko(fp
, 0, SEEK_END
);
211 o
= ((fpos
- 1) / UCHUNKSIZE
) * UCHUNKSIZE
;
212 if (fseeko(fp
, o
, SEEK_SET
) < 0) {
213 warn(_("seek on %s failed"), filename
);
216 bpos
= (int)(fpos
- o
);
217 if (fread(buf
, bpos
, 1, fp
) != 1) {
218 warn(_("cannot read %s"), filename
);
226 * Read one struct. From the buffer if possible.
230 memcpy(u
, buf
+ bpos
, sizeof(struct utmpx
));
235 * Oops we went "below" the buffer. We should be able to
236 * seek back UCHUNKSIZE bytes.
243 * Copy whatever is left in the buffer.
245 memcpy(tmp
+ (-bpos
), buf
, utsize
+ bpos
);
246 if (fseeko(fp
, fpos
, SEEK_SET
) < 0) {
247 warn(_("seek on %s failed"), filename
);
252 * Read another UCHUNKSIZE bytes.
254 if (fread(buf
, UCHUNKSIZE
, 1, fp
) != 1) {
255 warn(_("cannot read %s"), filename
);
260 * The end of the UCHUNKSIZE byte buffer should be the first
261 * few bytes of the current struct utmp.
263 memcpy(tmp
, buf
+ UCHUNKSIZE
+ bpos
, -bpos
);
266 memcpy(u
, tmp
, sizeof(struct utmpx
));
275 static void int_handler(int sig
__attribute__((unused
)))
277 ul_sig_err(EXIT_FAILURE
, "Interrupted");
283 static void quit_handler(int sig
__attribute__((unused
)))
285 ul_sig_warn("Interrupted");
286 signal(SIGQUIT
, quit_handler
);
291 * Lookup a host with DNS.
293 static int dns_lookup(char *result
, int size
, int useip
, int32_t *a
)
295 struct sockaddr_in sin
;
296 struct sockaddr_in6 sin6
;
301 flags
= useip
? NI_NUMERICHOST
: 0;
305 * 1. If last 3 4bytes are 0, must be IPv4
306 * 2. If IPv6 in IPv4, handle as IPv4
307 * 3. Anything else is IPv6
311 if (a
[0] == 0 && a
[1] == 0 && a
[2] == (int32_t)htonl (0xffff))
314 if (mapped
|| (a
[1] == 0 && a
[2] == 0 && a
[3] == 0)) {
316 sin
.sin_family
= AF_INET
;
318 sin
.sin_addr
.s_addr
= mapped
? a
[3] : a
[0];
319 sa
= (struct sockaddr
*)&sin
;
323 memset(&sin6
, 0, sizeof(sin6
));
324 sin6
.sin6_family
= AF_INET6
;
326 memcpy(sin6
.sin6_addr
.s6_addr
, a
, 16);
327 sa
= (struct sockaddr
*)&sin6
;
328 salen
= sizeof(sin6
);
331 return getnameinfo(sa
, salen
, result
, size
, NULL
, 0, flags
);
334 static int time_formatter(int fmt
, char *dst
, size_t dlen
, time_t *when
)
339 case LAST_TIMEFTM_NONE
:
342 case LAST_TIMEFTM_HHMM
:
346 localtime_r(when
, &tm
);
347 if (!snprintf(dst
, dlen
, "%02d:%02d", tm
.tm_hour
, tm
.tm_min
))
351 case LAST_TIMEFTM_CTIME
:
353 char buf
[CTIME_BUFSIZ
];
355 if (!ctime_r(when
, buf
)) {
359 snprintf(dst
, dlen
, "%s", buf
);
360 ret
= rtrim_whitespace((unsigned char *) dst
);
363 case LAST_TIMEFTM_ISO8601
:
364 ret
= strtime_iso(when
, ISO_TIMESTAMP_T
, dst
, dlen
);
373 * Remove trailing spaces from a string.
375 static void trim_trailing_spaces(char *s
)
381 while (p
> s
&& isspace(*--p
))
390 * Show one line of information on screen
392 static int list(const struct last_control
*ctl
, struct utmpx
*p
, time_t logout_time
, int what
)
394 time_t secs
, utmp_time
;
395 char logintime
[LAST_TIMESTAMP_LEN
];
396 char logouttime
[LAST_TIMESTAMP_LEN
];
397 char length
[LAST_TIMESTAMP_LEN
];
399 char utline
[sizeof(p
->ut_line
) + 1];
401 int mins
, hours
, days
;
403 struct last_timefmt
*fmt
;
406 * uucp and ftp have special-type entries
408 mem2strcpy(utline
, p
->ut_line
, sizeof(p
->ut_line
), sizeof(utline
));
409 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
411 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
415 * Is this something we want to show?
419 for (walk
= ctl
->show
; *walk
; walk
++) {
420 if (strncmp(p
->ut_user
, *walk
, sizeof(p
->ut_user
)) == 0 ||
421 strcmp(utline
, *walk
) == 0 ||
422 (strncmp(utline
, "tty", 3) == 0 &&
423 strcmp(utline
+ 3, *walk
) == 0)) break;
425 if (*walk
== NULL
) return 0;
431 fmt
= &timefmts
[ctl
->time_fmt
];
433 utmp_time
= p
->ut_tv
.tv_sec
;
436 if (ctl
->present
< utmp_time
)
438 if (0 < logout_time
&& logout_time
< ctl
->present
)
443 if (time_formatter(fmt
->in_fmt
, logintime
,
444 sizeof(logintime
), &utmp_time
) < 0)
445 errx(EXIT_FAILURE
, _("preallocation size exceeded"));
448 secs
= logout_time
- utmp_time
; /* Under strange circumstances, secs < 0 can happen */
449 mins
= (secs
/ 60) % 60;
450 hours
= (secs
/ 3600) % 24;
453 strcpy(logouttime
, "- ");
454 if (time_formatter(fmt
->out_fmt
, logouttime
+ 2,
455 sizeof(logouttime
) - 2, &logout_time
) < 0)
456 errx(EXIT_FAILURE
, _("preallocation size exceeded"));
458 if (logout_time
== currentdate
) {
459 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT
) {
460 snprintf(logouttime
, sizeof(logouttime
), " still running");
463 snprintf(logouttime
, sizeof(logouttime
), " still");
464 snprintf(length
, sizeof(length
), "running");
467 snprintf(length
, sizeof(length
), "(%d+%02d:%02d)", days
, abs(hours
), abs(mins
)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */
469 snprintf(length
, sizeof(length
), " (%02d:%02d)", hours
, abs(mins
)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
470 } else if (secs
>= 0) {
471 snprintf(length
, sizeof(length
), " (%02d:%02d)", hours
, mins
);
473 snprintf(length
, sizeof(length
), " (-00:%02d)", abs(mins
)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */
479 snprintf(logouttime
, sizeof(logouttime
), "- crash");
482 snprintf(logouttime
, sizeof(logouttime
), "- down ");
485 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT
) {
486 snprintf(logouttime
, sizeof(logouttime
), " still logged in");
489 snprintf(logouttime
, sizeof(logouttime
), " still");
490 snprintf(length
, sizeof(length
), "logged in");
494 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT
) {
495 snprintf(logouttime
, sizeof(logouttime
), " gone - no logout");
497 } else if (ctl
->time_fmt
== LAST_TIMEFTM_SHORT
) {
498 snprintf(logouttime
, sizeof(logouttime
), " gone");
499 snprintf(length
, sizeof(length
), "- no logout");
502 snprintf(length
, sizeof(length
), "no logout");
517 * Look up host with DNS if needed.
520 if (ctl
->usedns
|| ctl
->useip
)
521 r
= dns_lookup(domain
, sizeof(domain
), ctl
->useip
, (int32_t*)p
->ut_addr_v6
);
523 mem2strcpy(domain
, p
->ut_host
, sizeof(p
->ut_host
), sizeof(domain
));
526 * set last displayed character to an asterisk when
527 * user/domain/ip fields are to be truncated in non-fullnames mode
529 if (!ctl
->fullnames_mode
&& (strnlen(p
->ut_user
, sizeof(p
->ut_user
)) > ctl
->name_len
))
530 p
->ut_user
[ctl
->name_len
-1] = '*';
535 if (!ctl
->fullnames_mode
&& (strnlen(domain
, sizeof(domain
)) > ctl
->domain_len
))
536 domain
[ctl
->domain_len
-1] = '*';
538 len
= snprintf(final
, sizeof(final
),
539 "%-8.*s%c%-12.12s%c%-16.*s%c%-*.*s%c%-*.*s%c%s\n",
540 ctl
->name_len
, p
->ut_user
, ctl
->separator
, utline
, ctl
->separator
,
541 ctl
->domain_len
, domain
, ctl
->separator
,
542 fmt
->in_len
, fmt
->in_len
, logintime
, ctl
->separator
, fmt
->out_len
, fmt
->out_len
,
543 logouttime
, ctl
->separator
, length
);
545 len
= snprintf(final
, sizeof(final
),
546 "%-8.*s%c%-12.12s%c%-*.*s%c%-*.*s%c%-12.12s%c%s\n",
547 ctl
->name_len
, p
->ut_user
, ctl
->separator
, utline
, ctl
->separator
,
548 fmt
->in_len
, fmt
->in_len
, logintime
, ctl
->separator
, fmt
->out_len
, fmt
->out_len
,
549 logouttime
, ctl
->separator
, length
, ctl
->separator
, domain
);
552 len
= snprintf(final
, sizeof(final
),
553 "%-8.*s%c%-12.12s%c%-*.*s%c%-*.*s%c%s\n",
554 ctl
->name_len
, p
->ut_user
, ctl
->separator
, utline
, ctl
->separator
,
555 fmt
->in_len
, fmt
->in_len
, logintime
, ctl
->separator
, fmt
->out_len
, fmt
->out_len
,
556 logouttime
, ctl
->separator
, length
);
558 #if defined(__GLIBC__)
559 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
560 final
[sizeof(final
)-1] = '\0';
564 trim_trailing_spaces(final
);
566 * Print out "final" string safely.
568 fputs_careful(final
, stdout
, '*', false, 0);
570 if (len
< 0 || (size_t)len
>= sizeof(final
))
574 if (ctl
->maxrecs
&& ctl
->maxrecs
<= recsdone
)
581 static void __attribute__((__noreturn__
)) usage(const struct last_control
*ctl
)
584 fputs(USAGE_HEADER
, out
);
586 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name
);
588 fputs(USAGE_SEPARATOR
, out
);
589 fputs(_("Show a listing of last logged in users.\n"), out
);
591 fputs(USAGE_OPTIONS
, out
);
592 fputs(_(" -<number> how many lines to show\n"), out
);
593 fputs(_(" -a, --hostlast display hostnames in the last column\n"), out
);
594 fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out
);
596 _(" -f, --file <file> use a specific file instead of %s\n"), ctl
->lastb
? _PATH_BTMP
: _PATH_WTMP
);
597 fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out
);
598 fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out
);
599 fputs(_(" -n, --limit <number> how many lines to show\n"), out
);
600 fputs(_(" -p, --present <time> display who were present at the specified time\n"), out
);
601 fputs(_(" -R, --nohostname don't display the hostname field\n"), out
);
602 fputs(_(" -s, --since <time> display the lines since the specified time\n"), out
);
603 fputs(_(" -t, --until <time> display the lines until the specified time\n"), out
);
604 fputs(_(" -T, --tab-separated use tabs as delimiters\n"), out
);
605 fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
606 " notime|short|full|iso\n"), out
);
607 fputs(_(" -w, --fullnames display full user and domain names\n"), out
);
608 fputs(_(" -x, --system display system shutdown entries and run level changes\n"), out
);
610 fputs(USAGE_SEPARATOR
, out
);
611 fprintf(out
, USAGE_HELP_OPTIONS(22));
612 fprintf(out
, USAGE_MAN_TAIL("last(1)"));
614 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
618 static int is_phantom(const struct last_control
*ctl
, struct utmpx
*ut
)
621 char path
[sizeof(ut
->ut_line
) + 16];
622 char user
[sizeof(ut
->ut_user
) + 1];
625 if (ut
->ut_tv
.tv_sec
< ctl
->boot_time
.tv_sec
)
628 mem2strcpy(user
, ut
->ut_user
, sizeof(ut
->ut_user
), sizeof(user
));
632 snprintf(path
, sizeof(path
), "/proc/%u/loginuid", ut
->ut_pid
);
633 if (access(path
, R_OK
) == 0) {
634 unsigned int loginuid
;
637 if (!(f
= fopen(path
, "r")))
639 if (fscanf(f
, "%u", &loginuid
) != 1)
642 if (!ret
&& pw
->pw_uid
!= loginuid
)
646 char utline
[sizeof(ut
->ut_line
) + 1];
648 mem2strcpy(utline
, ut
->ut_line
, sizeof(ut
->ut_line
), sizeof(utline
));
650 snprintf(path
, sizeof(path
), "/dev/%s", utline
);
653 if (pw
->pw_uid
!= st
.st_uid
)
659 static void process_wtmp_file(const struct last_control
*ctl
,
660 const char *filename
)
662 FILE *fp
; /* File pointer of wtmp file */
664 struct utmpx ut
; /* Current utmp entry */
665 struct utmplist
*ulist
= NULL
; /* All entries */
666 struct utmplist
*p
; /* Pointer into utmplist */
667 struct utmplist
*next
; /* Pointer into utmplist */
669 time_t lastboot
= 0; /* Last boottime */
670 time_t lastrch
= 0; /* Last run level change */
671 time_t lastdown
; /* Last downtime */
672 time_t begintime
; /* When wtmp begins */
673 int whydown
= 0; /* Why we went down: crash or shutdown */
675 int c
, x
; /* Scratch */
676 struct stat st
; /* To stat the [uw]tmp file */
677 int quit
= 0; /* Flag */
678 int down
= 0; /* Down flag */
683 lastdown
= 1596001948;
688 lastdate
= currentdate
= lastrch
= lastdown
;
692 * Install signal handlers
694 signal(SIGINT
, int_handler
);
695 signal(SIGQUIT
, quit_handler
);
701 if ((fp
= fopen(filename
, "r")) == NULL
)
702 err(EXIT_FAILURE
, _("cannot open %s"), filename
);
705 * Optimize the buffer size.
707 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
710 * Read first structure to capture the time field
712 if (uread(fp
, &ut
, NULL
, filename
) == 1)
713 begintime
= ut
.ut_tv
.tv_sec
;
715 if (fstat(fileno(fp
), &st
) != 0)
716 err(EXIT_FAILURE
, _("stat of %s failed"), filename
);
717 begintime
= st
.st_ctime
;
722 * Go to end of file minus one structure
723 * and/or initialize utmp reading code.
725 uread(fp
, NULL
, NULL
, filename
);
728 * Read struct after struct backwards from the file.
732 if (uread(fp
, &ut
, &quit
, filename
) != 1)
735 if (ctl
->since
&& ut
.ut_tv
.tv_sec
< ctl
->since
)
738 if (ctl
->until
&& ctl
->until
< ut
.ut_tv
.tv_sec
)
741 lastdate
= ut
.ut_tv
.tv_sec
;
744 quit
= list(ctl
, &ut
, ut
.ut_tv
.tv_sec
, R_NORMAL
);
749 * Set ut_type to the correct type.
751 if (strncmp(ut
.ut_line
, "~", 1) == 0) {
752 if (strncmp(ut
.ut_user
, "shutdown", 8) == 0)
753 ut
.ut_type
= SHUTDOWN_TIME
;
754 else if (strncmp(ut
.ut_user
, "reboot", 6) == 0)
755 ut
.ut_type
= BOOT_TIME
;
756 else if (strncmp(ut
.ut_user
, "runlevel", 8) == 0)
757 ut
.ut_type
= RUN_LVL
;
761 * For stupid old applications that don't fill in
765 if (ut
.ut_type
!= DEAD_PROCESS
&&
766 ut
.ut_user
[0] && ut
.ut_line
[0] &&
767 strncmp(ut
.ut_user
, "LOGIN", 5) != 0)
768 ut
.ut_type
= USER_PROCESS
;
770 * Even worse, applications that write ghost
771 * entries: ut_type set to USER_PROCESS but
774 if (ut
.ut_user
[0] == 0)
775 ut
.ut_type
= DEAD_PROCESS
;
780 if (strncmp(ut
.ut_user
, "date", 4) == 0) {
781 if (ut
.ut_line
[0] == '|')
782 ut
.ut_type
= OLD_TIME
;
783 if (ut
.ut_line
[0] == '{')
784 ut
.ut_type
= NEW_TIME
;
788 switch (ut
.ut_type
) {
791 strcpy(ut
.ut_line
, "system down");
792 quit
= list(ctl
, &ut
, lastboot
, R_NORMAL
);
794 lastdown
= lastrch
= ut
.ut_tv
.tv_sec
;
801 ut
.ut_type
== NEW_TIME
? "new time" :
803 quit
= list(ctl
, &ut
, lastdown
, R_TIMECHANGE
);
807 strcpy(ut
.ut_line
, "system boot");
808 if (lastdown
> lastboot
&& lastdown
!= currentdate
)
809 quit
= list(ctl
, &ut
, lastboot
, R_REBOOT_CRASH
);
811 quit
= list(ctl
, &ut
, lastdown
, R_REBOOT
);
812 lastboot
= ut
.ut_tv
.tv_sec
;
818 snprintf(ut
.ut_line
, sizeof(ut
.ut_line
), "(to lvl %c)", x
);
819 quit
= list(ctl
, &ut
, lastrch
, R_NORMAL
);
821 if (x
== '0' || x
== '6') {
822 lastdown
= ut
.ut_tv
.tv_sec
;
824 ut
.ut_type
= SHUTDOWN_TIME
;
826 lastrch
= ut
.ut_tv
.tv_sec
;
831 * This was a login - show the first matching
832 * logout record and delete all records with
836 for (p
= ulist
; p
; p
= next
) {
838 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
839 sizeof(ut
.ut_line
)) == 0) {
842 quit
= list(ctl
, &ut
, p
->ut
.ut_tv
.tv_sec
, R_NORMAL
);
846 p
->next
->prev
= p
->prev
;
848 p
->prev
->next
= p
->next
;
855 * Not found? Then crashed, down, still
856 * logged in, or missing logout record.
861 /* Is process still alive? */
862 if (is_phantom(ctl
, &ut
))
866 quit
= list(ctl
, &ut
, lastboot
, c
);
872 * Just store the data if it is
873 * interesting enough.
875 if (ut
.ut_line
[0] == 0)
877 p
= xmalloc(sizeof(struct utmplist
));
878 memcpy(&p
->ut
, &ut
, sizeof(struct utmpx
));
892 /* ignored ut_types */
896 warnx("unrecognized ut_type: %d", ut
.ut_type
);
900 * If we saw a shutdown/reboot record we can remove
901 * the entire current ulist.
904 lastboot
= ut
.ut_tv
.tv_sec
;
905 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
906 for (p
= ulist
; p
; p
= next
) {
915 if (ctl
->time_fmt
!= LAST_TIMEFTM_NONE
) {
916 struct last_timefmt
*fmt
;
917 char timestr
[LAST_TIMESTAMP_LEN
];
918 char *tmp
= xstrdup(filename
);
920 fmt
= &timefmts
[ctl
->time_fmt
];
921 if (time_formatter(fmt
->in_fmt
, timestr
,
922 sizeof(timestr
), &begintime
) < 0)
923 errx(EXIT_FAILURE
, _("preallocation size exceeded"));
924 printf(_("\n%s begins %s\n"), basename(tmp
), timestr
);
930 for (p
= ulist
; p
; p
= next
) {
939 int LLVMFuzzerTestOneInput(const uint8_t *data
, size_t size
) {
940 struct last_control ctl
= {
942 .name_len
= LAST_LOGIN_LEN
,
943 .time_fmt
= LAST_TIMEFTM_SHORT
,
944 .domain_len
= LAST_DOMAIN_LEN
,
946 .tv_sec
= 1595978419,
950 char name
[] = "/tmp/test-last-fuzz.XXXXXX";
953 fd
= mkstemp_cloexec(name
);
955 err(EXIT_FAILURE
, "mkstemp() failed");
956 if (write_all(fd
, data
, size
) != 0)
957 err(EXIT_FAILURE
, "write() failed");
959 process_wtmp_file(&ctl
, name
);
967 int main(int argc
, char **argv
)
969 struct last_control ctl
= {
971 .name_len
= LAST_LOGIN_LEN
,
972 .time_fmt
= LAST_TIMEFTM_SHORT
,
973 .domain_len
= LAST_DOMAIN_LEN
,
974 .fullnames_mode
= false,
977 size_t i
, nfiles
= 0;
982 OPT_TIME_FORMAT
= CHAR_MAX
+ 1
984 static const struct option long_opts
[] = {
985 { "limit", required_argument
, NULL
, 'n' },
986 { "help", no_argument
, NULL
, 'h' },
987 { "file", required_argument
, NULL
, 'f' },
988 { "nohostname", no_argument
, NULL
, 'R' },
989 { "version", no_argument
, NULL
, 'V' },
990 { "hostlast", no_argument
, NULL
, 'a' },
991 { "since", required_argument
, NULL
, 's' },
992 { "until", required_argument
, NULL
, 't' },
993 { "present", required_argument
, NULL
, 'p' },
994 { "system", no_argument
, NULL
, 'x' },
995 { "dns", no_argument
, NULL
, 'd' },
996 { "ip", no_argument
, NULL
, 'i' },
997 { "fulltimes", no_argument
, NULL
, 'F' },
998 { "fullnames", no_argument
, NULL
, 'w' },
999 { "tab-separated", no_argument
, NULL
, 'T' },
1000 { "time-format", required_argument
, NULL
, OPT_TIME_FORMAT
},
1001 { NULL
, 0, NULL
, 0 }
1003 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1004 { 'F', OPT_TIME_FORMAT
}, /* fulltime, time-format */
1007 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1009 setlocale(LC_ALL
, "");
1010 bindtextdomain(PACKAGE
, LOCALEDIR
);
1011 textdomain(PACKAGE
);
1012 close_stdout_atexit();
1014 * Which file do we want to read?
1016 ctl
.lastb
= strcmp(program_invocation_short_name
, "lastb") == 0 ? 1 : 0;
1017 ctl
.separator
= ' ';
1018 while ((c
= getopt_long(argc
, argv
,
1019 "hVf:n:RxadFit:p:s:T0123456789w", long_opts
, NULL
)) != -1) {
1021 err_exclusive_options(c
, long_opts
, excl
, excl_st
);
1028 print_version(EXIT_SUCCESS
);
1036 ctl
.maxrecs
= strtos32_or_err(optarg
, _("failed to parse number"));
1040 files
= xmalloc(sizeof(char *) * argc
);
1041 files
[nfiles
++] = xstrdup(optarg
);
1053 ctl
.time_fmt
= LAST_TIMEFTM_CTIME
;
1056 if (ul_parse_timestamp(optarg
, &p
) < 0)
1057 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
1058 ctl
.present
= (time_t) (p
/ 1000000);
1061 if (ul_parse_timestamp(optarg
, &p
) < 0)
1062 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
1063 ctl
.since
= (time_t) (p
/ 1000000);
1066 if (ul_parse_timestamp(optarg
, &p
) < 0)
1067 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
1068 ctl
.until
= (time_t) (p
/ 1000000);
1071 ctl
.fullnames_mode
= true;
1072 if (ctl
.name_len
< sizeof_member(struct utmpx
, ut_user
))
1073 ctl
.name_len
= sizeof_member(struct utmpx
, ut_user
);
1074 if (ctl
.domain_len
< sizeof_member(struct utmpx
, ut_host
))
1075 ctl
.domain_len
= sizeof_member(struct utmpx
, ut_host
);
1077 case '0': case '1': case '2': case '3': case '4':
1078 case '5': case '6': case '7': case '8': case '9':
1079 ctl
.maxrecs
= 10 * ctl
.maxrecs
+ c
- '0';
1081 case OPT_TIME_FORMAT
:
1082 ctl
.time_fmt
= which_time_format(optarg
);
1085 ctl
.separator
= '\t';
1088 errtryhelp(EXIT_FAILURE
);
1093 ctl
.show
= argv
+ optind
;
1096 files
= xmalloc(sizeof(char *));
1097 files
[nfiles
++] = xstrdup(ctl
.lastb
? _PATH_BTMP
: _PATH_WTMP
);
1100 for (i
= 0; i
< nfiles
; i
++) {
1101 get_boot_time(&ctl
.boot_time
);
1102 process_wtmp_file(&ctl
, files
[i
]);
1106 return EXIT_SUCCESS
;