GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/cron/cron.c Lines: 0 215 0.0 %
Date: 2017-11-07 Branches: 0 135 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: cron.c,v 1.76 2017/06/07 23:36:43 millert Exp $	*/
2
3
/* Copyright 1988,1990,1993,1994 by Paul Vixie
4
 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
5
 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
17
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
#include <sys/types.h>
21
#include <sys/socket.h>
22
#include <sys/stat.h>
23
#include <sys/time.h>
24
#include <sys/un.h>
25
#include <sys/wait.h>
26
27
#include <bitstring.h>
28
#include <err.h>
29
#include <errno.h>
30
#include <grp.h>
31
#include <locale.h>
32
#include <poll.h>
33
#include <signal.h>
34
#include <stdio.h>
35
#include <stdlib.h>
36
#include <string.h>
37
#include <syslog.h>
38
#include <time.h>
39
#include <unistd.h>
40
41
#include "config.h"
42
#include "pathnames.h"
43
#include "macros.h"
44
#include "structs.h"
45
#include "funcs.h"
46
#include "globals.h"
47
48
enum timejump { negative, small, medium, large };
49
50
static	void	usage(void),
51
		run_reboot_jobs(cron_db *),
52
		find_jobs(time_t, cron_db *, int, int),
53
		set_time(int),
54
		cron_sleep(time_t, sigset_t *),
55
		sigchld_handler(int),
56
		sigchld_reaper(void),
57
		parse_args(int c, char *v[]);
58
59
static	int	open_socket(void);
60
61
static	volatile sig_atomic_t	got_sigchld;
62
static	time_t			timeRunning, virtualTime, clockTime;
63
static	int			cronSock;
64
static	long			GMToff;
65
static	cron_db			*database;
66
static	at_db			*at_database;
67
static	double			batch_maxload = BATCH_MAXLOAD;
68
static	int			NoFork;
69
static	time_t			StartTime;
70
	gid_t			cron_gid;
71
72
static void
73
usage(void)
74
{
75
76
	fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname);
77
	exit(EXIT_FAILURE);
78
}
79
80
int
81
main(int argc, char *argv[])
82
{
83
	struct sigaction sact;
84
	sigset_t blocked, omask;
85
	struct group *grp;
86
87
	setlocale(LC_ALL, "");
88
89
	setvbuf(stdout, NULL, _IOLBF, 0);
90
	setvbuf(stderr, NULL, _IOLBF, 0);
91
92
	parse_args(argc, argv);
93
94
	bzero((char *)&sact, sizeof sact);
95
	sigemptyset(&sact.sa_mask);
96
	sact.sa_flags = SA_RESTART;
97
	sact.sa_handler = sigchld_handler;
98
	(void) sigaction(SIGCHLD, &sact, NULL);
99
	sact.sa_handler = SIG_IGN;
100
	(void) sigaction(SIGHUP, &sact, NULL);
101
	(void) sigaction(SIGPIPE, &sact, NULL);
102
103
	openlog(__progname, LOG_PID, LOG_CRON);
104
105
	if (pledge("stdio rpath wpath cpath fattr getpw unix id dns proc exec",
106
	    NULL) == -1) {
107
		warn("pledge");
108
		syslog(LOG_ERR, "(CRON) PLEDGE (%m)");
109
		exit(EXIT_FAILURE);
110
	}
111
112
	if ((grp = getgrnam(CRON_GROUP)) == NULL) {
113
		warnx("can't find cron group %s", CRON_GROUP);
114
		syslog(LOG_ERR, "(CRON) DEATH (can't find cron group)");
115
		exit(EXIT_FAILURE);
116
	}
117
	cron_gid = grp->gr_gid;
118
119
	cronSock = open_socket();
120
121
	if (putenv("PATH="_PATH_DEFPATH) < 0) {
122
		warn("putenv");
123
		syslog(LOG_ERR, "(CRON) DEATH (%m)");
124
		exit(EXIT_FAILURE);
125
	}
126
127
	if (NoFork == 0) {
128
		if (daemon(0, 0) == -1) {
129
			syslog(LOG_ERR, "(CRON) DEATH (%m)");
130
			exit(EXIT_FAILURE);
131
		}
132
		syslog(LOG_INFO, "(CRON) STARTUP (%s)", CRON_VERSION);
133
	}
134
135
	load_database(&database);
136
	scan_atjobs(&at_database, NULL);
137
	set_time(TRUE);
138
	run_reboot_jobs(database);
139
	timeRunning = virtualTime = clockTime;
140
141
	/*
142
	 * We block SIGHUP and SIGCHLD while running jobs and receive them
143
	 * only while sleeping in ppoll().  This ensures no signal is lost.
144
	 */
145
	sigemptyset(&blocked);
146
	sigaddset(&blocked, SIGCHLD);
147
	sigaddset(&blocked, SIGHUP);
148
	sigprocmask(SIG_BLOCK, &blocked, &omask);
149
150
	/*
151
	 * Too many clocks, not enough time (Al. Einstein)
152
	 * These clocks are in minutes since the epoch, adjusted for timezone.
153
	 * virtualTime: is the time it *would* be if we woke up
154
	 * promptly and nobody ever changed the clock. It is
155
	 * monotonically increasing... unless a timejump happens.
156
	 * At the top of the loop, all jobs for 'virtualTime' have run.
157
	 * timeRunning: is the time we last awakened.
158
	 * clockTime: is the time when set_time was last called.
159
	 */
160
	while (TRUE) {
161
		int timeDiff;
162
		enum timejump wakeupKind;
163
164
		/* ... wait for the time (in minutes) to change ... */
165
		do {
166
			cron_sleep(timeRunning + 1, &omask);
167
			set_time(FALSE);
168
		} while (clockTime == timeRunning);
169
		timeRunning = clockTime;
170
171
		/*
172
		 * Calculate how the current time differs from our virtual
173
		 * clock.  Classify the change into one of 4 cases.
174
		 */
175
		timeDiff = timeRunning - virtualTime;
176
177
		/* shortcut for the most common case */
178
		if (timeDiff == 1) {
179
			virtualTime = timeRunning;
180
			find_jobs(virtualTime, database, TRUE, TRUE);
181
		} else {
182
			if (timeDiff > (3*MINUTE_COUNT) ||
183
			    timeDiff < -(3*MINUTE_COUNT))
184
				wakeupKind = large;
185
			else if (timeDiff > 5)
186
				wakeupKind = medium;
187
			else if (timeDiff > 0)
188
				wakeupKind = small;
189
			else
190
				wakeupKind = negative;
191
192
			switch (wakeupKind) {
193
			case small:
194
				/*
195
				 * case 1: timeDiff is a small positive number
196
				 * (wokeup late) run jobs for each virtual
197
				 * minute until caught up.
198
				 */
199
				do {
200
					if (job_runqueue())
201
						sleep(10);
202
					virtualTime++;
203
					find_jobs(virtualTime, database,
204
					    TRUE, TRUE);
205
				} while (virtualTime < timeRunning);
206
				break;
207
208
			case medium:
209
				/*
210
				 * case 2: timeDiff is a medium-sized positive
211
				 * number, for example because we went to DST
212
				 * run wildcard jobs once, then run any
213
				 * fixed-time jobs that would otherwise be
214
				 * skipped if we use up our minute (possible,
215
				 * if there are a lot of jobs to run) go
216
				 * around the loop again so that wildcard jobs
217
				 * have a chance to run, and we do our
218
				 * housekeeping.
219
				 */
220
				/* run wildcard jobs for current minute */
221
				find_jobs(timeRunning, database, TRUE, FALSE);
222
223
				/* run fixed-time jobs for each minute missed */
224
				do {
225
					if (job_runqueue())
226
						sleep(10);
227
					virtualTime++;
228
					find_jobs(virtualTime, database,
229
					    FALSE, TRUE);
230
					set_time(FALSE);
231
				} while (virtualTime< timeRunning &&
232
				    clockTime == timeRunning);
233
				break;
234
235
			case negative:
236
				/*
237
				 * case 3: timeDiff is a small or medium-sized
238
				 * negative num, eg. because of DST ending.
239
				 * Just run the wildcard jobs. The fixed-time
240
				 * jobs probably have already run, and should
241
				 * not be repeated.  Virtual time does not
242
				 * change until we are caught up.
243
				 */
244
				find_jobs(timeRunning, database, TRUE, FALSE);
245
				break;
246
			default:
247
				/*
248
				 * other: time has changed a *lot*,
249
				 * jump virtual time, and run everything
250
				 */
251
				virtualTime = timeRunning;
252
				find_jobs(timeRunning, database, TRUE, TRUE);
253
			}
254
		}
255
256
		/* Jobs to be run (if any) are loaded; clear the queue. */
257
		job_runqueue();
258
259
		/* Run any jobs in the at queue. */
260
		atrun(at_database, batch_maxload,
261
		    timeRunning * SECONDS_PER_MINUTE - GMToff);
262
263
		/* Reload jobs as needed. */
264
		load_database(&database);
265
		scan_atjobs(&at_database, NULL);
266
	}
267
}
268
269
static void
270
run_reboot_jobs(cron_db *db)
271
{
272
	user *u;
273
	entry *e;
274
275
	TAILQ_FOREACH(u, &db->users, entries) {
276
		SLIST_FOREACH(e, &u->crontab, entries) {
277
			if (e->flags & WHEN_REBOOT)
278
				job_add(e, u);
279
		}
280
	}
281
	(void) job_runqueue();
282
}
283
284
static void
285
find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild)
286
{
287
	time_t virtualSecond  = vtime * SECONDS_PER_MINUTE;
288
	struct tm *tm = gmtime(&virtualSecond);
289
	int minute, hour, dom, month, dow;
290
	user *u;
291
	entry *e;
292
293
	/* make 0-based values out of these so we can use them as indices
294
	 */
295
	minute = tm->tm_min -FIRST_MINUTE;
296
	hour = tm->tm_hour -FIRST_HOUR;
297
	dom = tm->tm_mday -FIRST_DOM;
298
	month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
299
	dow = tm->tm_wday -FIRST_DOW;
300
301
	/* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
302
	 * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
303
	 * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
304
	 * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
305
	 * like many bizarre things, it's the standard.
306
	 */
307
	TAILQ_FOREACH(u, &db->users, entries) {
308
		SLIST_FOREACH(e, &u->crontab, entries) {
309
			if (bit_test(e->minute, minute) &&
310
			    bit_test(e->hour, hour) &&
311
			    bit_test(e->month, month) &&
312
			    ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
313
			      ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
314
			      : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
315
			    )
316
			   ) {
317
				if ((doNonWild &&
318
				    !(e->flags & (MIN_STAR|HR_STAR))) ||
319
				    (doWild && (e->flags & (MIN_STAR|HR_STAR))))
320
					job_add(e, u);
321
			}
322
		}
323
	}
324
}
325
326
/*
327
 * Set StartTime and clockTime to the current time.
328
 * These are used for computing what time it really is right now.
329
 * Note that clockTime is a unix wallclock time converted to minutes.
330
 */
331
static void
332
set_time(int initialize)
333
{
334
	struct tm tm;
335
	static int isdst;
336
337
	StartTime = time(NULL);
338
339
	/* We adjust the time to GMT so we can catch DST changes. */
340
	tm = *localtime(&StartTime);
341
	if (initialize || tm.tm_isdst != isdst) {
342
		isdst = tm.tm_isdst;
343
		GMToff = get_gmtoff(&StartTime, &tm);
344
	}
345
	clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
346
}
347
348
/*
349
 * Try to just hit the next minute.
350
 */
351
static void
352
cron_sleep(time_t target, sigset_t *mask)
353
{
354
	int fd, nfds;
355
	unsigned char poke;
356
	struct timespec t1, t2, timeout;
357
	struct sockaddr_un s_un;
358
	socklen_t sunlen;
359
	static struct pollfd pfd[1];
360
361
	clock_gettime(CLOCK_REALTIME, &t1);
362
	t1.tv_sec += GMToff;
363
	timeout.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1;
364
	timeout.tv_nsec = 0;
365
366
	pfd[0].fd = cronSock;
367
	pfd[0].events = POLLIN;
368
369
	while (timespecisset(&timeout) && timeout.tv_sec < 65) {
370
		poke = RELOAD_CRON | RELOAD_AT;
371
372
		/* Sleep until we time out, get a poke, or get a signal. */
373
		nfds = ppoll(pfd, 1, &timeout, mask);
374
		if (nfds == 0)
375
			break;		/* timer expired */
376
		if (nfds == -1 && errno != EINTR)
377
			break;		/* an error occurred */
378
		if (nfds > 0) {
379
			sunlen = sizeof(s_un);
380
			fd = accept4(cronSock, (struct sockaddr *)&s_un,
381
			    &sunlen, SOCK_NONBLOCK);
382
			if (fd >= 0) {
383
				(void) read(fd, &poke, 1);
384
				close(fd);
385
				if (poke & RELOAD_CRON) {
386
					timespecclear(&database->mtime);
387
					load_database(&database);
388
				}
389
				if (poke & RELOAD_AT) {
390
					/*
391
					 * We run any pending at jobs right
392
					 * away so that "at now" really runs
393
					 * jobs immediately.
394
					 */
395
					clock_gettime(CLOCK_REALTIME, &t2);
396
					timespecclear(&at_database->mtime);
397
					if (scan_atjobs(&at_database, &t2))
398
						atrun(at_database,
399
						    batch_maxload, t2.tv_sec);
400
				}
401
			}
402
		} else {
403
			/* Interrupted by a signal. */
404
			if (got_sigchld) {
405
				got_sigchld = 0;
406
				sigchld_reaper();
407
			}
408
		}
409
410
		/* Adjust tv and continue where we left off.  */
411
		clock_gettime(CLOCK_REALTIME, &t2);
412
		t2.tv_sec += GMToff;
413
		timespecsub(&t2, &t1, &t1);
414
		timespecsub(&timeout, &t1, &timeout);
415
		memcpy(&t1, &t2, sizeof(t1));
416
		if (timeout.tv_sec < 0)
417
			timeout.tv_sec = 0;
418
		if (timeout.tv_nsec < 0)
419
			timeout.tv_nsec = 0;
420
	}
421
}
422
423
/* int open_socket(void)
424
 *	opens a UNIX domain socket that crontab uses to poke cron.
425
 *	If the socket is already in use, return an error.
426
 */
427
static int
428
open_socket(void)
429
{
430
	int		   sock, rc;
431
	mode_t		   omask;
432
	struct sockaddr_un s_un;
433
434
	sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
435
	if (sock == -1) {
436
		warn("socket");
437
		syslog(LOG_ERR, "(CRON) DEATH (can't create socket)");
438
		exit(EXIT_FAILURE);
439
	}
440
	bzero(&s_un, sizeof(s_un));
441
	if (strlcpy(s_un.sun_path, _PATH_CRON_SOCK, sizeof(s_un.sun_path))
442
	    >= sizeof(s_un.sun_path)) {
443
		warnc(ENAMETOOLONG, _PATH_CRON_SOCK);
444
		syslog(LOG_ERR, "(CRON) DEATH (socket path too long)");
445
		exit(EXIT_FAILURE);
446
	}
447
	s_un.sun_family = AF_UNIX;
448
449
	if (connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) {
450
		warnx("already running");
451
		syslog(LOG_ERR, "(CRON) DEATH (already running)");
452
		exit(EXIT_FAILURE);
453
	}
454
	if (errno != ENOENT)
455
		unlink(s_un.sun_path);
456
457
	omask = umask(007);
458
	rc = bind(sock, (struct sockaddr *)&s_un, sizeof(s_un));
459
	umask(omask);
460
	if (rc != 0) {
461
		warn("bind");
462
		syslog(LOG_ERR, "(CRON) DEATH (can't bind socket)");
463
		exit(EXIT_FAILURE);
464
	}
465
	if (listen(sock, SOMAXCONN)) {
466
		warn("listen");
467
		syslog(LOG_ERR, "(CRON) DEATH (can't listen on socket)");
468
		exit(EXIT_FAILURE);
469
	}
470
471
	/* pledge won't let us change files to a foreign group. */
472
	if (setegid(cron_gid) == 0) {
473
		chown(s_un.sun_path, -1, cron_gid);
474
		(void)setegid(getgid());
475
	}
476
	chmod(s_un.sun_path, 0660);
477
478
	return(sock);
479
}
480
481
static void
482
sigchld_handler(int x)
483
{
484
	got_sigchld = 1;
485
}
486
487
static void
488
sigchld_reaper(void)
489
{
490
	int waiter;
491
	pid_t pid;
492
493
	do {
494
		pid = waitpid(-1, &waiter, WNOHANG);
495
		switch (pid) {
496
		case -1:
497
			if (errno == EINTR)
498
				continue;
499
			break;
500
		case 0:
501
			break;
502
		default:
503
			break;
504
		}
505
	} while (pid > 0);
506
}
507
508
static void
509
parse_args(int argc, char *argv[])
510
{
511
	int argch;
512
	char *ep;
513
514
	while (-1 != (argch = getopt(argc, argv, "l:n"))) {
515
		switch (argch) {
516
		case 'l':
517
			errno = 0;
518
			batch_maxload = strtod(optarg, &ep);
519
			if (*ep != '\0' || ep == optarg || errno == ERANGE ||
520
			    batch_maxload < 0) {
521
				warnx("illegal load average: %s", optarg);
522
				usage();
523
			}
524
			break;
525
		case 'n':
526
			NoFork = 1;
527
			break;
528
		default:
529
			usage();
530
		}
531
	}
532
}