1: /* 2: * Copyright (c) 1983 Regents of the University of California. 3: * All rights reserved. The Berkeley software License Agreement 4: * specifies the terms and conditions for redistribution. 5: */ 6: 7: #ifndef lint 8: char copyright[] = 9: "@(#) Copyright (c) 1983 Regents of the University of California.\n\ 10: All rights reserved.\n"; 11: #endif not lint 12: 13: #ifndef lint 14: static char sccsid[] = "@(#)atrun.c 5.4 (Berkeley) 5/28/86"; 15: #endif not lint 16: 17: /* 18: * Synopsis: atrun 19: * 20: * 21: * Run jobs created by at(1) 22: * 23: * 24: * Modifications by: Steve Wall 25: * Computer Systems Research Group 26: * University of California @ Berkeley 27: * 28: */ 29: # include <stdio.h> 30: # include <sys/types.h> 31: # include <sys/dir.h> 32: # include <sys/file.h> 33: # include <sys/time.h> 34: # include <sys/param.h> 35: #ifdef notdef 36: # include <sys/quota.h> 37: #endif 38: # include <sys/stat.h> 39: # include <pwd.h> 40: 41: # define ATDIR "/usr/spool/at" /* spooling area */ 42: # define TMPDIR "/tmp" /* area for temporary files */ 43: # define MAILER "/bin/mail" /* program to use for sending 44: mail */ 45: # define NORMAL 0 /* job exited normally */ 46: # define ABNORMAL 1 /* job exited abnormally */ 47: # define PASTDIR "/usr/spool/at/past" /* area to run jobs from */ 48: # define LASTFILE "/usr/spool/at/lasttimedone" /* update time file */ 49: 50: 51: char nowtime[11]; /* time it is right now (yy.ddd.hhmm) */ 52: char errfile[25]; /* file where we redirect errors to */ 53: 54: 55: main(argc, argv) 56: char **argv; 57: { 58: 59: int i; /* for loop index */ 60: int numjobs; /* number of jobs to be run */ 61: int should_be_run(); /* should a job be run? */ 62: struct direct **jobqueue; /* queue of jobs to be run */ 63: 64: 65: /* 66: * Move to the spooling area. 67: */ 68: chdir(ATDIR); 69: 70: /* 71: * Create a filename that represents the time it is now. This is used 72: * to determine if the execution time for a job has arrived. 73: */ 74: makenowtime(nowtime); 75: 76: /* 77: * Create a queue of the jobs that should be run. 78: */ 79: if ((numjobs = scandir(".",&jobqueue,should_be_run, 0)) < 0) { 80: perror(ATDIR); 81: exit(1); 82: } 83: 84: /* 85: * If there are jobs to be run, run them. 86: */ 87: if (numjobs > 0) { 88: for (i = 0; i < numjobs; ++i) { 89: run(jobqueue[i]->d_name); 90: } 91: } 92: 93: /* 94: * Record the last update time. 95: */ 96: updatetime(); 97: 98: } 99: 100: /* 101: * Create a string with the syntax yy.ddd.hhmm that represents the 102: * time it is right now. This string is used to determine whether a 103: * job should be run. 104: */ 105: makenowtime(nowtime) 106: char *nowtime; 107: { 108: struct tm *now; /* broken down representation of the 109: time it is right now */ 110: struct timeval time; /* number of seconds since 1/1/70 */ 111: struct timezone zone; /* time zone we're in (NOT USED) */ 112: 113: /* 114: * Get the time of day. 115: */ 116: if (gettimeofday(&time,&zone) < 0) { 117: perror("gettimeofday"); 118: exit(1); 119: } 120: 121: /* 122: * Get a broken down representation of the time it is right now. 123: */ 124: now = localtime(&time.tv_sec); 125: 126: /* 127: * Create a string to be used in determining whether or not a job 128: * should be run. The syntax is yy.ddd.hhmm . 129: */ 130: sprintf(nowtime,"%d.%03d.%02d%02d",now->tm_year, 131: now->tm_yday, 132: now->tm_hour, 133: now->tm_min); 134: return; 135: } 136: 137: /* 138: * Run a job. 139: */ 140: run(spoolfile) 141: char *spoolfile; 142: { 143: int i; /* scratch variable */ 144: int pid; /* process id of forked shell */ 145: int exitstatus; /* exit status of the job */ 146: int notifybymail; /* should we notify the owner of the 147: job after the job is run? */ 148: char shell[4]; /* shell to run the job under */ 149: char *getname(); /* get a uname from using a uid */ 150: char mailvar[4]; /* send mail variable ("yes" or "no") */ 151: char runfile[100]; /* file sent to forked shell for exec- 152: ution */ 153: char owner[128]; /* owner of job we're going to run */ 154: char jobname[128]; /* name of job we're going to run */ 155: char whichshell[100]; /* which shell should we fork off? */ 156: struct passwd *pwdbuf; /* password info of the owner of job */ 157: struct stat errbuf; /* stats on error file */ 158: struct stat jobbuf; /* stats on job file */ 159: FILE *infile; /* I/O stream to spoolfile */ 160: 161: 162: /* 163: * First we fork a child so that the main can run other jobs. 164: */ 165: if (pid = fork()) 166: return; 167: 168: /* 169: * Open the spoolfile. 170: */ 171: if ((infile = fopen(spoolfile,"r")) == NULL) { 172: perror(spoolfile); 173: exit(1); 174: } 175: 176: /* 177: * Grab the 4-line header out of the spoolfile. 178: */ 179: if ( 180: (fscanf(infile,"# owner: %127s%*[^\n]\n",owner) != 1) || 181: (fscanf(infile,"# jobname: %127s%*[^\n]\n",jobname) != 1) || 182: (fscanf(infile,"# shell: %3s%*[^\n]\n",shell) != 1) || 183: (fscanf(infile,"# notify by mail: %3s%*[^\n]\n",mailvar) != 1) 184: ) { 185: fprintf(stderr, "%s: bad spool header\n", spoolfile); 186: exit(1); 187: } 188: 189: /* 190: * Check to see if we should send mail to the owner. 191: */ 192: notifybymail = (strcmp(mailvar, "yes") == 0); 193: fclose(infile); 194: 195: /* 196: * Change the ownership of the spoolfile from "daemon" to the owner 197: * of the job. 198: */ 199: pwdbuf = getpwnam(owner); 200: if (pwdbuf == NULL) { 201: fprintf(stderr, "%s: could not find owner in passwd file\n", 202: spoolfile); 203: exit(1); 204: } 205: if (chown(spoolfile,pwdbuf->pw_uid,pwdbuf->pw_gid) == -1) { 206: perror(spoolfile); 207: exit(1); 208: } 209: 210: /* 211: * Move the spoolfile to the directory where jobs are run from and 212: * then move into that directory. 213: */ 214: sprintf(runfile,"%s/%s",PASTDIR,spoolfile); 215: rename(spoolfile, runfile); 216: chdir(PASTDIR); 217: 218: /* 219: * Create a temporary file where we will redirect errors to. 220: * Just to make sure we've got a unique file, we'll run an "access" 221: * check on the file. 222: */ 223: for (i = 0; i <= 1000; i += 2) { 224: sprintf(errfile,"%s/at.err%d",TMPDIR,(getpid() + i)); 225: 226: if (access(errfile, F_OK)) 227: break; 228: 229: if (i == 1000) { 230: fprintf(stderr, "couldn't create errorfile.\n"); 231: exit(1); 232: } 233: } 234: 235: /* 236: * Get the stats of the job being run. 237: */ 238: if (stat(runfile, &jobbuf) == -1) { 239: perror(runfile); 240: exit(1); 241: } 242: 243: /* 244: * Fork another child that will run the job. 245: */ 246: if (pid = fork()) { 247: 248: /* 249: * If the child fails, save the job so that it gets 250: * rerun the next time "atrun" is executed and then exit. 251: */ 252: if (pid == -1) { 253: chdir(ATDIR); 254: rename(runfile, spoolfile); 255: exit(1); 256: } 257: 258: /* 259: * Wait for the child to terminate. 260: */ 261: wait((int *)0); 262: 263: /* 264: * Get the stats of the error file and determine the exit 265: * status of the child. We assume that if there is anything 266: * in the error file then the job ran into some errors. 267: */ 268: if (stat(errfile,&errbuf) != 0) { 269: perror(errfile); 270: exit(1); 271: } 272: exitstatus = ((errbuf.st_size == 0) ? NORMAL : ABNORMAL); 273: 274: /* If errors occurred, then we send mail to the owner 275: * telling him/her that we ran into trouble. 276: * 277: * (NOTE: this could easily be modified so that if any 278: * errors occurred while running a job, mail is sent regard- 279: * less of whether the -m flag was set or not. 280: * 281: * i.e. rather than: 282: * 283: * "if (notifybymail)" use 284: * use: 285: * 286: * "if ((exitstatus == ABNORMAL) || (notifybymail))" 287: * 288: * It's up to you if you want to implement this. 289: * 290: */ 291: if (exitstatus == ABNORMAL || notifybymail) 292: sendmailto(getname(jobbuf.st_uid),jobname,exitstatus); 293: 294: /* 295: * Remove the errorfile and the jobfile. 296: */ 297: if (unlink(errfile) == -1) 298: perror(errfile); 299: if (unlink(runfile) == -1) 300: perror(runfile); 301: 302: exit(0); 303: } 304: 305: /* 306: * HERE'S WHERE WE SET UP AND FORK THE SHELL. 307: */ 308: 309: /* 310: * Run the job as the owner of the jobfile 311: */ 312: #ifdef notdef 313: /* This is no longer needed with the new, stripped-down quota system */ 314: quota(Q_SETUID,jobbuf.st_uid,0,0); 315: #endif 316: setgid(jobbuf.st_gid); 317: initgroups(getname(jobbuf.st_uid),jobbuf.st_gid); 318: setuid(jobbuf.st_uid); 319: 320: /* 321: * Close all open files so that we can reopen a temporary file 322: * for stdout and sterr. 323: */ 324: for (i = getdtablesize(); --i >= 0;) 325: close(i); 326: 327: /* 328: * Reposition stdin, stdout, and stderr. 329: * 330: * stdin = /dev/null 331: * stout = /dev/null 332: * stderr = /tmp/at.err{pid} 333: * 334: */ 335: open("/dev/null", 0); 336: open("/dev/null", 1); 337: open(errfile,O_CREAT|O_WRONLY,00644); 338: 339: /* 340: * Now we fork the shell. 341: * 342: * See if the shell is in /bin 343: */ 344: sprintf(whichshell,"/bin/%s",shell); 345: execl(whichshell,shell,runfile, 0); 346: 347: /* 348: * If not in /bin, look for the shell in /usr/bin. 349: */ 350: sprintf(whichshell,"/usr/bin/%s",shell); 351: execl(whichshell,shell,runfile, 0); 352: 353: /* 354: * If not in /bin, look for the shell in /usr/new. 355: */ 356: sprintf(whichshell,"/usr/new/%s",shell); 357: execl(whichshell,shell,runfile, 0); 358: 359: /* 360: * If we don't succeed by now, we're really having troubles, 361: * so we'll send the owner some mail. 362: */ 363: fprintf(stderr, "%s: Can't execl shell\n",shell); 364: exit(1); 365: } 366: 367: /* 368: * Send mail to the owner of the job. 369: */ 370: sendmailto(user,jobname,exitstatus) 371: char *user; 372: char *jobname; 373: int exitstatus; 374: { 375: char ch; /* scratch variable */ 376: char mailtouser[100]; /* the process we use to send mail */ 377: FILE *mailptr; /* I/O stream to the mail process */ 378: FILE *errptr; /* I/O stream to file containing error 379: messages */ 380: FILE *popen(); /* initiate I/O to a process */ 381: 382: 383: /* 384: * Create the full name for the mail process. 385: */ 386: sprintf(mailtouser,"%s %s",MAILER, user); 387: 388: /* 389: * Open a stream to the mail process. 390: */ 391: if ((mailptr = popen(mailtouser,"w")) == NULL) { 392: perror(MAILER); 393: exit(1); 394: } 395: 396: /* 397: * Send the letter. If the job exited normally, just send a 398: * quick letter notifying the owner that everthing went ok. 399: */ 400: if (exitstatus == NORMAL) { 401: fprintf(mailptr,"Your job \"%s\" was run without ",jobname); 402: fprintf(mailptr,"any errors.\n"); 403: } 404: 405: /* 406: * If the job exited abnormally, send a letter notifying the user 407: * that the job didn't run proberly. Also, send a copy of the errors 408: * that occurred to the user. 409: */ 410: else { 411: if (exitstatus == ABNORMAL) { 412: 413: /* 414: * Write the intro to the letter. 415: */ 416: fprintf(mailptr,"\n\nThe job you submitted to at, "); 417: fprintf(mailptr,"\"%s\", ",jobname); 418: fprintf(mailptr,"exited abnormally.\nA list of the "); 419: fprintf(mailptr," errors that occurred follows:\n\n\n"); 420: 421: /* 422: * Open the file containing a log of the errors that 423: * occurred. 424: */ 425: if ((errptr = fopen(errfile,"r")) == NULL) { 426: perror(errfile); 427: exit(1); 428: } 429: 430: /* 431: * Send the copy of the errors to the owner. 432: */ 433: fputc('\t',mailptr); 434: while ((ch = fgetc(errptr)) != EOF) { 435: fputc(ch,mailptr); 436: if (ch == '\n') 437: fputc('\t',mailptr); 438: } 439: fclose(errptr); 440: } 441: } 442: 443: /* 444: * Sign the letter. 445: */ 446: fprintf(mailptr,"\n\n-----------------\n"); 447: fprintf(mailptr,"The Atrun Program\n"); 448: 449: /* 450: * Close the stream to the mail process. 451: */ 452: pclose(mailptr); 453: return; 454: } 455: 456: /* 457: * Do we want to include a file in the job queue? (used by "scandir") 458: * We are looking for files whose "value" (its name) is less than or 459: * equal to the time it is right now (represented by "nowtime"). 460: * We'll only consider files with three dots in their name since these 461: * are the only files that represent jobs to be run. 462: */ 463: should_be_run(direntry) 464: struct direct *direntry; 465: { 466: int numdot = 0; /* number of dots found in a filename */ 467: char *filename; /* pointer for scanning a filename */ 468: 469: 470: filename = direntry->d_name; 471: 472: /* 473: * Count the number of dots found in the directory entry. 474: */ 475: while (*filename) 476: numdot += (*(filename++) == '.'); 477: 478: /* 479: * If the directory entry doesn't represent a job, just return a 0. 480: */ 481: if (numdot != 3) 482: return(0); 483: 484: /* 485: * If a directory entry represents a job, determine if it's time to 486: * run it. 487: */ 488: return(strncmp(direntry->d_name, nowtime,11) <= 0); 489: } 490: 491: /* 492: * Record the last time that "atrun" was run. 493: */ 494: updatetime() 495: { 496: 497: struct timeval time; /* number of seconds since 1/1/70 */ 498: struct timezone zone; /* time zone we're in (NOT USED) */ 499: FILE *lastimefile; /* file where recored is kept */ 500: 501: /* 502: * Get the time of day. 503: */ 504: if (gettimeofday(&time,&zone) < 0) { 505: perror("gettimeofday"); 506: exit(1); 507: } 508: 509: /* 510: * Open the record file. 511: */ 512: if ((lastimefile = fopen(LASTFILE, "w")) == NULL) { 513: fprintf(stderr, "can't update lastfile: "); 514: perror(LASTFILE); 515: exit(1); 516: } 517: 518: /* 519: * Record the last update time (in seconds since 1/1/70). 520: */ 521: fprintf(lastimefile, "%d\n", (u_long) time.tv_sec); 522: 523: /* 524: * Close the record file. 525: */ 526: fclose(lastimefile); 527: } 528: 529: /* 530: * Get the full login name of a person using his/her user id. 531: */ 532: char * 533: getname(uid) 534: int uid; 535: { 536: struct passwd *pwdinfo; /* password info structure */ 537: 538: 539: if ((pwdinfo = getpwuid(uid)) == 0) { 540: perror(uid); 541: exit(1); 542: } 543: return(pwdinfo->pw_name); 544: }