last.c revision 202643
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 * 3. All advertising materials mentioning features or use of this software
141590Srgrimes *    must display the following acknowledgement:
151590Srgrimes *	This product includes software developed by the University of
161590Srgrimes *	California, Berkeley and its contributors.
171590Srgrimes * 4. Neither the name of the University nor the names of its contributors
181590Srgrimes *    may be used to endorse or promote products derived from this software
191590Srgrimes *    without specific prior written permission.
201590Srgrimes *
211590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311590Srgrimes * SUCH DAMAGE.
321590Srgrimes */
331590Srgrimes
341590Srgrimes#ifndef lint
3578201Sddstatic const char copyright[] =
361590Srgrimes"@(#) Copyright (c) 1987, 1993, 1994\n\
371590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
381590Srgrimes#endif /* not lint */
391590Srgrimes
401590Srgrimes#ifndef lint
4178201Sddstatic const char sccsid[] = "@(#)last.c	8.2 (Berkeley) 4/2/94";
421590Srgrimes#endif /* not lint */
4399112Sobrien#include <sys/cdefs.h>
4499112Sobrien__FBSDID("$FreeBSD: head/usr.bin/last/last.c 202643 2010-01-19 19:53:05Z ed $");
451590Srgrimes
461590Srgrimes#include <sys/param.h>
471590Srgrimes#include <sys/stat.h>
481590Srgrimes
491590Srgrimes#include <err.h>
50118077Stjr#include <errno.h>
511590Srgrimes#include <fcntl.h>
5274588Sache#include <langinfo.h>
5316438Sache#include <locale.h>
541590Srgrimes#include <paths.h>
551590Srgrimes#include <signal.h>
561590Srgrimes#include <stdio.h>
571590Srgrimes#include <stdlib.h>
581590Srgrimes#include <string.h>
591590Srgrimes#include <time.h>
60125856Sdwmalone#include <timeconv.h>
611590Srgrimes#include <unistd.h>
62202197Sed#include <utmpx.h>
6311547Sdg#include <sys/queue.h>
641590Srgrimes
651590Srgrimes#define	NO	0				/* false/no */
661590Srgrimes#define	YES	1				/* true/yes */
6777291Sdd#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
681590Srgrimes
691590Srgrimestypedef struct arg {
701590Srgrimes	char	*name;				/* argument */
711590Srgrimes#define	HOST_TYPE	-2
721590Srgrimes#define	TTY_TYPE	-3
731590Srgrimes#define	USER_TYPE	-4
741590Srgrimes	int	type;				/* type of arg */
751590Srgrimes	struct arg	*next;			/* linked list pointer */
761590Srgrimes} ARG;
771590SrgrimesARG	*arglist;				/* head of linked list */
781590Srgrimes
79202197SedLIST_HEAD(idlisthead, idtab) idlist;
8011547Sdg
81202197Sedstruct idtab {
8236062Sjb	time_t	logout;				/* log out time */
83202197Sed	char	id[sizeof ((struct utmpx *)0)->ut_id]; /* identifier */
84202197Sed	LIST_ENTRY(idtab) list;
8511547Sdg};
861590Srgrimes
8791536Siedowsestatic const	char *crmsg;			/* cause of last reboot */
88202197Sedstatic time_t	currentout;			/* current logout value */
89202197Sedstatic long	maxrec;				/* records to display */
90202197Sedstatic const	char *file = NULL;		/* wtmp file */
9136434Sdannystatic int	sflag = 0;			/* show delta in seconds */
9236434Sdannystatic int	width = 5;			/* show seconds in delta */
9391541Siedowsestatic int	yflag;				/* show year */
9474588Sachestatic int      d_first;
9591538Siedowsestatic int	snapfound = 0;			/* found snapshot entry? */
9677291Sddstatic time_t	snaptime;			/* if != 0, we will only
9777291Sdd						 * report users logged in
9877291Sdd						 * at this snapshot time
9977291Sdd						 */
1001590Srgrimes
10192920Simpvoid	 addarg(int, char *);
10292920Simptime_t	 dateconv(char *);
103202197Sedvoid	 doentry(struct utmpx *);
10492920Simpvoid	 hostconv(char *);
105202197Sedvoid	 printentry(struct utmpx *, struct idtab *);
10692920Simpchar	*ttyconv(char *);
107202197Sedint	 want(struct utmpx *);
10892920Simpvoid	 usage(void);
10992920Simpvoid	 wtmp(void);
1101590Srgrimes
11136434Sdannyvoid
11236434Sdannyusage(void)
11336434Sdanny{
11436434Sdanny	(void)fprintf(stderr,
115119023Stjr"usage: last [-swy] [-d [[CC]YY][MMDD]hhmm[.SS]] [-f file] [-h host]\n"
116119023Stjr"            [-n maxrec] [-t tty] [user ...]\n");
11736434Sdanny	exit(1);
11836434Sdanny}
11936434Sdanny
1201590Srgrimesint
121102944Sdwmalonemain(int argc, char *argv[])
1221590Srgrimes{
1231590Srgrimes	int ch;
1241590Srgrimes	char *p;
1251590Srgrimes
12616438Sache	(void) setlocale(LC_TIME, "");
12774588Sache	d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
12816438Sache
1291590Srgrimes	maxrec = -1;
13077291Sdd	snaptime = 0;
131118077Stjr	while ((ch = getopt(argc, argv, "0123456789d:f:h:n:st:wy")) != -1)
1321590Srgrimes		switch (ch) {
1331590Srgrimes		case '0': case '1': case '2': case '3': case '4':
1341590Srgrimes		case '5': case '6': case '7': case '8': case '9':
1351590Srgrimes			/*
1361590Srgrimes			 * kludge: last was originally designed to take
1371590Srgrimes			 * a number after a dash.
1381590Srgrimes			 */
1391590Srgrimes			if (maxrec == -1) {
140106215Smux				p = strchr(argv[optind - 1], ch);
141106215Smux				if (p == NULL)
142106215Smux					p = strchr(argv[optind], ch);
143106215Smux				maxrec = atol(p);
1441590Srgrimes				if (!maxrec)
1451590Srgrimes					exit(0);
1461590Srgrimes			}
1471590Srgrimes			break;
14877291Sdd		case 'd':
14977291Sdd			snaptime = dateconv(optarg);
15077291Sdd			break;
1511590Srgrimes		case 'f':
1521590Srgrimes			file = optarg;
1531590Srgrimes			break;
1541590Srgrimes		case 'h':
1551590Srgrimes			hostconv(optarg);
1561590Srgrimes			addarg(HOST_TYPE, optarg);
1571590Srgrimes			break;
158118077Stjr		case 'n':
159118077Stjr			errno = 0;
160118077Stjr			maxrec = strtol(optarg, &p, 10);
161118077Stjr			if (p == optarg || *p != '\0' || errno != 0 ||
162118077Stjr			    maxrec <= 0)
163118077Stjr				errx(1, "%s: bad line count", optarg);
164118077Stjr			break;
16536434Sdanny		case 's':
16636434Sdanny			sflag++;	/* Show delta as seconds */
16736434Sdanny			break;
1681590Srgrimes		case 't':
1691590Srgrimes			addarg(TTY_TYPE, ttyconv(optarg));
1701590Srgrimes			break;
17136434Sdanny		case 'w':
17236434Sdanny			width = 8;
17336434Sdanny			break;
17491541Siedowse		case 'y':
17591541Siedowse			yflag++;
17691541Siedowse			break;
1771590Srgrimes		case '?':
1781590Srgrimes		default:
17936434Sdanny			usage();
1801590Srgrimes		}
1811590Srgrimes
18236434Sdanny	if (sflag && width == 8) usage();
18336434Sdanny
1841590Srgrimes	if (argc) {
1851590Srgrimes		setlinebuf(stdout);
1861590Srgrimes		for (argv += optind; *argv; ++argv) {
1871590Srgrimes#define	COMPATIBILITY
1881590Srgrimes#ifdef	COMPATIBILITY
1891590Srgrimes			/* code to allow "last p5" to work */
1901590Srgrimes			addarg(TTY_TYPE, ttyconv(*argv));
1911590Srgrimes#endif
1921590Srgrimes			addarg(USER_TYPE, *argv);
1931590Srgrimes		}
1941590Srgrimes	}
1951590Srgrimes	wtmp();
1961590Srgrimes	exit(0);
1971590Srgrimes}
1981590Srgrimes
1991590Srgrimes/*
2001590Srgrimes * wtmp --
2011590Srgrimes *	read through the wtmp file
2021590Srgrimes */
2031590Srgrimesvoid
204102944Sdwmalonewtmp(void)
2051590Srgrimes{
206202643Sed	struct utmpx *buf = NULL;
207202197Sed	struct utmpx *ut;
208202643Sed	static unsigned int amount = 0;
209202197Sed	time_t t;
21016438Sache	char ct[80];
21116438Sache	struct tm *tm;
2121590Srgrimes
213202197Sed	LIST_INIT(&idlist);
214202197Sed	(void)time(&t);
21511547Sdg
216202197Sed	/* Load the last entries from the file. */
217202197Sed	if (setutxdb(UTXDB_LOG, file) != 0)
2181590Srgrimes		err(1, "%s", file);
219202197Sed	while ((ut = getutxent()) != NULL) {
220202643Sed		if (amount % 128 == 0) {
221202643Sed			buf = realloc(buf, (amount + 128) * sizeof *ut);
222202643Sed			if (buf == NULL)
223202643Sed				err(1, "realloc");
224202643Sed		}
225202643Sed		memcpy(&buf[amount++], ut, sizeof *ut);
226202197Sed		if (t > ut->ut_tv.tv_sec)
227202197Sed			t = ut->ut_tv.tv_sec;
228202197Sed	}
229202197Sed	endutxent();
2301590Srgrimes
231202197Sed	/* Display them in reverse order. */
232202197Sed	while (amount > 0)
233202643Sed		doentry(&buf[--amount]);
2341590Srgrimes
23585648Sdillon	tm = localtime(&t);
23678201Sdd	(void) strftime(ct, sizeof(ct), "\nwtmp begins %+\n", tm);
23762871Skris	printf("%s", ct);
2381590Srgrimes}
2391590Srgrimes
2401590Srgrimes/*
24191536Siedowse * doentry --
24291536Siedowse *	process a single wtmp entry
24391536Siedowse */
24491536Siedowsevoid
245202197Seddoentry(struct utmpx *bp)
24691536Siedowse{
247202197Sed	struct idtab	*tt, *ttx;		/* idlist entry */
24891536Siedowse
249202197Sed	/* the machine stopped */
250202197Sed	if (bp->ut_type == BOOT_TIME || bp->ut_type == SHUTDOWN_TIME) {
25191536Siedowse		/* everybody just logged out */
252202197Sed		for (tt = LIST_FIRST(&idlist); tt;) {
25391536Siedowse			LIST_REMOVE(tt, list);
25491536Siedowse			ttx = tt;
25591536Siedowse			tt = LIST_NEXT(tt, list);
25691536Siedowse			free(ttx);
25791536Siedowse		}
258202197Sed		currentout = -bp->ut_tv.tv_sec;
259202197Sed		crmsg = bp->ut_type != SHUTDOWN_TIME ?
26091536Siedowse		    "crash" : "shutdown";
26191536Siedowse		/*
26291536Siedowse		 * if we're in snapshot mode, we want to exit if this
26391536Siedowse		 * shutdown/reboot appears while we we are tracking the
26491536Siedowse		 * active range
26591536Siedowse		 */
26691536Siedowse		if (snaptime && snapfound)
26791536Siedowse			exit(0);
26891536Siedowse		/*
26991536Siedowse		 * don't print shutdown/reboot entries unless flagged for
27091536Siedowse		 */
27191538Siedowse		if (!snaptime && want(bp))
27291536Siedowse			printentry(bp, NULL);
27391536Siedowse		return;
27491536Siedowse	}
275202197Sed	/* date got set */
276202197Sed	if (bp->ut_type == OLD_TIME || bp->ut_type == NEW_TIME) {
27791538Siedowse		if (want(bp) && !snaptime)
27891536Siedowse			printentry(bp, NULL);
27991536Siedowse		return;
28091536Siedowse	}
281202197Sed
282202197Sed	if (bp->ut_type != USER_PROCESS && bp->ut_type != DEAD_PROCESS)
283202197Sed		return;
284202197Sed
285202197Sed	/* find associated identifier */
286202197Sed	LIST_FOREACH(tt, &idlist, list)
287202197Sed	    if (!memcmp(tt->id, bp->ut_id, sizeof bp->ut_id))
28891536Siedowse		    break;
28991536Siedowse
29091536Siedowse	if (tt == NULL) {
29191536Siedowse		/* add new one */
292202197Sed		tt = malloc(sizeof(struct idtab));
29391536Siedowse		if (tt == NULL)
29496785Sjmallett			errx(1, "malloc failure");
29591536Siedowse		tt->logout = currentout;
296202197Sed		memcpy(tt->id, bp->ut_id, sizeof bp->ut_id);
297202197Sed		LIST_INSERT_HEAD(&idlist, tt, list);
29891536Siedowse	}
29991536Siedowse
30091536Siedowse	/*
30191536Siedowse	 * print record if not in snapshot mode and wanted
30291536Siedowse	 * or in snapshot mode and in snapshot range
30391536Siedowse	 */
304202197Sed	if (bp->ut_type == USER_PROCESS && (want(bp) ||
305202197Sed	    (bp->ut_tv.tv_sec < snaptime &&
30691536Siedowse	    (tt->logout > snaptime || tt->logout < 1)))) {
30791536Siedowse		snapfound = 1;
30891536Siedowse		printentry(bp, tt);
30991536Siedowse	}
310202197Sed	tt->logout = bp->ut_tv.tv_sec;
31191536Siedowse}
31291536Siedowse
31391536Siedowse/*
31491536Siedowse * printentry --
31591536Siedowse *	output an entry
31691536Siedowse *
31791536Siedowse * If `tt' is non-NULL, use it and `crmsg' to print the logout time or
31891536Siedowse * logout type (crash/shutdown) as appropriate.
31991536Siedowse */
32091536Siedowsevoid
321202197Sedprintentry(struct utmpx *bp, struct idtab *tt)
32291536Siedowse{
32391536Siedowse	char ct[80];
32491536Siedowse	struct tm *tm;
32591536Siedowse	time_t	delta;				/* time difference */
32691536Siedowse	time_t	t;
32791536Siedowse
32891538Siedowse	if (maxrec != -1 && !maxrec--)
32991538Siedowse		exit(0);
330202197Sed	t = bp->ut_tv.tv_sec;
33191536Siedowse	tm = localtime(&t);
33291541Siedowse	(void) strftime(ct, sizeof(ct), d_first ?
33391541Siedowse	    (yflag ? "%a %e %b %Y %R" : "%a %e %b %R") :
33491541Siedowse	    (yflag ? "%a %b %e %Y %R" : "%a %b %e %R"), tm);
335202197Sed	switch (bp->ut_type) {
336202197Sed	case BOOT_TIME:
337202197Sed		printf("%-42s", "boot time");
338202197Sed		break;
339202197Sed	case SHUTDOWN_TIME:
340202197Sed		printf("%-42s", "shutdown time");
341202197Sed		break;
342202197Sed	case OLD_TIME:
343202197Sed		printf("%-42s", "old time");
344202197Sed		break;
345202197Sed	case NEW_TIME:
346202197Sed		printf("%-42s", "new time");
347202197Sed		break;
348202197Sed	case USER_PROCESS:
349202197Sed		printf("%-10s %-8s %-22.22s",
350202197Sed		    bp->ut_user, bp->ut_line, bp->ut_host);
351202197Sed		break;
352202197Sed	}
353202197Sed	printf(" %s%c", ct, tt == NULL ? '\n' : ' ');
35491536Siedowse	if (tt == NULL)
35591536Siedowse		return;
35691536Siedowse	if (!tt->logout) {
35791536Siedowse		puts("  still logged in");
35891536Siedowse		return;
35991536Siedowse	}
36091536Siedowse	if (tt->logout < 0) {
36191536Siedowse		tt->logout = -tt->logout;
36291536Siedowse		printf("- %s", crmsg);
36391536Siedowse	} else {
36491536Siedowse		tm = localtime(&tt->logout);
36591536Siedowse		(void) strftime(ct, sizeof(ct), "%R", tm);
36691536Siedowse		printf("- %s", ct);
36791536Siedowse	}
368202197Sed	delta = tt->logout - bp->ut_tv.tv_sec;
36991536Siedowse	if (sflag) {
37091536Siedowse		printf("  (%8ld)\n", (long)delta);
37191536Siedowse	} else {
37291536Siedowse		tm = gmtime(&delta);
37391536Siedowse		(void) strftime(ct, sizeof(ct), width >= 8 ? "%T" : "%R", tm);
37491536Siedowse		if (delta < 86400)
37591536Siedowse			printf("  (%s)\n", ct);
37691536Siedowse		else
37791536Siedowse			printf(" (%ld+%s)\n", (long)delta / 86400, ct);
37891536Siedowse	}
37991536Siedowse}
38091536Siedowse
38191536Siedowse/*
3821590Srgrimes * want --
3831590Srgrimes *	see if want this entry
3841590Srgrimes */
3851590Srgrimesint
386202197Sedwant(struct utmpx *bp)
3871590Srgrimes{
3881590Srgrimes	ARG *step;
3891590Srgrimes
39077291Sdd	if (snaptime)
39177291Sdd		return (NO);
39277291Sdd
3931590Srgrimes	if (!arglist)
3941590Srgrimes		return (YES);
3951590Srgrimes
3961590Srgrimes	for (step = arglist; step; step = step->next)
3971590Srgrimes		switch(step->type) {
3981590Srgrimes		case HOST_TYPE:
399202197Sed			if (!strcasecmp(step->name, bp->ut_host))
4001590Srgrimes				return (YES);
4011590Srgrimes			break;
4021590Srgrimes		case TTY_TYPE:
403202197Sed			if (!strcmp(step->name, bp->ut_line))
4041590Srgrimes				return (YES);
4051590Srgrimes			break;
4061590Srgrimes		case USER_TYPE:
407202197Sed			if (!strcmp(step->name, bp->ut_user))
4081590Srgrimes				return (YES);
4091590Srgrimes			break;
41091536Siedowse		}
4111590Srgrimes	return (NO);
4121590Srgrimes}
4131590Srgrimes
4141590Srgrimes/*
4151590Srgrimes * addarg --
4161590Srgrimes *	add an entry to a linked list of arguments
4171590Srgrimes */
4181590Srgrimesvoid
419102944Sdwmaloneaddarg(int type, char *arg)
4201590Srgrimes{
4211590Srgrimes	ARG *cur;
4221590Srgrimes
42396785Sjmallett	if ((cur = malloc(sizeof(ARG))) == NULL)
42496785Sjmallett		errx(1, "malloc failure");
4251590Srgrimes	cur->next = arglist;
4261590Srgrimes	cur->type = type;
4271590Srgrimes	cur->name = arg;
4281590Srgrimes	arglist = cur;
4291590Srgrimes}
4301590Srgrimes
4311590Srgrimes/*
4321590Srgrimes * hostconv --
4331590Srgrimes *	convert the hostname to search pattern; if the supplied host name
4341590Srgrimes *	has a domain attached that is the same as the current domain, rip
4351590Srgrimes *	off the domain suffix since that's what login(1) does.
4361590Srgrimes */
4371590Srgrimesvoid
438102944Sdwmalonehostconv(char *arg)
4391590Srgrimes{
4401590Srgrimes	static int first = 1;
4411590Srgrimes	static char *hostdot, name[MAXHOSTNAMELEN];
4421590Srgrimes	char *argdot;
4431590Srgrimes
4441590Srgrimes	if (!(argdot = strchr(arg, '.')))
4451590Srgrimes		return;
4461590Srgrimes	if (first) {
4471590Srgrimes		first = 0;
4481590Srgrimes		if (gethostname(name, sizeof(name)))
4491590Srgrimes			err(1, "gethostname");
4501590Srgrimes		hostdot = strchr(name, '.');
4511590Srgrimes	}
4521590Srgrimes	if (hostdot && !strcasecmp(hostdot, argdot))
4531590Srgrimes		*argdot = '\0';
4541590Srgrimes}
4551590Srgrimes
4561590Srgrimes/*
4571590Srgrimes * ttyconv --
4581590Srgrimes *	convert tty to correct name.
4591590Srgrimes */
4601590Srgrimeschar *
461102944Sdwmalonettyconv(char *arg)
4621590Srgrimes{
4631590Srgrimes	char *mval;
4641590Srgrimes
4651590Srgrimes	/*
4661590Srgrimes	 * kludge -- we assume that all tty's end with
4671590Srgrimes	 * a two character suffix.
4681590Srgrimes	 */
4691590Srgrimes	if (strlen(arg) == 2) {
4701590Srgrimes		/* either 6 for "ttyxx" or 8 for "console" */
47196785Sjmallett		if ((mval = malloc(8)) == NULL)
47296785Sjmallett			errx(1, "malloc failure");
4731590Srgrimes		if (!strcmp(arg, "co"))
4741590Srgrimes			(void)strcpy(mval, "console");
4751590Srgrimes		else {
4761590Srgrimes			(void)strcpy(mval, "tty");
4771590Srgrimes			(void)strcpy(mval + 3, arg);
4781590Srgrimes		}
4791590Srgrimes		return (mval);
4801590Srgrimes	}
4811590Srgrimes	if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
4821590Srgrimes		return (arg + 5);
4831590Srgrimes	return (arg);
4841590Srgrimes}
4851590Srgrimes
4861590Srgrimes/*
48777291Sdd * dateconv --
48877291Sdd * 	Convert the snapshot time in command line given in the format
48977291Sdd * 	[[CC]YY]MMDDhhmm[.SS]] to a time_t.
49077291Sdd * 	Derived from atime_arg1() in usr.bin/touch/touch.c
49177291Sdd */
49277291Sddtime_t
493102944Sdwmalonedateconv(char *arg)
49477291Sdd{
49577291Sdd        time_t timet;
49677291Sdd        struct tm *t;
49777291Sdd        int yearset;
49877291Sdd        char *p;
49977291Sdd
50077291Sdd        /* Start with the current time. */
50177291Sdd        if (time(&timet) < 0)
50277291Sdd                err(1, "time");
50377291Sdd        if ((t = localtime(&timet)) == NULL)
50477291Sdd                err(1, "localtime");
50577291Sdd
50677291Sdd        /* [[CC]YY]MMDDhhmm[.SS] */
50777291Sdd        if ((p = strchr(arg, '.')) == NULL)
50877291Sdd                t->tm_sec = 0; 		/* Seconds defaults to 0. */
50977291Sdd        else {
51077291Sdd                if (strlen(p + 1) != 2)
51177291Sdd                        goto terr;
51277291Sdd                *p++ = '\0';
51377291Sdd                t->tm_sec = ATOI2(p);
51477291Sdd        }
51577291Sdd
51677291Sdd        yearset = 0;
51777291Sdd        switch (strlen(arg)) {
51877291Sdd        case 12:                	/* CCYYMMDDhhmm */
51977291Sdd                t->tm_year = ATOI2(arg);
52077291Sdd                t->tm_year *= 100;
52177291Sdd                yearset = 1;
522133332Sdwmalone                /* FALLTHROUGH */
52377291Sdd        case 10:                	/* YYMMDDhhmm */
52477291Sdd                if (yearset) {
52577291Sdd                        yearset = ATOI2(arg);
52677291Sdd                        t->tm_year += yearset;
52777291Sdd                } else {
52877291Sdd                        yearset = ATOI2(arg);
52977291Sdd                        if (yearset < 69)
53077291Sdd                                t->tm_year = yearset + 2000;
53177291Sdd                        else
53277291Sdd                                t->tm_year = yearset + 1900;
53377291Sdd                }
53477291Sdd                t->tm_year -= 1900;     /* Convert to UNIX time. */
53577291Sdd                /* FALLTHROUGH */
53677291Sdd        case 8:				/* MMDDhhmm */
53777291Sdd                t->tm_mon = ATOI2(arg);
53877291Sdd                --t->tm_mon;    	/* Convert from 01-12 to 00-11 */
53977291Sdd                t->tm_mday = ATOI2(arg);
54077291Sdd                t->tm_hour = ATOI2(arg);
54177291Sdd                t->tm_min = ATOI2(arg);
54277291Sdd                break;
54377291Sdd        case 4:				/* hhmm */
54477291Sdd                t->tm_hour = ATOI2(arg);
54577291Sdd                t->tm_min = ATOI2(arg);
54677291Sdd                break;
54777291Sdd        default:
54877291Sdd                goto terr;
54977291Sdd        }
55077291Sdd        t->tm_isdst = -1;       	/* Figure out DST. */
55177291Sdd        timet = mktime(t);
55277291Sdd        if (timet == -1)
55377291Sddterr:           errx(1,
55477291Sdd        "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
55577291Sdd        return timet;
55677291Sdd}
557