last.c revision 285742
11590Srgrimes/*
21590Srgrimes * Copyright (c) 1987, 1993, 1994
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
291590Srgrimes
301590Srgrimes#ifndef lint
3178201Sddstatic const char copyright[] =
321590Srgrimes"@(#) Copyright (c) 1987, 1993, 1994\n\
331590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
341590Srgrimes#endif /* not lint */
351590Srgrimes
361590Srgrimes#ifndef lint
3778201Sddstatic const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
381590Srgrimes#endif /* not lint */
3999112Sobrien#include <sys/cdefs.h>
4099112Sobrien__FBSDID("$FreeBSD: head/usr.bin/last/last.c 285742 2015-07-21 10:52:05Z ed $");
411590Srgrimes
421590Srgrimes#include <sys/param.h>
431590Srgrimes#include <sys/stat.h>
441590Srgrimes
451590Srgrimes#include <err.h>
46118077Stjr#include <errno.h>
471590Srgrimes#include <fcntl.h>
4874588Sache#include <langinfo.h>
4916438Sache#include <locale.h>
501590Srgrimes#include <paths.h>
511590Srgrimes#include <signal.h>
521590Srgrimes#include <stdio.h>
531590Srgrimes#include <stdlib.h>
541590Srgrimes#include <string.h>
551590Srgrimes#include <time.h>
56125856Sdwmalone#include <timeconv.h>
571590Srgrimes#include <unistd.h>
58202197Sed#include <utmpx.h>
5911547Sdg#include <sys/queue.h>
601590Srgrimes
611590Srgrimes#define	NO	0				/* false/no */
621590Srgrimes#define	YES	1				/* true/yes */
6377291Sdd#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
641590Srgrimes
651590Srgrimestypedef struct arg {
661590Srgrimes	char	*name;				/* argument */
67285742Sed#define	REBOOT_TYPE	-1
681590Srgrimes#define	HOST_TYPE	-2
691590Srgrimes#define	TTY_TYPE	-3
701590Srgrimes#define	USER_TYPE	-4
711590Srgrimes	int	type;				/* type of arg */
721590Srgrimes	struct arg	*next;			/* linked list pointer */
731590Srgrimes} ARG;
74227168Sedstatic ARG	*arglist;			/* head of linked list */
751590Srgrimes
76240425Sedstatic SLIST_HEAD(, idtab) idlist;
7711547Sdg
78202197Sedstruct idtab {
7936062Sjb	time_t	logout;				/* log out time */
80202197Sed	char	id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
81240425Sed	SLIST_ENTRY(idtab) list;
8211547Sdg};
831590Srgrimes
8491536Siedowsestatic const	char *crmsg;			/* cause of last reboot */
85202197Sedstatic time_t	currentout;			/* current logout value */
86202197Sedstatic long	maxrec;				/* records to display */
87230458Shrsstatic const	char *file = NULL;		/* utx.log file */
8836434Sdannystatic int	sflag = 0;			/* show delta in seconds */
8936434Sdannystatic int	width = 5;			/* show seconds in delta */
9091541Siedowsestatic int	yflag;				/* show year */
9174588Sachestatic int      d_first;
9291538Siedowsestatic int	snapfound = 0;			/* found snapshot entry? */
9377291Sddstatic time_t	snaptime;			/* if != 0, we will only
9477291Sdd						 * report users logged in
9577291Sdd						 * at this snapshot time
9677291Sdd						 */
971590Srgrimes
98227168Sedstatic void	 addarg(int, char *);
99227168Sedstatic time_t	 dateconv(char *);
100227168Sedstatic void	 doentry(struct utmpx *);
101227168Sedstatic void	 hostconv(char *);
102227168Sedstatic void	 printentry(struct utmpx *, struct idtab *);
103227168Sedstatic char	*ttyconv(char *);
104227168Sedstatic int	 want(struct utmpx *);
105227168Sedstatic void	 usage(void);
106227168Sedstatic void	 wtmp(void);
1071590Srgrimes
108227168Sedstatic void
10936434Sdannyusage(void)
11036434Sdanny{
11136434Sdanny	(void)fprintf(stderr,
112119023Stjr"usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
113119023Stjr"            [-n maxrec] [-t tty] [user ...]\n");
11436434Sdanny	exit(1);
11536434Sdanny}
11636434Sdanny
1171590Srgrimesint
118102944Sdwmalonemain(int argc, char *argv[])
1191590Srgrimes{
1201590Srgrimes	int ch;
1211590Srgrimes	char *p;
1221590Srgrimes
12316438Sache	(void) setlocale(LC_TIME, "");
12474588Sache	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
12516438Sache
1261590Srgrimes	maxrec = -1;
12777291Sdd	snaptime = 0;
128118077Stjr	while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
1291590Srgrimes		switch (ch) {
1301590Srgrimes		case '0': case '1': case '2': case '3': case '4':
1311590Srgrimes		case '5': case '6': case '7': case '8': case '9':
1321590Srgrimes			/*
1331590Srgrimes			 * kludge: last was originally designed to take
1341590Srgrimes			 * a number after a dash.
1351590Srgrimes			 */
1361590Srgrimes			if (maxrec == -1) {
137106215Smux				p = strchr(argv[optind - 1], ch);
138106215Smux				if (p == NULL)
139106215Smux					p = strchr(argv[optind], ch);
140106215Smux				maxrec = atol(p);
1411590Srgrimes				if (!maxrec)
1421590Srgrimes					exit(0);
1431590Srgrimes			}
1441590Srgrimes			break;
14577291Sdd		case 'd':
14677291Sdd			snaptime = dateconv(optarg);
14777291Sdd			break;
1481590Srgrimes		case 'f':
1491590Srgrimes			file = optarg;
1501590Srgrimes			break;
1511590Srgrimes		case 'h':
1521590Srgrimes			hostconv(optarg);
1531590Srgrimes			addarg(HOST_TYPE, optarg);
1541590Srgrimes			break;
155118077Stjr		case 'n':
156118077Stjr			errno = 0;
157118077Stjr			maxrec = strtol(optarg, &p, 10);
158118077Stjr			if (p == optarg || *p != '\0' || errno != 0 ||
159118077Stjr			    maxrec <= 0)
160118077Stjr				errx(1, "%s: bad line count", optarg);
161118077Stjr			break;
16236434Sdanny		case 's':
16336434Sdanny			sflag++;	/* Show delta as seconds */
16436434Sdanny			break;
1651590Srgrimes		case 't':
1661590Srgrimes			addarg(TTY_TYPE, ttyconv(optarg));
1671590Srgrimes			break;
16836434Sdanny		case 'w':
16936434Sdanny			width = 8;
17036434Sdanny			break;
17191541Siedowse		case 'y':
17291541Siedowse			yflag++;
17391541Siedowse			break;
1741590Srgrimes		case '?':
1751590Srgrimes		default:
17636434Sdanny			usage();
1771590Srgrimes		}
1781590Srgrimes
17936434Sdanny	if (sflag && width == 8) usage();
18036434Sdanny
1811590Srgrimes	if (argc) {
1821590Srgrimes		setlinebuf(stdout);
1831590Srgrimes		for (argv += optind; *argv; ++argv) {
184285742Sed			if (strcmp(*argv, "reboot") == 0)
185285742Sed				addarg(REBOOT_TYPE, *argv);
1861590Srgrimes#define	COMPATIBILITY
1871590Srgrimes#ifdef	COMPATIBILITY
1881590Srgrimes			/* code to allow "last p5" to work */
1891590Srgrimes			addarg(TTY_TYPE, ttyconv(*argv));
1901590Srgrimes#endif
1911590Srgrimes			addarg(USER_TYPE, *argv);
1921590Srgrimes		}
1931590Srgrimes	}
1941590Srgrimes	wtmp();
1951590Srgrimes	exit(0);
1961590Srgrimes}
1971590Srgrimes
1981590Srgrimes/*
1991590Srgrimes * wtmp --
200230458Shrs *	read through the utx.log file
2011590Srgrimes */
202227168Sedstatic void
203102944Sdwmalonewtmp(void)
2041590Srgrimes{
205202643Sed	struct utmpx *buf = NULL;
206202197Sed	struct utmpx *ut;
207202643Sed	static unsigned int amount = 0;
208202197Sed	time_t t;
20916438Sache	char ct[80];
21016438Sache	struct tm *tm;
2111590Srgrimes
212240425Sed	SLIST_INIT(&idlist);
213202197Sed	(void)time(&t);
21411547Sdg
215202197Sed	/* Load the last entries from the file. */
216202197Sed	if (setutxdb(UTXDB_LOG, file) != 0)
2171590Srgrimes		err(1, "%s", file);
218202197Sed	while ((ut = getutxent()) != NULL) {
219202643Sed		if (amount % 128 == 0) {
220202643Sed			buf = realloc(buf, (amount + 128) * sizeof *ut);
221202643Sed			if (buf == NULL)
222202643Sed				err(1, "realloc");
223202643Sed		}
224202643Sed		memcpy(&buf[amount++], ut, sizeof *ut);
225202197Sed		if (t > ut->ut_tv.tv_sec)
226202197Sed			t = ut->ut_tv.tv_sec;
227202197Sed	}
228202197Sed	endutxent();
2291590Srgrimes
230202197Sed	/* Display them in reverse order. */
231202197Sed	while (amount > 0)
232202643Sed		doentry(&buf[--amount]);
2331590Srgrimes
23485648Sdillon	tm = localtime(&t);
235230458Shrs	(void) strftime(ct, sizeof(ct), "%+", tm);
236230458Shrs	printf("\n%s begins %s\n", ((file == NULL) ? "utx.log" : file), ct);
2371590Srgrimes}
2381590Srgrimes
2391590Srgrimes/*
24091536Siedowse * doentry --
241230458Shrs *	process a single utx.log entry
24291536Siedowse */
243227168Sedstatic void
244202197Seddoentry(struct utmpx *bp)
24591536Siedowse{
246240425Sed	struct idtab *tt;
24791536Siedowse
248202197Sed	/* the machine stopped */
249202197Sed	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
25091536Siedowse		/* everybody just logged out */
251240425Sed		while ((tt = SLIST_FIRST(&idlist)) != NULL) {
252240425Sed			SLIST_REMOVE_HEAD(&idlist, list);
253240425Sed			free(tt);
25491536Siedowse		}
255202197Sed		currentout = -bp->ut_tv.tv_sec;
256202197Sed		crmsg = bp->ut_type != SHUTDOWN_TIME ?
25791536Siedowse		    "crash" : "shutdown";
25891536Siedowse		/*
25991536Siedowse		 * if we're in snapshot mode, we want to exit if this
26091536Siedowse		 * shutdown/reboot appears while we we are tracking the
26191536Siedowse		 * active range
26291536Siedowse		 */
26391536Siedowse		if (snaptime && snapfound)
26491536Siedowse			exit(0);
26591536Siedowse		/*
26691536Siedowse		 * don't print shutdown/reboot entries unless flagged for
26791536Siedowse		 */
26891538Siedowse		if (!snaptime && want(bp))
26991536Siedowse			printentry(bp, NULL);
27091536Siedowse		return;
27191536Siedowse	}
272202197Sed	/* date got set */
273202197Sed	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
27491538Siedowse		if (want(bp) && !snaptime)
27591536Siedowse			printentry(bp, NULL);
27691536Siedowse		return;
27791536Siedowse	}
278202197Sed
279202197Sed	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
280202197Sed		return;
281202197Sed
282202197Sed	/* find associated identifier */
283240425Sed	SLIST_FOREACH(tt, &idlist, list)
284202197Sed	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
28591536Siedowse		    break;
28691536Siedowse
28791536Siedowse	if (tt == NULL) {
28891536Siedowse		/* add new one */
289202197Sed		tt = malloc(sizeof(struct idtab));
29091536Siedowse		if (tt == NULL)
29196785Sjmallett			errx(1, "malloc failure");
29291536Siedowse		tt->logout = currentout;
293202197Sed		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
294240425Sed		SLIST_INSERT_HEAD(&idlist, tt, list);
29591536Siedowse	}
29691536Siedowse
29791536Siedowse	/*
29891536Siedowse	 * print record if not in snapshot mode and wanted
29991536Siedowse	 * or in snapshot mode and in snapshot range
30091536Siedowse	 */
301202197Sed	if (bp->ut_type == USER_PROCESS && (want(bp) ||
302202197Sed	    (bp->ut_tv.tv_sec < snaptime &&
30391536Siedowse	    (tt->logout > snaptime || tt->logout < 1)))) {
30491536Siedowse		snapfound = 1;
30591536Siedowse		printentry(bp, tt);
30691536Siedowse	}
307202197Sed	tt->logout = bp->ut_tv.tv_sec;
30891536Siedowse}
30991536Siedowse
31091536Siedowse/*
31191536Siedowse * printentry --
31291536Siedowse *	output an entry
31391536Siedowse *
31491536Siedowse * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
31591536Siedowse * logout type (crash/shutdown) as appropriate.
31691536Siedowse */
317227168Sedstatic void
318202197Sedprintentry(struct utmpx *bp, struct idtab *tt)
31991536Siedowse{
32091536Siedowse	char ct[80];
32191536Siedowse	struct tm *tm;
32291536Siedowse	time_t	delta;				/* time difference */
32391536Siedowse	time_t	t;
32491536Siedowse
32591538Siedowse	if (maxrec != -1 && !maxrec--)
32691538Siedowse		exit(0);
327202197Sed	t = bp->ut_tv.tv_sec;
32891536Siedowse	tm = localtime(&t);
32991541Siedowse	(void) strftime(ct, sizeof(ct), d_first ?
33091541Siedowse	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
33191541Siedowse	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
332202197Sed	switch (bp->ut_type) {
333202197Sed	case BOOT_TIME:
334202197Sed		printf("%-42s", "boot time");
335202197Sed		break;
336202197Sed	case SHUTDOWN_TIME:
337202197Sed		printf("%-42s", "shutdown time");
338202197Sed		break;
339202197Sed	case OLD_TIME:
340202197Sed		printf("%-42s", "old time");
341202197Sed		break;
342202197Sed	case NEW_TIME:
343202197Sed		printf("%-42s", "new time");
344202197Sed		break;
345202197Sed	case USER_PROCESS:
346202197Sed		printf("%-10s %-8s %-22.22s",
347202197Sed		    bp->ut_user, bp->ut_line, bp->ut_host);
348202197Sed		break;
349202197Sed	}
350202197Sed	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
35191536Siedowse	if (tt == NULL)
35291536Siedowse		return;
35391536Siedowse	if (!tt->logout) {
35491536Siedowse		puts("  still logged in");
35591536Siedowse		return;
35691536Siedowse	}
35791536Siedowse	if (tt->logout < 0) {
35891536Siedowse		tt->logout = -tt->logout;
35991536Siedowse		printf("- %s", crmsg);
36091536Siedowse	} else {
36191536Siedowse		tm = localtime(&tt->logout);
36291536Siedowse		(void) strftime(ct, sizeof(ct), "%R", tm);
36391536Siedowse		printf("- %s", ct);
36491536Siedowse	}
365202197Sed	delta = tt->logout - bp->ut_tv.tv_sec;
36691536Siedowse	if (sflag) {
36791536Siedowse		printf("  (%8ld)\n", (long)delta);
36891536Siedowse	} else {
36991536Siedowse		tm = gmtime(&delta);
37091536Siedowse		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
37191536Siedowse		if (delta < 86400)
37291536Siedowse			printf("  (%s)\n", ct);
37391536Siedowse		else
37491536Siedowse			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
37591536Siedowse	}
37691536Siedowse}
37791536Siedowse
37891536Siedowse/*
3791590Srgrimes * want --
3801590Srgrimes *	see if want this entry
3811590Srgrimes */
382227168Sedstatic int
383202197Sedwant(struct utmpx *bp)
3841590Srgrimes{
3851590Srgrimes	ARG *step;
3861590Srgrimes
38777291Sdd	if (snaptime)
38877291Sdd		return (NO);
38977291Sdd
3901590Srgrimes	if (!arglist)
3911590Srgrimes		return (YES);
3921590Srgrimes
3931590Srgrimes	for (step = arglist; step; step = step->next)
3941590Srgrimes		switch(step->type) {
395285742Sed		case REBOOT_TYPE:
396285742Sed			if (bp->ut_type == BOOT_TIME ||
397285742Sed			    bp->ut_type == SHUTDOWN_TIME)
398285742Sed				return (YES);
399285742Sed			break;
4001590Srgrimes		case HOST_TYPE:
401202197Sed			if (!strcasecmp(step->name, bp->ut_host))
4021590Srgrimes				return (YES);
4031590Srgrimes			break;
4041590Srgrimes		case TTY_TYPE:
405202197Sed			if (!strcmp(step->name, bp->ut_line))
4061590Srgrimes				return (YES);
4071590Srgrimes			break;
4081590Srgrimes		case USER_TYPE:
409202197Sed			if (!strcmp(step->name, bp->ut_user))
4101590Srgrimes				return (YES);
4111590Srgrimes			break;
41291536Siedowse		}
4131590Srgrimes	return (NO);
4141590Srgrimes}
4151590Srgrimes
4161590Srgrimes/*
4171590Srgrimes * addarg --
4181590Srgrimes *	add an entry to a linked list of arguments
4191590Srgrimes */
420227168Sedstatic void
421102944Sdwmaloneaddarg(int type, char *arg)
4221590Srgrimes{
4231590Srgrimes	ARG *cur;
4241590Srgrimes
42596785Sjmallett	if ((cur = malloc(sizeof(ARG))) == NULL)
42696785Sjmallett		errx(1, "malloc failure");
4271590Srgrimes	cur->next = arglist;
4281590Srgrimes	cur->type = type;
4291590Srgrimes	cur->name = arg;
4301590Srgrimes	arglist = cur;
4311590Srgrimes}
4321590Srgrimes
4331590Srgrimes/*
4341590Srgrimes * hostconv --
4351590Srgrimes *	convert the hostname to search pattern; if the supplied host name
4361590Srgrimes *	has a domain attached that is the same as the current domain, rip
4371590Srgrimes *	off the domain suffix since that's what login(1) does.
4381590Srgrimes */
439227168Sedstatic void
440102944Sdwmalonehostconv(char *arg)
4411590Srgrimes{
4421590Srgrimes	static int first = 1;
4431590Srgrimes	static char *hostdot, name[MAXHOSTNAMELEN];
4441590Srgrimes	char *argdot;
4451590Srgrimes
4461590Srgrimes	if (!(argdot = strchr(arg, '.')))
4471590Srgrimes		return;
4481590Srgrimes	if (first) {
4491590Srgrimes		first = 0;
4501590Srgrimes		if (gethostname(name, sizeof(name)))
4511590Srgrimes			err(1, "gethostname");
4521590Srgrimes		hostdot = strchr(name, '.');
4531590Srgrimes	}
4541590Srgrimes	if (hostdot && !strcasecmp(hostdot, argdot))
4551590Srgrimes		*argdot = '\0';
4561590Srgrimes}
4571590Srgrimes
4581590Srgrimes/*
4591590Srgrimes * ttyconv --
4601590Srgrimes *	convert tty to correct name.
4611590Srgrimes */
462227168Sedstatic char *
463102944Sdwmalonettyconv(char *arg)
4641590Srgrimes{
4651590Srgrimes	char *mval;
4661590Srgrimes
4671590Srgrimes	/*
4681590Srgrimes	 * kludge -- we assume that all tty's end with
4691590Srgrimes	 * a two character suffix.
4701590Srgrimes	 */
4711590Srgrimes	if (strlen(arg) == 2) {
4721590Srgrimes		/* either 6 for "ttyxx" or 8 for "console" */
47396785Sjmallett		if ((mval = malloc(8)) == NULL)
47496785Sjmallett			errx(1, "malloc failure");
4751590Srgrimes		if (!strcmp(arg, "co"))
4761590Srgrimes			(void)strcpy(mval, "console");
4771590Srgrimes		else {
4781590Srgrimes			(void)strcpy(mval, "tty");
4791590Srgrimes			(void)strcpy(mval + 3, arg);
4801590Srgrimes		}
4811590Srgrimes		return (mval);
4821590Srgrimes	}
4831590Srgrimes	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
4841590Srgrimes		return (arg + 5);
4851590Srgrimes	return (arg);
4861590Srgrimes}
4871590Srgrimes
4881590Srgrimes/*
48977291Sdd * dateconv --
49077291Sdd * 	Convert the snapshot time in command line given in the format
49177291Sdd * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
49277291Sdd * 	Derived from atime_arg1() in usr.bin/touch/touch.c
49377291Sdd */
494227168Sedstatic time_t
495102944Sdwmalonedateconv(char *arg)
49677291Sdd{
49777291Sdd        time_t timet;
49877291Sdd        struct tm *t;
49977291Sdd        int yearset;
50077291Sdd        char *p;
50177291Sdd
50277291Sdd        /* Start with the current time. */
50377291Sdd        if (time(&timet) < 0)
50477291Sdd                err(1, "time");
50577291Sdd        if ((t = localtime(&timet)) == NULL)
50677291Sdd                err(1, "localtime");
50777291Sdd
50877291Sdd        /* [[CC]YY]MMDDhhmm[.SS] */
50977291Sdd        if ((p = strchr(arg, '.')) == NULL)
51077291Sdd                t->tm_sec = 0; 		/* Seconds defaults to 0. */
51177291Sdd        else {
51277291Sdd                if (strlen(p + 1) != 2)
51377291Sdd                        goto terr;
51477291Sdd                *p++ = '\0';
51577291Sdd                t->tm_sec = ATOI2(p);
51677291Sdd        }
51777291Sdd
51877291Sdd        yearset = 0;
51977291Sdd        switch (strlen(arg)) {
52077291Sdd        case 12:                	/* CCYYMMDDhhmm */
52177291Sdd                t->tm_year = ATOI2(arg);
52277291Sdd                t->tm_year *= 100;
52377291Sdd                yearset = 1;
524133332Sdwmalone                /* FALLTHROUGH */
52577291Sdd        case 10:                	/* YYMMDDhhmm */
52677291Sdd                if (yearset) {
52777291Sdd                        yearset = ATOI2(arg);
52877291Sdd                        t->tm_year += yearset;
52977291Sdd                } else {
53077291Sdd                        yearset = ATOI2(arg);
53177291Sdd                        if (yearset < 69)
53277291Sdd                                t->tm_year = yearset + 2000;
53377291Sdd                        else
53477291Sdd                                t->tm_year = yearset + 1900;
53577291Sdd                }
53677291Sdd                t->tm_year -= 1900;     /* Convert to UNIX time. */
53777291Sdd                /* FALLTHROUGH */
53877291Sdd        case 8:				/* MMDDhhmm */
53977291Sdd                t->tm_mon = ATOI2(arg);
54077291Sdd                --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
54177291Sdd                t->tm_mday = ATOI2(arg);
54277291Sdd                t->tm_hour = ATOI2(arg);
54377291Sdd                t->tm_min = ATOI2(arg);
54477291Sdd                break;
54577291Sdd        case 4:				/* hhmm */
54677291Sdd                t->tm_hour = ATOI2(arg);
54777291Sdd                t->tm_min = ATOI2(arg);
54877291Sdd                break;
54977291Sdd        default:
55077291Sdd                goto terr;
55177291Sdd        }
55277291Sdd        t->tm_isdst = -1;       	/* Figure out DST. */
55377291Sdd        timet = mktime(t);
55477291Sdd        if (timet == -1)
55577291Sddterr:           errx(1,
55677291Sdd        "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
55777291Sdd        return timet;
55877291Sdd}
559