GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/at/parsetime.c Lines: 0 237 0.0 %
Date: 2017-11-07 Branches: 0 190 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: parsetime.c,v 1.26 2015/11/11 17:42:51 millert Exp $	*/
2
3
/*
4
 * parsetime.c - parse time for at(1)
5
 * Copyright (C) 1993, 1994  Thomas Koenig
6
 *
7
 * modifications for english-language times
8
 * Copyright (C) 1993  David Parsons
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions
12
 * are met:
13
 * 1. Redistributions of source code must retain the above copyright
14
 *    notice, this list of conditions and the following disclaimer.
15
 * 2. The name of the author(s) may not be used to endorse or promote
16
 *    products derived from this software without specific prior written
17
 *    permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
 *
30
 *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS
31
 *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
32
 *     |NOON                       | |[TOMORROW]                          |
33
 *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
34
 *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
35
 *                                   \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS/
36
 */
37
38
#include <sys/types.h>
39
40
#include <err.h>
41
#include <errno.h>
42
#include <ctype.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <time.h>
47
#include <unistd.h>
48
49
#include "globals.h"
50
#include "at.h"
51
52
/* Structures and unions */
53
54
enum {	/* symbols */
55
	MIDNIGHT, NOON, TEATIME,
56
	PM, AM, TOMORROW, TODAY, NOW,
57
	MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
58
	NUMBER, NEXT, PLUS, DOT, SLASH, ID, JUNK,
59
	JAN, FEB, MAR, APR, MAY, JUN,
60
	JUL, AUG, SEP, OCT, NOV, DEC,
61
	SUN, MON, TUE, WED, THU, FRI, SAT
62
};
63
64
/*
65
 * parse translation table - table driven parsers can be your FRIEND!
66
 */
67
struct {
68
	char *name;	/* token name */
69
	int value;	/* token id */
70
	int plural;	/* is this plural? */
71
} Specials[] = {
72
	{ "midnight", MIDNIGHT, 0 },	/* 00:00:00 of today or tomorrow */
73
	{ "noon", NOON, 0 },		/* 12:00:00 of today or tomorrow */
74
	{ "teatime", TEATIME, 0 },	/* 16:00:00 of today or tomorrow */
75
	{ "am", AM, 0 },		/* morning times for 0-12 clock */
76
	{ "pm", PM, 0 },		/* evening times for 0-12 clock */
77
	{ "tomorrow", TOMORROW, 0 },	/* execute 24 hours from time */
78
	{ "today", TODAY, 0 },		/* execute today - don't advance time */
79
	{ "now", NOW, 0 },		/* opt prefix for PLUS */
80
	{ "next", NEXT, 0 },		/* opt prefix for + 1 */
81
82
	{ "minute", MINUTES, 0 },	/* minutes multiplier */
83
	{ "min", MINUTES, 0 },
84
	{ "m", MINUTES, 0 },
85
	{ "minutes", MINUTES, 1 },	/* (pluralized) */
86
	{ "hour", HOURS, 0 },		/* hours ... */
87
	{ "hr", HOURS, 0 },		/* abbreviated */
88
	{ "h", HOURS, 0 },
89
	{ "hours", HOURS, 1 },		/* (pluralized) */
90
	{ "day", DAYS, 0 },		/* days ... */
91
	{ "d", DAYS, 0 },
92
	{ "days", DAYS, 1 },		/* (pluralized) */
93
	{ "week", WEEKS, 0 },		/* week ... */
94
	{ "w", WEEKS, 0 },
95
	{ "weeks", WEEKS, 1 },		/* (pluralized) */
96
	{ "month", MONTHS, 0 },		/* month ... */
97
	{ "mo", MONTHS, 0 },
98
	{ "mth", MONTHS, 0 },
99
	{ "months", MONTHS, 1 },	/* (pluralized) */
100
	{ "year", YEARS, 0 },		/* year ... */
101
	{ "y", YEARS, 0 },
102
	{ "years", YEARS, 1 },		/* (pluralized) */
103
	{ "jan", JAN, 0 },
104
	{ "feb", FEB, 0 },
105
	{ "mar", MAR, 0 },
106
	{ "apr", APR, 0 },
107
	{ "may", MAY, 0 },
108
	{ "jun", JUN, 0 },
109
	{ "jul", JUL, 0 },
110
	{ "aug", AUG, 0 },
111
	{ "sep", SEP, 0 },
112
	{ "oct", OCT, 0 },
113
	{ "nov", NOV, 0 },
114
	{ "dec", DEC, 0 },
115
	{ "january", JAN,0 },
116
	{ "february", FEB,0 },
117
	{ "march", MAR,0 },
118
	{ "april", APR,0 },
119
	{ "may", MAY,0 },
120
	{ "june", JUN,0 },
121
	{ "july", JUL,0 },
122
	{ "august", AUG,0 },
123
	{ "september", SEP,0 },
124
	{ "october", OCT,0 },
125
	{ "november", NOV,0 },
126
	{ "december", DEC,0 },
127
	{ "sunday", SUN, 0 },
128
	{ "sun", SUN, 0 },
129
	{ "monday", MON, 0 },
130
	{ "mon", MON, 0 },
131
	{ "tuesday", TUE, 0 },
132
	{ "tue", TUE, 0 },
133
	{ "wednesday", WED, 0 },
134
	{ "wed", WED, 0 },
135
	{ "thursday", THU, 0 },
136
	{ "thu", THU, 0 },
137
	{ "friday", FRI, 0 },
138
	{ "fri", FRI, 0 },
139
	{ "saturday", SAT, 0 },
140
	{ "sat", SAT, 0 },
141
};
142
143
static char **scp;	/* scanner - pointer at arglist */
144
static int scc;		/* scanner - count of remaining arguments */
145
static char *sct;	/* scanner - next char pointer in current argument */
146
static int need;	/* scanner - need to advance to next argument */
147
static char *sc_token;	/* scanner - token buffer */
148
static size_t sc_len;   /* scanner - length of token buffer */
149
static int sc_tokid;	/* scanner - token id */
150
static int sc_tokplur;	/* scanner - is token plural? */
151
152
/*
153
 * parse a token, checking if it's something special to us
154
 */
155
static int
156
parse_token(char *arg)
157
{
158
	int i;
159
160
	for (i=0; i < sizeof(Specials) / sizeof(Specials[0]); i++) {
161
		if (strcasecmp(Specials[i].name, arg) == 0) {
162
			sc_tokplur = Specials[i].plural;
163
		    	return (sc_tokid = Specials[i].value);
164
		}
165
	}
166
167
	/* not special - must be some random id */
168
	return (ID);
169
}
170
171
172
/*
173
 * init_scanner() sets up the scanner to eat arguments
174
 */
175
static int
176
init_scanner(int argc, char **argv)
177
{
178
	scp = argv;
179
	scc = argc;
180
	need = 1;
181
	sc_len = 1;
182
	while (argc-- > 0)
183
		sc_len += strlen(*argv++);
184
185
	if ((sc_token = malloc(sc_len)) == NULL) {
186
		warn(NULL);
187
		return (-1);
188
	}
189
	return (0);
190
}
191
192
/*
193
 * token() fetches a token from the input stream
194
 */
195
static int
196
token(void)
197
{
198
	int idx;
199
200
	for (;;) {
201
		bzero(sc_token, sc_len);
202
		sc_tokid = EOF;
203
		sc_tokplur = 0;
204
		idx = 0;
205
206
		/*
207
		 * if we need to read another argument, walk along the
208
		 * argument list; when we fall off the arglist, we'll
209
		 * just return EOF forever
210
		 */
211
		if (need) {
212
			if (scc < 1)
213
				return (sc_tokid);
214
			sct = *scp;
215
			scp++;
216
			scc--;
217
			need = 0;
218
		}
219
		/*
220
		 * eat whitespace now - if we walk off the end of the argument,
221
		 * we'll continue, which puts us up at the top of the while loop
222
		 * to fetch the next argument in
223
		 */
224
		while (isspace((unsigned char)*sct))
225
			++sct;
226
		if (!*sct) {
227
			need = 1;
228
			continue;
229
		}
230
231
		/*
232
		 * preserve the first character of the new token
233
		 */
234
		sc_token[0] = *sct++;
235
236
		/*
237
		 * then see what it is
238
		 */
239
		if (isdigit((unsigned char)sc_token[0])) {
240
			while (isdigit((unsigned char)*sct))
241
				sc_token[++idx] = *sct++;
242
			sc_token[++idx] = 0;
243
			return ((sc_tokid = NUMBER));
244
		} else if (isalpha((unsigned char)sc_token[0])) {
245
			while (isalpha((unsigned char)*sct))
246
				sc_token[++idx] = *sct++;
247
			sc_token[++idx] = 0;
248
			return (parse_token(sc_token));
249
		}
250
		else if (sc_token[0] == ':' || sc_token[0] == '.')
251
			return ((sc_tokid = DOT));
252
		else if (sc_token[0] == '+')
253
			return ((sc_tokid = PLUS));
254
		else if (sc_token[0] == '/')
255
			return ((sc_tokid = SLASH));
256
		else
257
			return ((sc_tokid = JUNK));
258
	}
259
}
260
261
262
/*
263
 * plonk() gives an appropriate error message if a token is incorrect
264
 */
265
static void
266
plonk(int tok)
267
{
268
	warnx("%s time", (tok == EOF) ? "incomplete" : "garbled");
269
}
270
271
272
/*
273
 * expect() gets a token and returns -1 if it's not the token we want
274
 */
275
static int
276
expect(int desired)
277
{
278
	if (token() != desired) {
279
		plonk(sc_tokid);
280
		return (-1);
281
	}
282
	return (0);
283
}
284
285
286
/*
287
 * dateadd() adds a number of minutes to a date.  It is extraordinarily
288
 * stupid regarding day-of-month overflow, and will most likely not
289
 * work properly
290
 */
291
static void
292
dateadd(int minutes, struct tm *tm)
293
{
294
	/* increment days */
295
296
	while (minutes > 24*60) {
297
		minutes -= 24*60;
298
		tm->tm_mday++;
299
	}
300
301
	/* increment hours */
302
	while (minutes > 60) {
303
		minutes -= 60;
304
		tm->tm_hour++;
305
		if (tm->tm_hour > 23) {
306
			tm->tm_mday++;
307
			tm->tm_hour = 0;
308
		}
309
	}
310
311
	/* increment minutes */
312
	tm->tm_min += minutes;
313
314
	if (tm->tm_min > 59) {
315
		tm->tm_hour++;
316
		tm->tm_min -= 60;
317
318
		if (tm->tm_hour > 23) {
319
			tm->tm_mday++;
320
			tm->tm_hour = 0;
321
		}
322
	}
323
}
324
325
326
/*
327
 * plus() parses a now + time
328
 *
329
 *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS]
330
 *
331
 */
332
static int
333
plus(struct tm *tm)
334
{
335
	int increment;
336
	int expectplur;
337
338
	if (sc_tokid == NEXT) {
339
		increment = 1;
340
		expectplur = 0;
341
	} else {
342
		if (expect(NUMBER) != 0)
343
			return (-1);
344
		increment = atoi(sc_token);
345
		expectplur = (increment != 1) ? 1 : 0;
346
	}
347
348
	switch (token()) {
349
	case YEARS:
350
		tm->tm_year += increment;
351
		return (0);
352
	case MONTHS:
353
		tm->tm_mon += increment;
354
		while (tm->tm_mon >= 12) {
355
		    tm->tm_year++;
356
		    tm->tm_mon -= 12;
357
		}
358
		return (0);
359
	case WEEKS:
360
		increment *= 7;
361
		/* FALLTHROUGH */
362
	case DAYS:
363
		increment *= 24;
364
		/* FALLTHROUGH */
365
	case HOURS:
366
		increment *= 60;
367
		/* FALLTHROUGH */
368
	case MINUTES:
369
		if (expectplur != sc_tokplur)
370
			warnx("pluralization is wrong");
371
		dateadd(increment, tm);
372
		return (0);
373
	}
374
375
	plonk(sc_tokid);
376
	return (-1);
377
}
378
379
380
/*
381
 * tod() computes the time of day
382
 *     [NUMBER [DOT NUMBER] [AM|PM]]
383
 */
384
static int
385
tod(struct tm *tm)
386
{
387
	int hour, minute = 0;
388
	size_t tlen;
389
390
	hour = atoi(sc_token);
391
	tlen = strlen(sc_token);
392
393
	/*
394
	 * first pick out the time of day - if it's 4 digits, we assume
395
	 * a HHMM time, otherwise it's HH DOT MM time
396
	 */
397
	if (token() == DOT) {
398
		if (expect(NUMBER) != 0)
399
			return (-1);
400
		minute = atoi(sc_token);
401
		if (minute > 59)
402
			goto bad;
403
		token();
404
	} else if (tlen == 4) {
405
		minute = hour % 100;
406
		if (minute > 59)
407
			goto bad;
408
		hour = hour / 100;
409
	}
410
411
	/*
412
	 * check if an AM or PM specifier was given
413
	 */
414
	if (sc_tokid == AM || sc_tokid == PM) {
415
		if (hour > 12)
416
			goto bad;
417
418
		if (sc_tokid == PM) {
419
			if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
420
				hour += 12;
421
		} else {
422
			if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
423
				hour = 0;
424
		}
425
		token();
426
	} else if (hour > 23)
427
		goto bad;
428
429
	/*
430
	 * if we specify an absolute time, we don't want to bump the day even
431
	 * if we've gone past that time - but if we're specifying a time plus
432
	 * a relative offset, it's okay to bump things
433
	 */
434
	if ((sc_tokid == EOF || sc_tokid == PLUS || sc_tokid == NEXT) &&
435
	    tm->tm_hour > hour) {
436
		tm->tm_mday++;
437
		tm->tm_wday++;
438
	}
439
440
	tm->tm_hour = hour;
441
	tm->tm_min = minute;
442
	if (tm->tm_hour == 24) {
443
		tm->tm_hour = 0;
444
		tm->tm_mday++;
445
	}
446
	return (0);
447
bad:
448
	warnx("garbled time");
449
	return (-1);
450
}
451
452
453
/*
454
 * assign_date() assigns a date, wrapping to next year if needed
455
 */
456
static void
457
assign_date(struct tm *tm, int mday, int mon, int year)
458
{
459
460
	/*
461
	 * Convert year into tm_year format (year - 1900).
462
	 * We may be given the year in 2 digit, 4 digit, or tm_year format.
463
	 */
464
	if (year != -1) {
465
		if (year >= 1900)
466
			year -= 1900;	/* convert from 4 digit year */
467
		else if (year < 100) {
468
			/* Convert to tm_year assuming current century */
469
			year += (tm->tm_year / 100) * 100;
470
471
			if (year == tm->tm_year - 1)
472
				year++;		/* Common off by one error */
473
			else if (year < tm->tm_year)
474
				year += 100;	/* must be in next century */
475
		}
476
	}
477
478
	if (year < 0 &&
479
	    (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
480
		year = tm->tm_year + 1;
481
482
	tm->tm_mday = mday;
483
	tm->tm_mon = mon;
484
485
	if (year >= 0)
486
		tm->tm_year = year;
487
}
488
489
490
/*
491
 * month() picks apart a month specification
492
 *
493
 *  /[<month> NUMBER [NUMBER]]           \
494
 *  |[TOMORROW]                          |
495
 *  |[DAY OF WEEK]                       |
496
 *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
497
 *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS/
498
 */
499
static int
500
month(struct tm *tm)
501
{
502
	int year = (-1);
503
	int mday, wday, mon;
504
	size_t tlen;
505
506
	switch (sc_tokid) {
507
	case NEXT:
508
	case PLUS:
509
		if (plus(tm) != 0)
510
			return (-1);
511
		break;
512
513
	case TOMORROW:
514
		/* do something tomorrow */
515
		tm->tm_mday++;
516
		tm->tm_wday++;
517
	case TODAY:
518
		/* force ourselves to stay in today - no further processing */
519
		token();
520
		break;
521
522
	case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
523
	case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
524
		/*
525
		 * do month mday [year]
526
		 */
527
		mon = sc_tokid - JAN;
528
		if (expect(NUMBER) != 0)
529
			return (-1);
530
		mday = atoi(sc_token);
531
		if (token() == NUMBER) {
532
			year = atoi(sc_token);
533
			token();
534
		}
535
		assign_date(tm, mday, mon, year);
536
		break;
537
538
	case SUN: case MON: case TUE:
539
	case WED: case THU: case FRI:
540
	case SAT:
541
		/* do a particular day of the week */
542
		wday = sc_tokid - SUN;
543
544
		mday = tm->tm_mday;
545
546
		/* if this day is < today, then roll to next week */
547
		if (wday < tm->tm_wday)
548
			mday += 7 - (tm->tm_wday - wday);
549
		else
550
			mday += (wday - tm->tm_wday);
551
552
		tm->tm_wday = wday;
553
554
		assign_date(tm, mday, tm->tm_mon, tm->tm_year);
555
		break;
556
557
	case NUMBER:
558
		/*
559
		 * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
560
		 */
561
		tlen = strlen(sc_token);
562
		mon = atoi(sc_token);
563
		token();
564
565
		if (sc_tokid == SLASH || sc_tokid == DOT) {
566
			int sep;
567
568
			sep = sc_tokid;
569
			if (expect(NUMBER) != 0)
570
				return (-1);
571
			mday = atoi(sc_token);
572
			if (token() == sep) {
573
				if (expect(NUMBER) != 0)
574
					return (-1);
575
				year = atoi(sc_token);
576
				token();
577
			}
578
579
			/*
580
			 * flip months and days for european timing
581
			 */
582
			if (sep == DOT) {
583
				int x = mday;
584
				mday = mon;
585
				mon = x;
586
			}
587
		} else if (tlen == 6 || tlen == 8) {
588
			if (tlen == 8) {
589
				year = (mon % 10000) - 1900;
590
				mon /= 10000;
591
			} else {
592
				year = mon % 100;
593
				mon /= 100;
594
			}
595
			mday = mon % 100;
596
			mon /= 100;
597
		} else
598
			goto bad;
599
600
		mon--;
601
		if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
602
			goto bad;
603
604
		assign_date(tm, mday, mon, year);
605
		break;
606
	}
607
	return (0);
608
bad:
609
	warnx("garbled time");
610
	return (-1);
611
}
612
613
614
time_t
615
parsetime(int argc, char **argv)
616
{
617
	/*
618
	 * Do the argument parsing, die if necessary, and return the
619
	 * time the job should be run.
620
	 */
621
	time_t nowtimer, runtimer;
622
	struct tm nowtime, runtime;
623
	int hr = 0;
624
	/* this MUST be initialized to zero for midnight/noon/teatime */
625
626
	if (argc == 0)
627
		return (-1);
628
629
	nowtimer = time(NULL);
630
	nowtime = *localtime(&nowtimer);
631
632
	runtime = nowtime;
633
	runtime.tm_sec = 0;
634
	runtime.tm_isdst = 0;
635
636
	if (init_scanner(argc, argv) == -1)
637
		return (-1);
638
639
	switch (token()) {
640
	case NOW:	/* now is optional prefix for PLUS tree */
641
		token();
642
		if (sc_tokid == EOF) {
643
			runtime = nowtime;
644
			break;
645
		}
646
		else if (sc_tokid != PLUS && sc_tokid != NEXT)
647
			plonk(sc_tokid);
648
	case NEXT:
649
	case PLUS:
650
		if (plus(&runtime) != 0)
651
			return (-1);
652
		break;
653
654
	case NUMBER:
655
		if (tod(&runtime) != 0 || month(&runtime) != 0)
656
			return (-1);
657
		break;
658
659
		/*
660
		 * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
661
		 * hr to zero up above, then fall into this case in such a
662
		 * way so we add +12 +4 hours to it for teatime, +12 hours
663
		 * to it for noon, and nothing at all for midnight, then
664
		 * set our runtime to that hour before leaping into the
665
		 * month scanner
666
		 */
667
	case TEATIME:
668
		hr += 4;
669
		/* FALLTHROUGH */
670
	case NOON:
671
		hr += 12;
672
		/* FALLTHROUGH */
673
	case MIDNIGHT:
674
		if (runtime.tm_hour >= hr) {
675
			runtime.tm_mday++;
676
			runtime.tm_wday++;
677
		}
678
		runtime.tm_hour = hr;
679
		runtime.tm_min = 0;
680
		token();
681
		/* fall through to month setting */
682
		/* FALLTHROUGH */
683
	default:
684
		if (month(&runtime) != 0)
685
			return (-1);
686
		break;
687
	} /* ugly case statement */
688
	if (expect(EOF) != 0)
689
		return (-1);
690
691
	/*
692
	 * adjust for daylight savings time
693
	 */
694
	runtime.tm_isdst = -1;
695
	runtimer = mktime(&runtime);
696
697
	if (runtimer < 0) {
698
		warnx("garbled time");
699
		return (-1);
700
	}
701
702
	if (nowtimer > runtimer) {
703
		warnx("cannot schedule jobs in the past");
704
		return (-1);
705
	}
706
707
	return (runtimer);
708
}