crontab.c revision 15161
1296077Sadrian/* Copyright 1988,1990,1993,1994 by Paul Vixie 2296077Sadrian * All rights reserved 3296077Sadrian * 4296077Sadrian * Distribute freely, except: don't remove my name from the source or 5296077Sadrian * documentation (don't take credit for my work), mark your changes (don't 6296077Sadrian * get me blamed for your possible bugs), don't alter or remove this 7296077Sadrian * notice. May be sold if buildable source is provided to buyer. No 8296077Sadrian * warrantee of any kind, express or implied, is included with this 9296077Sadrian * software; use at your own risk, responsibility for damages (if any) to 10296077Sadrian * anyone resulting from the use of this software rests entirely with the 11296077Sadrian * user. 12296077Sadrian * 13296077Sadrian * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 14296077Sadrian * I'll try to keep a version up to date. I can be reached as follows: 15296077Sadrian * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 16296077Sadrian * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp 17296077Sadrian */ 18296077Sadrian 19296077Sadrian#if !defined(lint) && !defined(LINT) 20296077Sadrianstatic char rcsid[] = "$Id: crontab.c,v 1.3 1995/05/30 03:47:04 rgrimes Exp $"; 21296077Sadrian#endif 22296077Sadrian 23296077Sadrian/* crontab - install and manage per-user crontab files 24296077Sadrian * vix 02may87 [RCS has the rest of the log] 25296077Sadrian * vix 26jan87 [original] 26296077Sadrian */ 27296077Sadrian 28296077Sadrian 29296077Sadrian#define MAIN_PROGRAM 30296077Sadrian 31296077Sadrian 32296077Sadrian#include "cron.h" 33296077Sadrian#include <errno.h> 34296077Sadrian#include <fcntl.h> 35296077Sadrian#include <sys/file.h> 36296077Sadrian#include <sys/stat.h> 37296077Sadrian#ifdef USE_UTIMES 38296077Sadrian# include <sys/time.h> 39296077Sadrian#else 40296077Sadrian# include <time.h> 41296077Sadrian# include <utime.h> 42296077Sadrian#endif 43296077Sadrian#if defined(POSIX) 44296077Sadrian# include <locale.h> 45296077Sadrian#endif 46296077Sadrian 47296077Sadrian 48296077Sadrian#define NHEADER_LINES 3 49296077Sadrian 50296077Sadrian 51296077Sadrianenum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; 52296077Sadrian 53296077Sadrian#if DEBUGGING 54296077Sadrianstatic char *Options[] = { "???", "list", "delete", "edit", "replace" }; 55296077Sadrian#endif 56296077Sadrian 57296077Sadrian 58296077Sadrianstatic PID_T Pid; 59296077Sadrianstatic char User[MAX_UNAME], RealUser[MAX_UNAME]; 60296077Sadrianstatic char Filename[MAX_FNAME]; 61296077Sadrianstatic FILE *NewCrontab; 62296077Sadrianstatic int CheckErrorCount; 63296077Sadrianstatic enum opt_t Option; 64296077Sadrianstatic struct passwd *pw; 65296077Sadrianstatic void list_cmd __P((void)), 66296077Sadrian delete_cmd __P((void)), 67296077Sadrian edit_cmd __P((void)), 68296077Sadrian poke_daemon __P((void)), 69296077Sadrian check_error __P((char *)), 70296077Sadrian parse_args __P((int c, char *v[])); 71296077Sadrianstatic int replace_cmd __P((void)); 72296077Sadrian 73296077Sadrian 74296077Sadrianstatic void 75296077Sadrianusage(msg) 76296077Sadrian char *msg; 77296077Sadrian{ 78296077Sadrian fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg); 79296077Sadrian fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName); 80296077Sadrian fprintf(stderr, "\t%s [-u user] { -e | -l | -r }\n", ProgramName); 81296077Sadrian fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n"); 82296077Sadrian fprintf(stderr, "\t-e\t(edit user's crontab)\n"); 83296077Sadrian fprintf(stderr, "\t-l\t(list user's crontab)\n"); 84296077Sadrian fprintf(stderr, "\t-r\t(delete user's crontab)\n"); 85296077Sadrian exit(ERROR_EXIT); 86296077Sadrian} 87296077Sadrian 88296077Sadrian 89296077Sadrianint 90296077Sadrianmain(argc, argv) 91296077Sadrian int argc; 92296077Sadrian char *argv[]; 93296077Sadrian{ 94296077Sadrian int exitstatus; 95296077Sadrian 96296077Sadrian Pid = getpid(); 97296077Sadrian ProgramName = argv[0]; 98296077Sadrian 99296077Sadrian#if defined(POSIX) 100296077Sadrian setlocale(LC_ALL, ""); 101296077Sadrian#endif 102296077Sadrian 103296077Sadrian#if defined(BSD) 104296077Sadrian setlinebuf(stderr); 105296077Sadrian#endif 106296077Sadrian parse_args(argc, argv); /* sets many globals, opens a file */ 107296077Sadrian set_cron_uid(); 108296077Sadrian set_cron_cwd(); 109296077Sadrian if (!allowed(User)) { 110296077Sadrian fprintf(stderr, 111296077Sadrian "You (%s) are not allowed to use this program (%s)\n", 112296077Sadrian User, ProgramName); 113296077Sadrian fprintf(stderr, "See crontab(1) for more information\n"); 114296077Sadrian log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); 115296077Sadrian exit(ERROR_EXIT); 116296077Sadrian } 117296077Sadrian exitstatus = OK_EXIT; 118296077Sadrian switch (Option) { 119296077Sadrian case opt_list: list_cmd(); 120296077Sadrian break; 121296077Sadrian case opt_delete: delete_cmd(); 122296077Sadrian break; 123296077Sadrian case opt_edit: edit_cmd(); 124296077Sadrian break; 125296077Sadrian case opt_replace: if (replace_cmd() < 0) 126296077Sadrian exitstatus = ERROR_EXIT; 127296077Sadrian break; 128296077Sadrian } 129296077Sadrian exit(0); 130296077Sadrian /*NOTREACHED*/ 131296077Sadrian} 132296077Sadrian 133296077Sadrian 134296077Sadrianstatic void 135296077Sadrianparse_args(argc, argv) 136296077Sadrian int argc; 137296077Sadrian char *argv[]; 138296077Sadrian{ 139296077Sadrian int argch; 140296077Sadrian 141296077Sadrian if (!(pw = getpwuid(getuid()))) { 142296077Sadrian fprintf(stderr, "%s: your UID isn't in the passwd file.\n", 143296077Sadrian ProgramName); 144296077Sadrian fprintf(stderr, "bailing out.\n"); 145296077Sadrian exit(ERROR_EXIT); 146296077Sadrian } 147296077Sadrian strcpy(User, pw->pw_name); 148296077Sadrian strcpy(RealUser, User); 149296077Sadrian Filename[0] = '\0'; 150296077Sadrian Option = opt_unknown; 151296077Sadrian while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) { 152296077Sadrian switch (argch) { 153296077Sadrian case 'x': 154296077Sadrian if (!set_debug_flags(optarg)) 155296077Sadrian usage("bad debug option"); 156296077Sadrian break; 157296077Sadrian case 'u': 158296077Sadrian if (getuid() != ROOT_UID) 159296077Sadrian { 160296077Sadrian fprintf(stderr, 161296077Sadrian "must be privileged to use -u\n"); 162296077Sadrian exit(ERROR_EXIT); 163296077Sadrian } 164296077Sadrian if (!(pw = getpwnam(optarg))) 165296077Sadrian { 166296077Sadrian fprintf(stderr, "%s: user `%s' unknown\n", 167296077Sadrian ProgramName, optarg); 168296077Sadrian exit(ERROR_EXIT); 169296077Sadrian } 170296077Sadrian (void) strcpy(User, optarg); 171296077Sadrian break; 172296077Sadrian case 'l': 173296077Sadrian if (Option != opt_unknown) 174296077Sadrian usage("only one operation permitted"); 175296077Sadrian Option = opt_list; 176296077Sadrian break; 177296077Sadrian case 'r': 178296077Sadrian if (Option != opt_unknown) 179296077Sadrian usage("only one operation permitted"); 180296077Sadrian Option = opt_delete; 181296077Sadrian break; 182296077Sadrian case 'e': 183296077Sadrian if (Option != opt_unknown) 184296077Sadrian usage("only one operation permitted"); 185296077Sadrian Option = opt_edit; 186296077Sadrian break; 187296077Sadrian default: 188296077Sadrian usage("unrecognized option"); 189296077Sadrian } 190296077Sadrian } 191296077Sadrian 192296077Sadrian endpwent(); 193296077Sadrian 194296077Sadrian if (Option != opt_unknown) { 195296077Sadrian if (argv[optind] != NULL) { 196296077Sadrian usage("no arguments permitted after this option"); 197296077Sadrian } 198296077Sadrian } else { 199296077Sadrian if (argv[optind] != NULL) { 200296077Sadrian Option = opt_replace; 201296077Sadrian (void) strcpy (Filename, argv[optind]); 202296077Sadrian } else { 203296077Sadrian usage("file name must be specified for replace"); 204296077Sadrian } 205296077Sadrian } 206296077Sadrian 207296077Sadrian if (Option == opt_replace) { 208296077Sadrian /* we have to open the file here because we're going to 209296077Sadrian * chdir(2) into /var/cron before we get around to 210296077Sadrian * reading the file. 211296077Sadrian */ 212296077Sadrian if (!strcmp(Filename, "-")) { 213296077Sadrian NewCrontab = stdin; 214296077Sadrian } else { 215296077Sadrian /* relinquish the setuid status of the binary during 216296077Sadrian * the open, lest nonroot users read files they should 217296077Sadrian * not be able to read. we can't use access() here 218296077Sadrian * since there's a race condition. thanks go out to 219296077Sadrian * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting 220296077Sadrian * the race. 221296077Sadrian */ 222296077Sadrian 223296077Sadrian if (swap_uids() < OK) { 224296077Sadrian perror("swapping uids"); 225296077Sadrian exit(ERROR_EXIT); 226296077Sadrian } 227296077Sadrian if (!(NewCrontab = fopen(Filename, "r"))) { 228296077Sadrian perror(Filename); 229296077Sadrian exit(ERROR_EXIT); 230296077Sadrian } 231296077Sadrian if (swap_uids() < OK) { 232296077Sadrian perror("swapping uids back"); 233296077Sadrian exit(ERROR_EXIT); 234296077Sadrian } 235296077Sadrian } 236296077Sadrian } 237296077Sadrian 238296077Sadrian Debug(DMISC, ("user=%s, file=%s, option=%s\n", 239296077Sadrian User, Filename, Options[(int)Option])) 240296077Sadrian} 241296077Sadrian 242296077Sadrian 243296077Sadrianstatic void 244296077Sadrianlist_cmd() { 245296077Sadrian char n[MAX_FNAME]; 246296077Sadrian FILE *f; 247296077Sadrian int ch; 248296077Sadrian 249296077Sadrian log_it(RealUser, Pid, "LIST", User); 250296077Sadrian (void) sprintf(n, CRON_TAB(User)); 251296077Sadrian if (!(f = fopen(n, "r"))) { 252296077Sadrian if (errno == ENOENT) 253296077Sadrian fprintf(stderr, "no crontab for %s\n", User); 254296077Sadrian else 255296077Sadrian perror(n); 256296077Sadrian exit(ERROR_EXIT); 257296077Sadrian } 258296077Sadrian 259296077Sadrian /* file is open. copy to stdout, close. 260296077Sadrian */ 261296077Sadrian Set_LineNum(1) 262296077Sadrian while (EOF != (ch = get_char(f))) 263296077Sadrian putchar(ch); 264296077Sadrian fclose(f); 265296077Sadrian} 266296077Sadrian 267296077Sadrian 268296077Sadrianstatic void 269296077Sadriandelete_cmd() { 270296077Sadrian char n[MAX_FNAME]; 271296077Sadrian 272296077Sadrian log_it(RealUser, Pid, "DELETE", User); 273296077Sadrian (void) sprintf(n, CRON_TAB(User)); 274296077Sadrian if (unlink(n)) { 275296077Sadrian if (errno == ENOENT) 276296077Sadrian fprintf(stderr, "no crontab for %s\n", User); 277296077Sadrian else 278296077Sadrian perror(n); 279296077Sadrian exit(ERROR_EXIT); 280296077Sadrian } 281296077Sadrian poke_daemon(); 282296077Sadrian} 283296077Sadrian 284296077Sadrian 285296077Sadrianstatic void 286296077Sadriancheck_error(msg) 287296077Sadrian char *msg; 288296077Sadrian{ 289296077Sadrian CheckErrorCount++; 290296077Sadrian fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); 291296077Sadrian} 292296077Sadrian 293296077Sadrian 294296077Sadrianstatic void 295296077Sadrianedit_cmd() { 296296077Sadrian char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; 297296077Sadrian FILE *f; 298296077Sadrian int ch, t, x; 299296077Sadrian struct stat statbuf; 300296077Sadrian time_t mtime; 301296077Sadrian WAIT_T waiter; 302296077Sadrian PID_T pid, xpid; 303296077Sadrian 304296077Sadrian log_it(RealUser, Pid, "BEGIN EDIT", User); 305296077Sadrian (void) sprintf(n, CRON_TAB(User)); 306296077Sadrian if (!(f = fopen(n, "r"))) { 307296077Sadrian if (errno != ENOENT) { 308296077Sadrian perror(n); 309296077Sadrian exit(ERROR_EXIT); 310296077Sadrian } 311296077Sadrian fprintf(stderr, "no crontab for %s - using an empty one\n", 312296077Sadrian User); 313296077Sadrian if (!(f = fopen("/dev/null", "r"))) { 314296077Sadrian perror("/dev/null"); 315296077Sadrian exit(ERROR_EXIT); 316296077Sadrian } 317296077Sadrian } 318296077Sadrian 319296077Sadrian (void) sprintf(Filename, "/tmp/crontab.%d", Pid); 320296077Sadrian if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) { 321296077Sadrian perror(Filename); 322296077Sadrian goto fatal; 323296077Sadrian } 324296077Sadrian#ifdef HAS_FCHOWN 325296077Sadrian if (fchown(t, getuid(), getgid()) < 0) { 326296077Sadrian#else 327296077Sadrian if (chown(Filename, getuid(), getgid()) < 0) { 328296077Sadrian#endif 329296077Sadrian perror("fchown"); 330296077Sadrian goto fatal; 331296077Sadrian } 332296077Sadrian if (!(NewCrontab = fdopen(t, "w"))) { 333296077Sadrian perror("fdopen"); 334296077Sadrian goto fatal; 335296077Sadrian } 336296077Sadrian 337296077Sadrian Set_LineNum(1) 338296077Sadrian 339296077Sadrian /* ignore the top few comments since we probably put them there. 340296077Sadrian */ 341296077Sadrian for (x = 0; x < NHEADER_LINES; x++) { 342296077Sadrian ch = get_char(f); 343296077Sadrian if (EOF == ch) 344296077Sadrian break; 345296077Sadrian if ('#' != ch) { 346296077Sadrian putc(ch, NewCrontab); 347296077Sadrian break; 348296077Sadrian } 349296077Sadrian while (EOF != (ch = get_char(f))) 350296077Sadrian if (ch == '\n') 351296077Sadrian break; 352296077Sadrian if (EOF == ch) 353296077Sadrian break; 354296077Sadrian } 355296077Sadrian 356296077Sadrian /* copy the rest of the crontab (if any) to the temp file. 357296077Sadrian */ 358296077Sadrian if (EOF != ch) 359296077Sadrian while (EOF != (ch = get_char(f))) 360296077Sadrian putc(ch, NewCrontab); 361296077Sadrian fclose(f); 362296077Sadrian if (fclose(NewCrontab)) { 363296077Sadrian perror(Filename); 364296077Sadrian exit(ERROR_EXIT); 365296077Sadrian } 366296077Sadrian again: 367296077Sadrian if (stat(Filename, &statbuf) < 0) { 368296077Sadrian perror("stat"); 369296077Sadrian fatal: unlink(Filename); 370296077Sadrian exit(ERROR_EXIT); 371296077Sadrian } 372296077Sadrian mtime = statbuf.st_mtime; 373296077Sadrian 374296077Sadrian if ((!(editor = getenv("VISUAL"))) 375296077Sadrian && (!(editor = getenv("EDITOR"))) 376296077Sadrian ) { 377296077Sadrian editor = EDITOR; 378296077Sadrian } 379296077Sadrian 380296077Sadrian /* we still have the file open. editors will generally rewrite the 381296077Sadrian * original file rather than renaming/unlinking it and starting a 382296077Sadrian * new one; even backup files are supposed to be made by copying 383296077Sadrian * rather than by renaming. if some editor does not support this, 384296077Sadrian * then don't use it. the security problems are more severe if we 385296077Sadrian * close and reopen the file around the edit. 386296077Sadrian */ 387296077Sadrian 388 switch (pid = fork()) { 389 case -1: 390 perror("fork"); 391 goto fatal; 392 case 0: 393 /* child */ 394 if (setuid(getuid()) < 0) { 395 perror("setuid(getuid())"); 396 exit(ERROR_EXIT); 397 } 398 if (chdir("/tmp") < 0) { 399 perror("chdir(/tmp)"); 400 exit(ERROR_EXIT); 401 } 402 if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) { 403 fprintf(stderr, "%s: editor or filename too long\n", 404 ProgramName); 405 exit(ERROR_EXIT); 406 } 407 execlp(editor, editor, Filename, NULL); 408 perror(editor); 409 exit(ERROR_EXIT); 410 /*NOTREACHED*/ 411 default: 412 /* parent */ 413 break; 414 } 415 416 /* parent */ 417 { 418 void (*f[4])(); 419 f[0] = signal(SIGHUP, SIG_IGN); 420 f[1] = signal(SIGINT, SIG_IGN); 421 f[2] = signal(SIGTERM, SIG_IGN); 422 xpid = wait(&waiter); 423 signal(SIGHUP, f[0]); 424 signal(SIGINT, f[1]); 425 signal(SIGTERM, f[2]); 426 } 427 if (xpid != pid) { 428 fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n", 429 ProgramName, xpid, pid, editor); 430 goto fatal; 431 } 432 if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { 433 fprintf(stderr, "%s: \"%s\" exited with status %d\n", 434 ProgramName, editor, WEXITSTATUS(waiter)); 435 goto fatal; 436 } 437 if (WIFSIGNALED(waiter)) { 438 fprintf(stderr, 439 "%s: \"%s\" killed; signal %d (%score dumped)\n", 440 ProgramName, editor, WTERMSIG(waiter), 441 WCOREDUMP(waiter) ?"" :"no "); 442 goto fatal; 443 } 444 if (stat(Filename, &statbuf) < 0) { 445 perror("stat"); 446 goto fatal; 447 } 448 if (mtime == statbuf.st_mtime) { 449 fprintf(stderr, "%s: no changes made to crontab\n", 450 ProgramName); 451 goto remove; 452 } 453 fprintf(stderr, "%s: installing new crontab\n", ProgramName); 454 if (!(NewCrontab = fopen(Filename, "r"))) { 455 perror(Filename); 456 goto fatal; 457 } 458 switch (replace_cmd()) { 459 case 0: 460 break; 461 case -1: 462 for (;;) { 463 printf("Do you want to retry the same edit? "); 464 fflush(stdout); 465 q[0] = '\0'; 466 (void) fgets(q, sizeof q, stdin); 467 switch (islower(q[0]) ? q[0] : tolower(q[0])) { 468 case 'y': 469 goto again; 470 case 'n': 471 goto abandon; 472 default: 473 fprintf(stderr, "Enter Y or N\n"); 474 } 475 } 476 /*NOTREACHED*/ 477 case -2: 478 abandon: 479 fprintf(stderr, "%s: edits left in %s\n", 480 ProgramName, Filename); 481 goto done; 482 default: 483 fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n"); 484 goto fatal; 485 } 486 remove: 487 unlink(Filename); 488 done: 489 log_it(RealUser, Pid, "END EDIT", User); 490} 491 492 493/* returns 0 on success 494 * -1 on syntax error 495 * -2 on install error 496 */ 497static int 498replace_cmd() { 499 char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; 500 FILE *tmp; 501 int ch, eof; 502 entry *e; 503 time_t now = time(NULL); 504 char **envp = env_init(); 505 506 (void) sprintf(n, "tmp.%d", Pid); 507 (void) sprintf(tn, CRON_TAB(n)); 508 if (!(tmp = fopen(tn, "w+"))) { 509 perror(tn); 510 return (-2); 511 } 512 513 /* write a signature at the top of the file. 514 * 515 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. 516 */ 517 fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); 518 fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); 519 fprintf(tmp, "# (Cron version -- %s)\n", rcsid); 520 521 /* copy the crontab to the tmp 522 */ 523 Set_LineNum(1) 524 while (EOF != (ch = get_char(NewCrontab))) 525 putc(ch, tmp); 526 fclose(NewCrontab); 527 ftruncate(fileno(tmp), ftell(tmp)); 528 fflush(tmp); rewind(tmp); 529 530 if (ferror(tmp)) { 531 fprintf(stderr, "%s: error while writing new crontab to %s\n", 532 ProgramName, tn); 533 fclose(tmp); unlink(tn); 534 return (-2); 535 } 536 537 /* check the syntax of the file being installed. 538 */ 539 540 /* BUG: was reporting errors after the EOF if there were any errors 541 * in the file proper -- kludged it by stopping after first error. 542 * vix 31mar87 543 */ 544 Set_LineNum(1 - NHEADER_LINES) 545 CheckErrorCount = 0; eof = FALSE; 546 while (!CheckErrorCount && !eof) { 547 switch (load_env(envstr, tmp)) { 548 case ERR: 549 eof = TRUE; 550 break; 551 case FALSE: 552 e = load_entry(tmp, check_error, pw, envp); 553 if (e) 554 free(e); 555 break; 556 case TRUE: 557 break; 558 } 559 } 560 561 if (CheckErrorCount != 0) { 562 fprintf(stderr, "errors in crontab file, can't install.\n"); 563 fclose(tmp); unlink(tn); 564 return (-1); 565 } 566 567#ifdef HAS_FCHOWN 568 if (fchown(fileno(tmp), ROOT_UID, -1) < OK) 569#else 570 if (chown(tn, ROOT_UID, -1) < OK) 571#endif 572 { 573 perror("chown"); 574 fclose(tmp); unlink(tn); 575 return (-2); 576 } 577 578#ifdef HAS_FCHMOD 579 if (fchmod(fileno(tmp), 0600) < OK) 580#else 581 if (chmod(tn, 0600) < OK) 582#endif 583 { 584 perror("chown"); 585 fclose(tmp); unlink(tn); 586 return (-2); 587 } 588 589 if (fclose(tmp) == EOF) { 590 perror("fclose"); 591 unlink(tn); 592 return (-2); 593 } 594 595 (void) sprintf(n, CRON_TAB(User)); 596 if (rename(tn, n)) { 597 fprintf(stderr, "%s: error renaming %s to %s\n", 598 ProgramName, tn, n); 599 perror("rename"); 600 unlink(tn); 601 return (-2); 602 } 603 log_it(RealUser, Pid, "REPLACE", User); 604 605 poke_daemon(); 606 607 return (0); 608} 609 610 611static void 612poke_daemon() { 613#ifdef USE_UTIMES 614 struct timeval tvs[2]; 615 struct timezone tz; 616 617 (void) gettimeofday(&tvs[0], &tz); 618 tvs[1] = tvs[0]; 619 if (utimes(SPOOL_DIR, tvs) < OK) { 620 fprintf(stderr, "crontab: can't update mtime on spooldir\n"); 621 perror(SPOOL_DIR); 622 return; 623 } 624#else 625 if (utime(SPOOL_DIR, NULL) < OK) { 626 fprintf(stderr, "crontab: can't update mtime on spooldir\n"); 627 perror(SPOOL_DIR); 628 return; 629 } 630#endif /*USE_UTIMES*/ 631} 632