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