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, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 #include <sys/types.h>
29 #include <sys/fcntl.h>
40 #include <netinet/in.h>
42 #include <arpa/inet.h>
47 #include "pathnames.h"
49 #include "closestream.h"
50 #include "carefulputc.h"
52 #include "timeutils.h"
55 #if defined(_HAVE_UT_TV)
56 # define UL_UT_TIME ut_tv.tv_sec
58 # define UL_UT_TIME ut_time
62 # define SHUTDOWN_TIME 254
65 #ifndef LAST_LOGIN_LEN
66 # define LAST_LOGIN_LEN 8
69 #ifndef LAST_DOMAIN_LEN
70 # define LAST_DOMAIN_LEN 16
73 #ifndef LAST_TIMESTAMP_LEN
74 # define LAST_TIMESTAMP_LEN 32
77 #define UCHUNKSIZE 16384 /* How much we read at once. */
80 unsigned int lastb
:1, /* Is this command 'lastb' */
81 extended
:1, /* Lots of info */
82 showhost
:1, /* Show hostname */
83 altlist
:1, /* Hostname at the end */
84 usedns
:1, /* Use DNS to lookup the hostname */
85 useip
:1; /* Print IP address in number format */
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 */
91 char **show
; /* Match search list */
93 char **altv
; /* Alternate wtmp files */
94 unsigned int altc
; /* Number of alternative files */
95 unsigned int alti
; /* Index number of the alternative file */
97 struct timeval boot_time
; /* system boot time */
98 time_t since
; /* at what time to start displaying the file */
99 time_t until
; /* at what time to stop displaying the file */
100 time_t present
; /* who where present at time_t */
101 unsigned int time_fmt
; /* time format */
104 /* Double linked list of struct utmp's */
107 struct utmplist
*next
;
108 struct utmplist
*prev
;
110 struct utmplist
*utmplist
= NULL
;
112 /* Types of listing */
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 */
124 LAST_TIMEFTM_NONE
= 0,
125 LAST_TIMEFTM_SHORT_CTIME
,
126 LAST_TIMEFTM_FULL_CTIME
,
130 struct last_timefmt
{
136 static 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}
143 /* Global variables */
144 static unsigned int recsdone
; /* Number of records listed */
145 static time_t lastdate
; /* Last date we've seen */
147 /* --time-format=option parser */
148 static int which_time_format(const char *optarg
)
152 for (i
= 0; i
< ARRAY_SIZE(timefmts
); i
++) {
153 if (strcmp(timefmts
[i
].name
, optarg
) == 0)
156 errx(EXIT_FAILURE
, _("unknown time format: %s"), optarg
);
160 * Read one utmp entry, return in new format.
161 * Automatically reposition file pointer.
163 static int uread(const struct last_control
*ctl
, FILE *fp
, struct utmp
*u
,
167 static char buf
[UCHUNKSIZE
];
173 if (quit
== NULL
&& u
!= NULL
) {
177 return fread(u
, sizeof(struct utmp
), 1, fp
);
182 * Initialize and position.
184 utsize
= sizeof(struct utmp
);
185 fseeko(fp
, 0, SEEK_END
);
189 o
= ((fpos
- 1) / UCHUNKSIZE
) * UCHUNKSIZE
;
190 if (fseeko(fp
, o
, SEEK_SET
) < 0) {
191 warn(_("seek on %s failed"), ctl
->altv
[ctl
->alti
]);
194 bpos
= (int)(fpos
- o
);
195 if (fread(buf
, bpos
, 1, fp
) != 1) {
196 warn(_("cannot read %s"), ctl
->altv
[ctl
->alti
]);
204 * Read one struct. From the buffer if possible.
208 memcpy(u
, buf
+ bpos
, sizeof(struct utmp
));
213 * Oops we went "below" the buffer. We should be able to
214 * seek back UCHUNKSIZE bytes.
221 * Copy whatever is left in the buffer.
223 memcpy(tmp
+ (-bpos
), buf
, utsize
+ bpos
);
224 if (fseeko(fp
, fpos
, SEEK_SET
) < 0) {
225 warn(_("seek on %s failed"), ctl
->altv
[ctl
->alti
]);
230 * Read another UCHUNKSIZE bytes.
232 if (fread(buf
, UCHUNKSIZE
, 1, fp
) != 1) {
233 warn(_("cannot read %s"), ctl
->altv
[ctl
->alti
]);
238 * The end of the UCHUNKSIZE byte buffer should be the first
239 * few bytes of the current struct utmp.
241 memcpy(tmp
, buf
+ UCHUNKSIZE
+ bpos
, -bpos
);
244 memcpy(u
, tmp
, sizeof(struct utmp
));
250 * Print a short date.
252 static char *showdate(void)
254 char *s
= ctime(&lastdate
);
262 static void int_handler(int sig
__attribute__((unused
)))
264 errx(EXIT_FAILURE
, _("Interrupted %s"), showdate());
270 static void quit_handler(int sig
__attribute__((unused
)))
272 warnx(_("Interrupted %s"), showdate());
273 signal(SIGQUIT
, quit_handler
);
277 * Lookup a host with DNS.
279 static int dns_lookup(char *result
, int size
, int useip
, int32_t *a
)
281 struct sockaddr_in sin
;
282 struct sockaddr_in6 sin6
;
287 flags
= useip
? NI_NUMERICHOST
: 0;
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
297 if (a
[0] == 0 && a
[1] == 0 && a
[2] == (int32_t)htonl (0xffff))
300 if (mapped
|| (a
[1] == 0 && a
[2] == 0 && a
[3] == 0)) {
302 sin
.sin_family
= AF_INET
;
304 sin
.sin_addr
.s_addr
= mapped
? a
[3] : a
[0];
305 sa
= (struct sockaddr
*)&sin
;
309 memset(&sin6
, 0, sizeof(sin6
));
310 sin6
.sin6_family
= AF_INET6
;
312 memcpy(sin6
.sin6_addr
.s6_addr
, a
, 16);
313 sa
= (struct sockaddr
*)&sin6
;
314 salen
= sizeof(sin6
);
317 return getnameinfo(sa
, salen
, result
, size
, NULL
, 0, flags
);
320 static int time_formatter(const struct last_control
*ctl
, char *dst
,
321 size_t dlen
, time_t *when
, int pos
)
326 switch (ctl
->time_fmt
) {
327 case LAST_TIMEFTM_NONE
:
330 case LAST_TIMEFTM_SHORT_CTIME
:
332 ret
= sprintf(dst
, "%s", ctime(when
));
334 tm
= localtime(when
);
335 if (!strftime(dst
, dlen
, "- %H:%M", tm
))
339 case LAST_TIMEFTM_FULL_CTIME
:
341 ret
= sprintf(dst
, "%s", ctime(when
));
343 ret
= sprintf(dst
, "- %s", ctime(when
));
345 case LAST_TIMEFTM_ISO8601
:
346 tm
= localtime(when
);
348 if (!strftime(dst
, dlen
, "%Y-%m-%dT%H:%M:%S%z", tm
))
350 } else if (!strftime(dst
, dlen
, "- %Y-%m-%dT%H:%M:%S%z", tm
))
360 * Remove trailing spaces from a string.
362 static void trim_trailing_spaces(char *s
)
368 while (p
> s
&& isspace(*--p
))
377 * Show one line of information on screen
379 static int list(const struct last_control
*ctl
, struct utmp
*p
, time_t t
, int what
)
381 time_t secs
, tmp
, epoch
;
382 char logintime
[LAST_TIMESTAMP_LEN
];
383 char logouttime
[LAST_TIMESTAMP_LEN
];
384 char length
[LAST_TIMESTAMP_LEN
];
386 char utline
[UT_LINESIZE
+1];
389 int mins
, hours
, days
;
391 struct last_timefmt
*fmt
;
394 * uucp and ftp have special-type entries
397 strncat(utline
, p
->ut_line
, UT_LINESIZE
);
398 if (strncmp(utline
, "ftp", 3) == 0 && isdigit(utline
[3]))
400 if (strncmp(utline
, "uucp", 4) == 0 && isdigit(utline
[4]))
404 * Is this something we want to show?
408 for (walk
= ctl
->show
; *walk
; walk
++) {
409 if (strncmp(p
->ut_user
, *walk
, UT_NAMESIZE
) == 0 ||
410 strcmp(utline
, *walk
) == 0 ||
411 (strncmp(utline
, "tty", 3) == 0 &&
412 strcmp(utline
+ 3, *walk
) == 0)) break;
414 if (*walk
== NULL
) return 0;
422 if (ctl
->present
&& (ctl
->present
< tmp
|| (0 < t
&& t
< ctl
->present
)))
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"));
429 secs
= t
- p
->UL_UT_TIME
;
430 mins
= (secs
/ 60) % 60;
431 hours
= (secs
/ 3600) % 24;
436 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT_CTIME
) {
437 sprintf(logouttime
, " still running");
440 sprintf(logouttime
, " still");
441 sprintf(length
, "running");
444 sprintf(length
, "(%d+%02d:%02d)", days
, hours
, mins
);
446 sprintf(length
, " (%02d:%02d)", hours
, mins
);
450 sprintf(logouttime
, "- crash");
453 sprintf(logouttime
, "- down ");
456 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT_CTIME
) {
457 sprintf(logouttime
, " still logged in");
460 sprintf(logouttime
, " still");
461 sprintf(length
, "logged in");
465 if (ctl
->time_fmt
> LAST_TIMEFTM_SHORT_CTIME
) {
466 sprintf(logouttime
, " gone - no logout");
468 } else if (ctl
->time_fmt
== LAST_TIMEFTM_SHORT_CTIME
) {
469 sprintf(logouttime
, " gone");
470 sprintf(length
, "- no logout");
473 sprintf(length
, "no logout");
489 * Look up host with DNS if needed.
492 if (ctl
->usedns
|| ctl
->useip
)
493 r
= dns_lookup(domain
, sizeof(domain
), ctl
->useip
, p
->ut_addr_v6
);
496 if (len
>= (int)sizeof(domain
)) len
= sizeof(domain
) - 1;
498 strncat(domain
, p
->ut_host
, len
);
501 fmt
= &timefmts
[ctl
->time_fmt
];
505 len
= snprintf(final
, sizeof(final
),
506 "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n",
507 ctl
->name_len
, p
->ut_user
, utline
,
508 ctl
->domain_len
, domain
,
509 fmt
->in
, fmt
->in
, logintime
, fmt
->out
, fmt
->out
,
512 len
= snprintf(final
, sizeof(final
),
513 "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n",
514 ctl
->name_len
, p
->ut_user
, utline
,
515 fmt
->in
, fmt
->in
, logintime
, fmt
->out
, fmt
->out
,
516 logouttime
, length
, domain
);
519 len
= snprintf(final
, sizeof(final
),
520 "%-8.*s %-12.12s %-*.*s %-*.*s %s\n",
521 ctl
->name_len
, p
->ut_user
, utline
,
522 fmt
->in
, fmt
->in
, logintime
, fmt
->out
, fmt
->out
,
525 #if defined(__GLIBC__)
526 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
527 final
[sizeof(final
)-1] = '\0';
531 trim_trailing_spaces(final
);
533 * Print out "final" string safely.
535 for (s
= final
; *s
; s
++)
536 fputc_careful(*s
, stdout
, '*');
538 if (len
< 0 || (size_t)len
>= sizeof(final
))
542 if (ctl
->maxrecs
&& ctl
->maxrecs
<= recsdone
)
549 static void __attribute__((__noreturn__
)) usage(FILE *out
)
551 fputs(USAGE_HEADER
, out
);
553 " %s [options] [<username>...] [<tty>...]\n"), program_invocation_short_name
);
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
);
560 _(" -f, --file <file> use a specific file instead of %s\n"), _PATH_WTMP
);
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
);
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
);
567 fputs(_(" -p, --present <time> display who were present at the specified time\n"), out
);
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
);
570 fputs(_(" --time-format <format> show timestamps in the specified <format>:\n"
571 " notime|short|full|iso\n"), out
);
573 fputs(USAGE_SEPARATOR
, out
);
574 fputs(USAGE_HELP
, out
);
575 fputs(USAGE_VERSION
, out
);
576 fprintf(out
, USAGE_MAN_TAIL("last(1)"));
578 exit(out
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
581 static int is_phantom(const struct last_control
*ctl
, struct utmp
*ut
)
586 unsigned int loginuid
;
589 if (ut
->UL_UT_TIME
< ctl
->boot_time
.tv_sec
)
591 pw
= getpwnam(ut
->ut_name
);
594 sprintf(path
, "/proc/%u/loginuid", ut
->ut_pid
);
595 if (access(path
, R_OK
) != 0 || !(f
= fopen(path
, "r")))
598 if (fscanf(f
, "%u", &loginuid
) != 1)
601 if (!ret
&& pw
->pw_uid
!= loginuid
)
606 static void process_wtmp_file(const struct last_control
*ctl
)
608 FILE *fp
; /* Filepointer of wtmp file */
610 struct utmp ut
; /* Current utmp entry */
611 struct utmplist
*p
; /* Pointer into utmplist */
612 struct utmplist
*next
; /* Pointer into utmplist */
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 */
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 */
634 * Install signal handlers
636 signal(SIGINT
, int_handler
);
637 signal(SIGQUIT
, quit_handler
);
642 if ((fp
= fopen(ctl
->altv
[ctl
->alti
], "r")) == NULL
)
643 err(EXIT_FAILURE
, _("cannot open %s"), ctl
->altv
[ctl
->alti
]);
646 * Optimize the buffer size.
648 setvbuf(fp
, NULL
, _IOFBF
, UCHUNKSIZE
);
651 * Read first structure to capture the time field
653 if (uread(ctl
, fp
, &ut
, NULL
) == 1)
654 begintime
= ut
.UL_UT_TIME
;
656 if (fstat(fileno(fp
), &st
) != 0)
657 err(EXIT_FAILURE
, _("stat failed %s"), ctl
->altv
[ctl
->alti
]);
658 begintime
= st
.st_ctime
;
663 * Go to end of file minus one structure
664 * and/or initialize utmp reading code.
666 uread(ctl
, fp
, NULL
, NULL
);
669 * Read struct after struct backwards from the file.
673 if (uread(ctl
, fp
, &ut
, &quit
) != 1)
676 if (ctl
->since
&& ut
.UL_UT_TIME
< ctl
->since
)
679 if (ctl
->until
&& ctl
->until
< ut
.UL_UT_TIME
)
682 lastdate
= ut
.UL_UT_TIME
;
685 quit
= list(ctl
, &ut
, ut
.UL_UT_TIME
, R_NORMAL
);
690 * Set ut_type to the correct type.
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
;
702 * For stupid old applications that don't fill in
706 if (ut
.ut_type
!= DEAD_PROCESS
&&
707 ut
.ut_user
[0] && ut
.ut_line
[0] &&
708 strcmp(ut
.ut_user
, "LOGIN") != 0)
709 ut
.ut_type
= USER_PROCESS
;
711 * Even worse, applications that write ghost
712 * entries: ut_type set to USER_PROCESS but
715 if (ut
.ut_user
[0] == 0)
716 ut
.ut_type
= DEAD_PROCESS
;
721 if (strcmp(ut
.ut_user
, "date") == 0) {
722 if (ut
.ut_line
[0] == '|')
723 ut
.ut_type
= OLD_TIME
;
724 if (ut
.ut_line
[0] == '{')
725 ut
.ut_type
= NEW_TIME
;
729 switch (ut
.ut_type
) {
732 strcpy(ut
.ut_line
, "system down");
733 quit
= list(ctl
, &ut
, lastboot
, R_NORMAL
);
735 lastdown
= lastrch
= ut
.UL_UT_TIME
;
742 ut
.ut_type
== NEW_TIME
? "new time" :
744 quit
= list(ctl
, &ut
, lastdown
, R_TIMECHANGE
);
748 strcpy(ut
.ut_line
, "system boot");
749 quit
= list(ctl
, &ut
, lastdown
, R_REBOOT
);
750 lastboot
= ut
.UL_UT_TIME
;
756 sprintf(ut
.ut_line
, "(to lvl %c)", x
);
757 quit
= list(ctl
, &ut
, lastrch
, R_NORMAL
);
759 if (x
== '0' || x
== '6') {
760 lastdown
= ut
.UL_UT_TIME
;
762 ut
.ut_type
= SHUTDOWN_TIME
;
764 lastrch
= ut
.UL_UT_TIME
;
769 * This was a login - show the first matching
770 * logout record and delete all records with
774 for (p
= utmplist
; p
; p
= next
) {
776 if (strncmp(p
->ut
.ut_line
, ut
.ut_line
,
780 quit
= list(ctl
, &ut
, p
->ut
.UL_UT_TIME
, R_NORMAL
);
783 if (p
->next
) p
->next
->prev
= p
->prev
;
785 p
->prev
->next
= p
->next
;
792 * Not found? Then crashed, down, still
793 * logged in, or missing logout record.
798 /* Is process still alive? */
799 if (is_phantom(ctl
, &ut
))
803 quit
= list(ctl
, &ut
, lastboot
, c
);
809 * Just store the data if it is
810 * interesting enough.
812 if (ut
.ut_line
[0] == 0)
814 p
= xmalloc(sizeof(struct utmplist
));
815 memcpy(&p
->ut
, &ut
, sizeof(struct utmp
));
818 if (utmplist
) utmplist
->prev
= p
;
826 /* ignored ut_types */
830 warnx("unrecogized ut_type: %d", ut
.ut_type
);
834 * If we saw a shutdown/reboot record we can remove
835 * the entire current utmplist.
838 lastboot
= ut
.UL_UT_TIME
;
839 whydown
= (ut
.ut_type
== SHUTDOWN_TIME
) ? R_DOWN
: R_CRASH
;
840 for (p
= utmplist
; p
; p
= next
) {
849 printf(_("\n%s begins %s"), basename(ctl
->altv
[ctl
->alti
]), ctime(&begintime
));
851 for (p
= utmplist
; p
; p
= next
) {
857 int main(int argc
, char **argv
)
859 struct last_control ctl
= {
861 .name_len
= LAST_LOGIN_LEN
,
862 .time_fmt
= LAST_TIMEFTM_SHORT_CTIME
,
863 .domain_len
= LAST_DOMAIN_LEN
869 OPT_TIME_FORMAT
= CHAR_MAX
+ 1
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' },
878 { "since", required_argument
, NULL
, 's' },
879 { "until", required_argument
, NULL
, 't' },
880 { "present", required_argument
, NULL
, 'p' },
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' },
886 { "time-format", required_argument
, NULL
, OPT_TIME_FORMAT
},
889 static const ul_excl_t excl
[] = { /* rows and cols in in ASCII order */
890 { 'F', OPT_TIME_FORMAT
}, /* fulltime, time-format */
893 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
895 setlocale(LC_ALL
, "");
896 bindtextdomain(PACKAGE
, LOCALEDIR
);
898 atexit(close_stdout
);
900 while ((c
= getopt_long(argc
, argv
,
901 "hVf:n:RxadFit:p:s:0123456789w", long_opts
, NULL
)) != -1) {
903 err_exclusive_options(c
, long_opts
, excl
, excl_st
);
910 printf(UTIL_LINUX_VERSION
);
919 ctl
.maxrecs
= strtos32_or_err(optarg
, _("failed to parse number"));
923 ctl
.altv
= xmalloc(sizeof(char *) * argc
);
924 ctl
.altv
[ctl
.altc
++] = xstrdup(optarg
);
936 ctl
.time_fmt
= LAST_TIMEFTM_FULL_CTIME
;
939 if (parse_timestamp(optarg
, &p
) < 0)
940 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
941 ctl
.present
= (time_t) (p
/ 1000000);
944 if (parse_timestamp(optarg
, &p
) < 0)
945 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
946 ctl
.since
= (time_t) (p
/ 1000000);
949 if (parse_timestamp(optarg
, &p
) < 0)
950 errx(EXIT_FAILURE
, _("invalid time value \"%s\""), optarg
);
951 ctl
.until
= (time_t) (p
/ 1000000);
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
;
959 case '0': case '1': case '2': case '3': case '4':
960 case '5': case '6': case '7': case '8': case '9':
961 ctl
.maxrecs
= 10 * ctl
.maxrecs
+ c
- '0';
963 case OPT_TIME_FORMAT
:
964 ctl
.time_fmt
= which_time_format(optarg
);
973 ctl
.show
= argv
+ optind
;
976 * Which file do we want to read?
978 ctl
.lastb
= strcmp(program_invocation_short_name
, "lastb") == 0 ? 1 : 0;
980 ctl
.altv
= xmalloc(sizeof(char *));
982 ctl
.altv
[0] = xstrdup(_PATH_BTMP
);
984 ctl
.altv
[0] = xstrdup(_PATH_WTMP
);
988 for (; ctl
.alti
< ctl
.altc
; ctl
.alti
++) {
989 get_boot_time(&ctl
.boot_time
);
990 process_wtmp_file(&ctl
);
991 free(ctl
.altv
[ctl
.alti
]);