1 /* cron 1.4 - clock daemon Author: Kees J. Bot 2 * 7 Dec 1996 3 */ 4 5 #define nil ((void*)0) 6 #include <sys/types.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <signal.h> 11 #include <limits.h> 12 #include <dirent.h> 13 #include <time.h> 14 #include <errno.h> 15 #include <unistd.h> 16 #include <fcntl.h> 17 #include <pwd.h> 18 #include <grp.h> 19 #include <sys/stat.h> 20 #include <sys/wait.h> 21 #include "misc.h" 22 #include "tab.h" 23 24 static volatile int busy; /* Set when something is afoot, don't sleep! */ 25 static volatile int need_reload;/* Set if table reload required. */ 26 static volatile int need_quit; /* Set if cron must exit. */ 27 static volatile int debug; /* Debug level. */ 28 29 static void run_job(cronjob_t *job) 30 /* Execute a cron job. Register its pid in the job structure. If a job's 31 * crontab has an owner then its output is mailed to that owner, otherwise 32 * no special provisions are made, so the output will go where cron's output 33 * goes. This keeps root's mailbox from filling up. 34 */ 35 { 36 pid_t pid; 37 int need_mailer; 38 int mailfd[2], errfd[2]; 39 struct passwd *pw; 40 crontab_t *tab= job->tab; 41 42 need_mailer= (tab->user != nil); 43 44 if (job->atjob) { 45 struct stat st; 46 47 need_mailer= 1; 48 if (rename(tab->file, tab->data) < 0) { 49 if (errno == ENOENT) { 50 /* Normal error, job deleted. */ 51 need_reload= 1; 52 } else { 53 /* Bad error, halt processing AT jobs. */ 54 cronlog(LOG_CRIT, "Can't rename %s: %s\n", 55 tab->file, strerror(errno)); 56 tab_reschedule(job); 57 } 58 return; 59 } 60 /* Will need to determine the next AT job. */ 61 need_reload= 1; 62 63 if (stat(tab->data, &st) < 0) { 64 cronlog(LOG_ERR, "Can't stat %s: %s\n", 65 tab->data, strerror(errno)); 66 tab_reschedule(job); 67 return; 68 } 69 if ((pw= getpwuid(st.st_uid)) == nil) { 70 cronlog(LOG_ERR, 71 "Unknown owner for uid %lu of AT job %s\n", 72 (unsigned long) st.st_uid, job->cmd); 73 tab_reschedule(job); 74 return; 75 } 76 } else { 77 pw= nil; 78 if (job->user != nil && (pw= getpwnam(job->user)) == nil) { 79 cronlog(LOG_ERR, "%s: Unknown user\n", job->user); 80 tab_reschedule(job); 81 return; 82 } 83 } 84 85 if (need_mailer) { 86 errfd[0]= -1; 87 if (pipe(errfd) < 0 || pipe(mailfd) < 0) { 88 cronlog(LOG_ERR, "pipe() call failed: %s\n", 89 strerror(errno)); 90 if (errfd[0] != -1) { 91 close(errfd[0]); 92 close(errfd[1]); 93 } 94 tab_reschedule(job); 95 return; 96 } 97 (void) fcntl(errfd[1], F_SETFD, 98 fcntl(errfd[1], F_GETFD) | FD_CLOEXEC); 99 100 if ((pid= fork()) == -1) { 101 cronlog(LOG_ERR, "fork() call failed: %s\n", 102 strerror(errno)); 103 close(errfd[0]); 104 close(errfd[1]); 105 close(mailfd[0]); 106 close(mailfd[1]); 107 tab_reschedule(job); 108 return; 109 } 110 111 if (pid == 0) { 112 /* Child that is to be the mailer. */ 113 char subject[70+20], *ps; 114 115 close(errfd[0]); 116 close(mailfd[1]); 117 if (mailfd[0] != 0) { 118 dup2(mailfd[0], 0); 119 close(mailfd[0]); 120 } 121 122 memset(subject, 0, sizeof(subject)); 123 sprintf(subject, 124 "Output from your %s job: %.50s", 125 job->atjob ? "AT" : "cron", 126 job->cmd); 127 if (subject[70] != 0) { 128 strcpy(subject+70-3, "..."); 129 } 130 for (ps= subject; *ps != 0; ps++) { 131 if (*ps == '\n') *ps= '%'; 132 } 133 134 execl("/usr/bin/mail", "mail", "-s", subject, 135 pw->pw_name, (char *) nil); 136 write(errfd[1], &errno, sizeof(errno)); 137 _exit(1); 138 } 139 140 close(mailfd[0]); 141 close(errfd[1]); 142 if (read(errfd[0], &errno, sizeof(errno)) > 0) { 143 cronlog(LOG_ERR, "can't execute /usr/bin/mail: %s\n", 144 strerror(errno)); 145 close(errfd[0]); 146 close(mailfd[1]); 147 tab_reschedule(job); 148 return; 149 } 150 close(errfd[0]); 151 } 152 153 if (pipe(errfd) < 0) { 154 cronlog(LOG_ERR, "pipe() call failed: %s\n", strerror(errno)); 155 if (need_mailer) close(mailfd[1]); 156 tab_reschedule(job); 157 return; 158 } 159 (void) fcntl(errfd[1], F_SETFD, fcntl(errfd[1], F_GETFD) | FD_CLOEXEC); 160 161 if ((pid= fork()) == -1) { 162 cronlog(LOG_ERR, "fork() call failed: %s\n", strerror(errno)); 163 close(errfd[0]); 164 close(errfd[1]); 165 if (need_mailer) close(mailfd[1]); 166 tab_reschedule(job); 167 return; 168 } 169 170 if (pid == 0) { 171 /* Child that is to be the cron job. */ 172 close(errfd[0]); 173 if (need_mailer) { 174 if (mailfd[1] != 1) { 175 dup2(mailfd[1], 1); 176 close(mailfd[1]); 177 } 178 dup2(1, 2); 179 } 180 181 if (pw != nil) { 182 /* Change id to the owner of the job. */ 183 initgroups(pw->pw_name, pw->pw_gid); 184 setgid(pw->pw_gid); 185 setuid(pw->pw_uid); 186 chdir(pw->pw_dir); 187 if (setenv("USER", pw->pw_name, 1) < 0) goto bad; 188 if (setenv("LOGNAME", pw->pw_name, 1) < 0) goto bad; 189 if (setenv("HOME", pw->pw_dir, 1) < 0) goto bad; 190 if (setenv("SHELL", pw->pw_shell[0] == 0 ? "/bin/sh" 191 : pw->pw_shell, 1) < 0) goto bad; 192 } 193 194 if (job->atjob) { 195 execl("/bin/sh", "sh", tab->data, (char *) nil); 196 } else { 197 execl("/bin/sh", "sh", "-c", job->cmd, (char *) nil); 198 } 199 bad: 200 write(errfd[1], &errno, sizeof(errno)); 201 _exit(1); 202 } 203 204 if (need_mailer) close(mailfd[1]); 205 close(errfd[1]); 206 if (read(errfd[0], &errno, sizeof(errno)) > 0) { 207 cronlog(LOG_ERR, "can't execute /bin/sh: %s\n", 208 strerror(errno)); 209 close(errfd[0]); 210 tab_reschedule(job); 211 return; 212 } 213 close(errfd[0]); 214 job->pid= pid; 215 if (debug >= 1) fprintf(stderr, "executing >%s<, pid = %ld\n", 216 job->cmd, (long) job->pid); 217 } 218 219 static void load_crontabs(void) 220 /* Load all the crontabs we like to run. We didn't bother to make a list in 221 * an array or something, this is too system specific to make nice. 222 */ 223 { 224 DIR *spool; 225 #if __minix_vmd 226 FILE *pkgs; 227 #endif 228 229 tab_parse("/usr/lib/crontab", nil); 230 tab_parse("/usr/local/lib/crontab", nil); 231 tab_parse("/var/lib/crontab", nil); 232 233 #if __minix_vmd 234 if ((pkgs= fopen("/usr/lib/packages", "r")) != nil) { 235 char name[NAME_MAX+1]; 236 char *np; 237 int c; 238 char tab[sizeof("/var/opt//lib/crontab") + NAME_MAX]; 239 240 while ((c= fgetc(pkgs)) != EOF) { 241 np= name; 242 while (c != EOF && c != '/' && c != '\n') { 243 if (np < name+NAME_MAX) *np++ = c; 244 c= fgetc(pkgs); 245 } 246 *np= 0; 247 while (c != EOF && c != '\n') c= fgetc(pkgs); 248 249 if (name[0] == 0) continue; /* ? */ 250 251 strcpy(tab, "/var/opt/"); 252 strcat(tab, name); 253 strcat(tab, "/lib/crontab"); 254 tab_parse(tab, nil); 255 } 256 if (ferror(pkgs)) { 257 cronlog(LOG_CRIT, "/usr/lib/packages: %s\n", 258 strerror(errno)); 259 } 260 fclose(pkgs); 261 } else { 262 if (errno != ENOENT) { 263 cronlog(LOG_ERR, "/usr/lib/packages: %s\n", 264 strerror(errno)); 265 } 266 } 267 #endif /* Minix-vmd */ 268 269 if ((spool= opendir("/usr/spool/crontabs")) != nil) { 270 struct dirent *entry; 271 char tab[sizeof("/usr/spool/crontabs/") + NAME_MAX]; 272 273 while ((entry= readdir(spool)) != nil) { 274 if (entry->d_name[0] == '.') continue; 275 276 strcpy(tab, "/usr/spool/crontabs/"); 277 strcat(tab, entry->d_name); 278 tab_parse(tab, entry->d_name); 279 } 280 closedir(spool); 281 } 282 283 /* Find the first to be executed AT job. */ 284 tab_find_atjob("/usr/spool/at"); 285 286 tab_purge(); 287 if (debug >= 2) { 288 tab_print(stderr); 289 fprintf(stderr, "%lu memory chunks in use\n", 290 (unsigned long) alloc_count); 291 } 292 } 293 294 static void handler(int sig) 295 { 296 switch (sig) { 297 case SIGHUP: 298 need_reload= 1; 299 break; 300 case SIGINT: 301 case SIGTERM: 302 need_quit= 1; 303 break; 304 case SIGUSR1: 305 debug++; 306 break; 307 case SIGUSR2: 308 debug= 0; 309 break; 310 } 311 alarm(1); /* A signal may come just before a blocking call. */ 312 busy= 1; 313 } 314 315 static void usage(void) 316 { 317 fprintf(stderr, "Usage: %s [-d[#]]\n", prog_name); 318 exit(1); 319 } 320 321 int main(int argc, char **argv) 322 { 323 int i; 324 struct sigaction sa, osa; 325 FILE *pf; 326 int r; 327 328 prog_name= strrchr(argv[0], '/'); 329 if (prog_name == nil) prog_name= argv[0]; else prog_name++; 330 331 i= 1; 332 while (i < argc && argv[i][0] == '-') { 333 char *opt= argv[i++] + 1; 334 335 if (opt[0] == '-' && opt[1] == 0) break; /* -- */ 336 337 while (*opt != 0) switch (*opt++) { 338 case 'd': 339 if (*opt == 0) { 340 debug= 1; 341 } else { 342 debug= strtoul(opt, &opt, 10); 343 if (*opt != 0) usage(); 344 } 345 break; 346 default: 347 usage(); 348 } 349 } 350 if (i != argc) usage(); 351 352 selectlog(SYSLOG); 353 openlog(prog_name, LOG_PID, LOG_DAEMON); 354 setlogmask(LOG_UPTO(LOG_INFO)); 355 356 /* Save process id. */ 357 if ((pf= fopen(PIDFILE, "w")) == NULL) { 358 fprintf(stderr, "%s: %s\n", PIDFILE, strerror(errno)); 359 exit(1); 360 } 361 fprintf(pf, "%d\n", getpid()); 362 if (ferror(pf) || fclose(pf) == EOF) { 363 fprintf(stderr, "%s: %s\n", PIDFILE, strerror(errno)); 364 exit(1); 365 } 366 367 sigemptyset(&sa.sa_mask); 368 sa.sa_flags= 0; 369 sa.sa_handler= handler; 370 371 /* Hangup: Reload crontab files. */ 372 sigaction(SIGHUP, &sa, nil); 373 374 /* User signal 1 & 2: Raise or reset debug level. */ 375 sigaction(SIGUSR1, &sa, nil); 376 sigaction(SIGUSR2, &sa, nil); 377 378 /* Interrupt and Terminate: Cleanup and exit. */ 379 if (sigaction(SIGINT, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) { 380 sigaction(SIGINT, &sa, nil); 381 } 382 if (sigaction(SIGTERM, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) { 383 sigaction(SIGTERM, &sa, nil); 384 } 385 386 /* Alarm: Wake up and run a job. */ 387 sigaction(SIGALRM, &sa, nil); 388 389 /* Initialize current time and time next to do something. */ 390 time(&now); 391 next= NEVER; 392 393 /* Table load required first time. */ 394 need_reload= 1; 395 396 do { 397 if (need_reload) { 398 need_reload= 0; 399 load_crontabs(); 400 busy= 1; 401 } 402 403 /* Run jobs whose time has come. */ 404 if (next <= now) { 405 cronjob_t *job; 406 407 if ((job= tab_nextjob()) != nil) run_job(job); 408 busy= 1; 409 } 410 411 if (busy) { 412 /* Did a job finish? */ 413 r= waitpid(-1, nil, WNOHANG); 414 busy= 0; 415 } else { 416 /* Sleep until the next job must be started. */ 417 if (next == NEVER) { 418 alarm(0); 419 } else { 420 #if __minix_vmd 421 struct timeval tvnext; 422 423 tvnext.tv_sec= next; 424 tvnext.tv_usec= 0; 425 sysutime(UTIME_SETALARM, &tvnext); 426 #else 427 alarm((next - now) > INT_MAX 428 ? INT_MAX : (next - now)); 429 #endif 430 } 431 if (debug >= 1) fprintf(stderr, "%s: sleep until %s", 432 prog_name, ctime(&next)); 433 434 closelog(); /* Don't keep resources open. */ 435 436 /* Wait for a job to exit or a timeout. */ 437 r= waitpid(-1, nil, 0); 438 if (r == -1 && errno == ECHILD) pause(); 439 alarm(0); 440 time(&now); 441 } 442 443 if (r > 0) { 444 /* A job has finished, reschedule it. */ 445 if (debug >= 1) fprintf(stderr, "pid %d has exited\n", 446 r); 447 tab_reap_job((pid_t) r); 448 busy= 1; 449 } 450 } while (!need_quit); 451 452 /* Remove the pid file to signal that cron is gone. */ 453 unlink(PIDFILE); 454 455 return 0; 456 } 457 458 /* 459 * $PchId: cron.c,v 1.4 2000/07/17 19:00:35 philip Exp $ 460 */ 461