Added extra/crond.service for systemd
[dcron.git] / job.c
blob017ca3de627abab44fe29fc333493be0b050d609
2 /*
3 * JOB.C
5 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
6 * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
7 * May be distributed under the GNU General Public License
8 */
10 #include "defs.h"
12 Prototype void RunJob(CronFile *file, CronLine *line);
13 Prototype void EndJob(CronFile *file, CronLine *line, int exit_status);
15 Prototype const char *SendMail;
17 void
18 RunJob(CronFile *file, CronLine *line)
20 char mailFile[SMALL_BUFFER];
21 int mailFd;
22 const char *value = Mailto;
24 line->cl_Pid = 0;
25 line->cl_MailFlag = 0;
28 * try to open mail output file - owner root so nobody can screw with it.
31 snprintf(mailFile, sizeof(mailFile), TempFileFmt,
32 file->cf_UserName, (int)getpid());
34 if ((mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600)) >= 0) {
35 /* success: write headers to mailFile */
36 line->cl_MailFlag = 1;
37 /* if we didn't specify a -m Mailto, use the local user */
38 if (!value)
39 value = file->cf_UserName;
40 fdprintf(mailFd, "To: %s\nSubject: cron for user %s %s\n\n",
41 value,
42 file->cf_UserName,
43 line->cl_Description
45 /* remember mailFile's size */
46 line->cl_MailPos = lseek(mailFd, 0, 1);
49 * else no mailFd, we complain later and don't check job output
50 * but we still run the job if we can
55 * Fork as the user in question and run program
58 if ((line->cl_Pid = fork()) == 0) {
60 * CHILD, FORK OK, PRE-EXEC
62 * Change running state to the user in question
65 if (ChangeUser(file->cf_UserName, TempDir) < 0) {
66 printlogf(LOG_ERR, "unable to ChangeUser (user %s %s)\n",
67 file->cf_UserName,
68 line->cl_Description
70 exit(0);
73 /* from this point we are unpriviledged */
75 if (DebugOpt)
76 printlogf(LOG_DEBUG, "child running: %s\n", line->cl_Description);
79 * Inside child, we copy our fd 2 (which may be /dev/null) into
80 * an open-until-exec fd 8
82 dup2(2, 8);
83 fcntl(8, F_SETFD, FD_CLOEXEC);
84 fclose(stderr);
86 if (mailFd >= 0) {
87 /* stdin is already /dev/null, setup stdout and stderr > mailFile */
88 dup2(mailFd, 1);
89 dup2(mailFd, 2);
90 close(mailFd);
91 } else {
92 /* complain about no mailFd to log (now associated with fd 8) */
93 fdprintlogf(LOG_WARNING, 8, "unable to create mail file %s: cron output for user %s %s to /dev/null\n",
94 mailFile,
95 file->cf_UserName,
96 line->cl_Description
98 /* stderr > /dev/null */
99 dup2(1, 2);
103 * Start a new process group, so that children still in the crond's process group
104 * are all mailjobs.
106 setpgid(0, 0);
108 execl("/bin/sh", "/bin/sh", "-c", line->cl_Shell, NULL);
110 * CHILD FAILED TO EXEC CRONJOB
112 * Complain to our log (now associated with fd 8)
114 fdprintlogf(LOG_ERR, 8, "unable to exec (user %s cmd /bin/sh -c %s)\n",
115 file->cf_UserName,
116 line->cl_Shell
119 * Also complain to stdout, which will be either the mailFile or /dev/null
121 fdprintf(1, "unable to exec: /bin/sh -c %s\n", line->cl_Shell);
122 exit(0);
124 } else if (line->cl_Pid < 0) {
126 * PARENT, FORK FAILED
128 * Complain to log (with regular fd 2)
130 printlogf(LOG_ERR, "unable to fork (user %s %s)\n",
131 file->cf_UserName,
132 line->cl_Description
134 line->cl_Pid = 0;
135 remove(mailFile);
137 } else {
139 * PARENT, FORK SUCCESS
141 * rename mail-file based on pid of child process
143 char mailFile2[SMALL_BUFFER];
145 snprintf(mailFile2, sizeof(mailFile2), TempFileFmt,
146 file->cf_UserName, line->cl_Pid);
147 rename(mailFile, mailFile2);
151 * Close the mail file descriptor.. we can't just leave it open in
152 * a structure, closing it later, because we might run out of descriptors
155 if (mailFd >= 0)
156 close(mailFd);
160 * EndJob - called when main job terminates
163 void
164 EndJob(CronFile *file, CronLine *line, int exit_status)
166 int mailFd;
167 char mailFile[SMALL_BUFFER];
168 struct stat sbuf;
169 struct CronNotifier *notif;
171 if (line->cl_Pid <= 0) {
173 * No job. This should never happen.
175 line->cl_Pid = 0;
176 return;
181 * check return status
183 if (line->cl_Delay > 0) {
184 if (exit_status == EAGAIN) {
186 * returned EAGAIN, wait cl_Delay then retry
187 * we base off the time the job was scheduled/started waiting, not the time it finished
189 * line->cl_NotUntil = time(NULL) + line->cl_Delay; // use this to base off time finished
190 * line->cl_NotUntil += line->cl_Delay; // already applied
192 } else {
194 * process finished without returning EAGAIN (it may have returned some other error)
195 * mark as having run and update timestamp
197 FILE *fi;
198 char buf[SMALL_BUFFER];
199 int succeeded = 0;
201 * we base off the time the job was scheduled/started waiting, not the time it finished
203 * line->cl_LastRan = time(NULL); // use this to base off time finished
205 line->cl_LastRan = line->cl_NotUntil - line->cl_Delay;
206 if ((fi = fopen(line->cl_Timestamp, "w")) != NULL) {
207 if (strftime(buf, sizeof(buf), CRONSTAMP_FMT, localtime(&line->cl_LastRan)))
208 if (fputs(buf, fi) >= 0)
209 succeeded = 1;
210 fclose(fi);
212 if (!succeeded)
213 printlogf(LOG_WARNING, "unable to write timestamp to %s (user %s %s)\n", line->cl_Timestamp, file->cf_UserName, line->cl_Description);
214 line->cl_NotUntil = line->cl_LastRan;
215 line->cl_NotUntil += (line->cl_Freq > 0) ? line->cl_Freq : line->cl_Delay;
219 if (exit_status != EAGAIN) {
221 * notify any waiters
223 notif = line->cl_Notifs;
224 while (notif) {
225 if (notif->cn_Waiter) {
226 notif->cn_Waiter->cw_Flag = exit_status;
228 notif = notif->cn_Next;
231 if (exit_status) {
233 * log non-zero exit_status
235 printlogf(LOG_NOTICE, "exit status %d from user %s %s\n",
236 exit_status,
237 file->cf_UserName,
238 line->cl_Description
243 if (!exit_status || exit_status == EAGAIN)
244 if (DebugOpt)
245 printlogf(LOG_DEBUG, "exit status %d from user %s %s\n",
246 exit_status,
247 file->cf_UserName,
248 line->cl_Description
252 if (line->cl_MailFlag != 1) {
253 /* End of job and no mail file */
254 line->cl_Pid = 0;
255 return;
259 * Calculate mailFile's name before clearing cl_Pid
261 snprintf(mailFile, sizeof(mailFile), TempFileFmt,
262 file->cf_UserName, line->cl_Pid);
263 line->cl_Pid = 0;
265 line->cl_MailFlag = 0;
268 * Check mail file. If size has increased and
269 * the file is still valid, we sendmail it.
272 mailFd = open(mailFile, O_RDONLY);
273 remove(mailFile);
274 if (mailFd < 0) {
275 return;
278 /* Was mailFile tampered with, or didn't grow? */
280 if (fstat(mailFd, &sbuf) < 0 ||
281 sbuf.st_uid != DaemonUid ||
282 sbuf.st_nlink != 0 ||
283 sbuf.st_size == line->cl_MailPos ||
284 !S_ISREG(sbuf.st_mode)
286 close(mailFd);
287 return;
290 if ((line->cl_Pid = fork()) == 0) {
292 * CHILD, FORK OK, PRE-EXEC
294 * Change user id - no way in hell security can be compromised
295 * by the mailing and we already verified the mail file.
298 if (ChangeUser(file->cf_UserName, TempDir) < 0) {
299 printlogf(LOG_ERR, "unable to ChangeUser to send mail (user %s %s)\n",
300 file->cf_UserName,
301 line->cl_Description
303 exit(0);
306 /* from this point we are unpriviledged */
309 * Inside child, we copy our fd 2 (which may be /dev/null) into
310 * an open-until-exec fd 8
313 dup2(2, 8);
314 fcntl(8, F_SETFD, FD_CLOEXEC);
315 fclose(stderr);
318 * Run sendmail with stdin < mailFile and stderr > /dev/null
321 dup2(mailFd, 0);
322 dup2(1, 2);
323 close(mailFd);
325 if (!SendMail) {
327 * If using standard sendmail, note in our log (now associated with fd 8)
328 * that we're trying to mail output
330 fdprintlogf(LOG_INFO, 8, "mailing cron output for user %s %s\n",
331 file->cf_UserName,
332 line->cl_Description
334 execl(SENDMAIL, SENDMAIL, SENDMAIL_ARGS, NULL);
336 /* exec failed: pass through and log the error */
337 SendMail = SENDMAIL;
339 } else {
341 * If using custom mailer script, just try to exec it
343 execl(SendMail, SendMail, NULL);
347 * CHILD FAILED TO EXEC SENDMAIL
349 * Complain to our log (now associated with fd 8)
352 fdprintlogf(LOG_WARNING, 8, "unable to exec %s: cron output for user %s %s to /dev/null\n",
353 SendMail,
354 file->cf_UserName,
355 line->cl_Description
357 exit(0);
359 } else if (line->cl_Pid < 0) {
361 * PARENT, FORK FAILED
363 * Complain to our log (with regular fd 2)
365 printlogf(LOG_WARNING, "unable to fork: cron output for user %s %s to /dev/null\n",
366 file->cf_UserName,
367 line->cl_Description
369 line->cl_Pid = 0;
370 } else {
372 * PARENT, FORK OK
374 * We clear cl_Pid even when mailjob successfully forked
375 * and catch the dead mailjobs with our SIGCHLD handler.
377 line->cl_Pid = 0;
380 close(mailFd);