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