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