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