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.5 (Berkeley) 11/26/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/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 exit(1); 173 } 174 175 /* 176 * Grab the 4-line header out of the spoolfile. 177 */ 178 if ( 179 (fscanf(infile,"# owner: %127s%*[^\n]\n",owner) != 1) || 180 (fscanf(infile,"# jobname: %127s%*[^\n]\n",jobname) != 1) || 181 (fscanf(infile,"# shell: %3s%*[^\n]\n",shell) != 1) || 182 (fscanf(infile,"# notify by mail: %3s%*[^\n]\n",mailvar) != 1) 183 ) { 184 fprintf(stderr, "%s: bad spool header\n", spoolfile); 185 exit(1); 186 } 187 188 /* 189 * Check to see if we should send mail to the owner. 190 */ 191 notifybymail = (strcmp(mailvar, "yes") == 0); 192 fclose(infile); 193 194 /* 195 * Change the ownership of the spoolfile from "daemon" to the owner 196 * of the job. 197 */ 198 pwdbuf = getpwnam(owner); 199 if (pwdbuf == NULL) { 200 fprintf(stderr, "%s: could not find owner in passwd file\n", 201 spoolfile); 202 exit(1); 203 } 204 if (chown(spoolfile,pwdbuf->pw_uid,pwdbuf->pw_gid) == -1) { 205 perror(spoolfile); 206 exit(1); 207 } 208 209 /* 210 * Move the spoolfile to the directory where jobs are run from and 211 * then move into that directory. 212 */ 213 sprintf(runfile,"%s/%s",PASTDIR,spoolfile); 214 rename(spoolfile, runfile); 215 chdir(PASTDIR); 216 217 /* 218 * Create a temporary file where we will redirect errors to. 219 * Just to make sure we've got a unique file, we'll run an "access" 220 * check on the file. 221 */ 222 for (i = 0; i <= 1000; i += 2) { 223 sprintf(errfile,"%s/at.err%d",TMPDIR,(getpid() + i)); 224 225 if (access(errfile, F_OK)) 226 break; 227 228 if (i == 1000) { 229 fprintf(stderr, "couldn't create errorfile.\n"); 230 exit(1); 231 } 232 } 233 234 /* 235 * Get the stats of the job being run. 236 */ 237 if (stat(runfile, &jobbuf) == -1) { 238 perror(runfile); 239 exit(1); 240 } 241 242 /* 243 * Fork another child that will run the job. 244 */ 245 if (pid = fork()) { 246 247 /* 248 * If the child fails, save the job so that it gets 249 * rerun the next time "atrun" is executed and then exit. 250 */ 251 if (pid == -1) { 252 chdir(ATDIR); 253 rename(runfile, spoolfile); 254 exit(1); 255 } 256 257 /* 258 * Wait for the child to terminate. 259 */ 260 wait((int *)0); 261 262 /* 263 * Get the stats of the error file and determine the exit 264 * status of the child. We assume that if there is anything 265 * in the error file then the job ran into some errors. 266 */ 267 if (stat(errfile,&errbuf) != 0) { 268 perror(errfile); 269 exit(1); 270 } 271 exitstatus = ((errbuf.st_size == 0) ? NORMAL : ABNORMAL); 272 273 /* If errors occurred, then we send mail to the owner 274 * telling him/her that we ran into trouble. 275 * 276 * (NOTE: this could easily be modified so that if any 277 * errors occurred while running a job, mail is sent regard- 278 * less of whether the -m flag was set or not. 279 * 280 * i.e. rather than: 281 * 282 * "if (notifybymail)" use 283 * use: 284 * 285 * "if ((exitstatus == ABNORMAL) || (notifybymail))" 286 * 287 * It's up to you if you want to implement this. 288 * 289 */ 290 if (exitstatus == ABNORMAL || notifybymail) 291 sendmailto(getname(jobbuf.st_uid),jobname,exitstatus); 292 293 /* 294 * Remove the errorfile and the jobfile. 295 */ 296 if (unlink(errfile) == -1) 297 perror(errfile); 298 if (unlink(runfile) == -1) 299 perror(runfile); 300 301 exit(0); 302 } 303 304 /* 305 * HERE'S WHERE WE SET UP AND FORK THE SHELL. 306 */ 307 308 /* 309 * Run the job as the owner of the jobfile 310 */ 311 #ifdef notdef 312 /* This is no longer needed with the new, stripped-down quota system */ 313 quota(Q_SETUID,jobbuf.st_uid,0,0); 314 #endif 315 setgid(jobbuf.st_gid); 316 initgroups(getname(jobbuf.st_uid),jobbuf.st_gid); 317 setuid(jobbuf.st_uid); 318 319 /* 320 * Close all open files so that we can reopen a temporary file 321 * for stdout and sterr. 322 */ 323 for (i = getdtablesize(); --i >= 0;) 324 close(i); 325 326 /* 327 * Reposition stdin, stdout, and stderr. 328 * 329 * stdin = /dev/null 330 * stout = /dev/null 331 * stderr = /tmp/at.err{pid} 332 * 333 */ 334 open("/dev/null", 0); 335 open("/dev/null", 1); 336 open(errfile,O_CREAT|O_WRONLY,00644); 337 338 /* 339 * Now we fork the shell. 340 * 341 * See if the shell is in /bin 342 */ 343 sprintf(whichshell,"/bin/%s",shell); 344 execl(whichshell,shell,runfile, 0); 345 346 /* 347 * If not in /bin, look for the shell in /usr/bin. 348 */ 349 sprintf(whichshell,"/usr/bin/%s",shell); 350 execl(whichshell,shell,runfile, 0); 351 352 /* 353 * If not in /bin, look for the shell in /usr/new. 354 */ 355 sprintf(whichshell,"/usr/new/%s",shell); 356 execl(whichshell,shell,runfile, 0); 357 358 /* 359 * If we don't succeed by now, we're really having troubles, 360 * so we'll send the owner some mail. 361 */ 362 fprintf(stderr, "%s: Can't execl shell\n",shell); 363 exit(1); 364 } 365 366 /* 367 * Send mail to the owner of the job. 368 */ 369 sendmailto(user,jobname,exitstatus) 370 char *user; 371 char *jobname; 372 int exitstatus; 373 { 374 int ch; /* scratch variable */ 375 char mailtouser[100]; /* the process we use to send mail */ 376 FILE *mailptr; /* I/O stream to the mail process */ 377 FILE *errptr; /* I/O stream to file containing error 378 messages */ 379 FILE *popen(); /* initiate I/O to a process */ 380 381 382 /* 383 * Create the full name for the mail process. 384 */ 385 sprintf(mailtouser,"%s %s",MAILER, user); 386 387 /* 388 * Open a stream to the mail process. 389 */ 390 if ((mailptr = popen(mailtouser,"w")) == NULL) { 391 perror(MAILER); 392 exit(1); 393 } 394 395 /* 396 * Send the letter. If the job exited normally, just send a 397 * quick letter notifying the owner that everthing went ok. 398 */ 399 if (exitstatus == NORMAL) { 400 fprintf(mailptr,"Your job \"%s\" was run without ",jobname); 401 fprintf(mailptr,"any errors.\n"); 402 } 403 404 /* 405 * If the job exited abnormally, send a letter notifying the user 406 * that the job didn't run proberly. Also, send a copy of the errors 407 * that occurred to the user. 408 */ 409 else { 410 if (exitstatus == ABNORMAL) { 411 412 /* 413 * Write the intro to the letter. 414 */ 415 fprintf(mailptr,"\n\nThe job you submitted to at, "); 416 fprintf(mailptr,"\"%s\", ",jobname); 417 fprintf(mailptr,"exited abnormally.\nA list of the "); 418 fprintf(mailptr," errors that occurred follows:\n\n\n"); 419 420 /* 421 * Open the file containing a log of the errors that 422 * occurred. 423 */ 424 if ((errptr = fopen(errfile,"r")) == NULL) { 425 perror(errfile); 426 exit(1); 427 } 428 429 /* 430 * Send the copy of the errors to the owner. 431 */ 432 fputc('\t',mailptr); 433 while ((ch = fgetc(errptr)) != EOF) { 434 fputc(ch,mailptr); 435 if (ch == (int)'\n') 436 fputc('\t',mailptr); 437 } 438 fclose(errptr); 439 } 440 } 441 442 /* 443 * Sign the letter. 444 */ 445 fprintf(mailptr,"\n\n-----------------\n"); 446 fprintf(mailptr,"The Atrun Program\n"); 447 448 /* 449 * Close the stream to the mail process. 450 */ 451 pclose(mailptr); 452 return; 453 } 454 455 /* 456 * Do we want to include a file in the job queue? (used by "scandir") 457 * We are looking for files whose "value" (its name) is less than or 458 * equal to the time it is right now (represented by "nowtime"). 459 * We'll only consider files with three dots in their name since these 460 * are the only files that represent jobs to be run. 461 */ 462 should_be_run(direntry) 463 struct direct *direntry; 464 { 465 int numdot = 0; /* number of dots found in a filename */ 466 char *filename; /* pointer for scanning a filename */ 467 468 469 filename = direntry->d_name; 470 471 /* 472 * Count the number of dots found in the directory entry. 473 */ 474 while (*filename) 475 numdot += (*(filename++) == '.'); 476 477 /* 478 * If the directory entry doesn't represent a job, just return a 0. 479 */ 480 if (numdot != 3) 481 return(0); 482 483 /* 484 * If a directory entry represents a job, determine if it's time to 485 * run it. 486 */ 487 return(strncmp(direntry->d_name, nowtime,11) <= 0); 488 } 489 490 /* 491 * Record the last time that "atrun" was run. 492 */ 493 updatetime() 494 { 495 496 struct timeval time; /* number of seconds since 1/1/70 */ 497 struct timezone zone; /* time zone we're in (NOT USED) */ 498 FILE *lastimefile; /* file where recored is kept */ 499 500 /* 501 * Get the time of day. 502 */ 503 if (gettimeofday(&time,&zone) < 0) { 504 perror("gettimeofday"); 505 exit(1); 506 } 507 508 /* 509 * Open the record file. 510 */ 511 if ((lastimefile = fopen(LASTFILE, "w")) == NULL) { 512 fprintf(stderr, "can't update lastfile: "); 513 perror(LASTFILE); 514 exit(1); 515 } 516 517 /* 518 * Record the last update time (in seconds since 1/1/70). 519 */ 520 fprintf(lastimefile, "%d\n", (u_long) time.tv_sec); 521 522 /* 523 * Close the record file. 524 */ 525 fclose(lastimefile); 526 } 527 528 /* 529 * Get the full login name of a person using his/her user id. 530 */ 531 char * 532 getname(uid) 533 int uid; 534 { 535 struct passwd *pwdinfo; /* password info structure */ 536 537 538 if ((pwdinfo = getpwuid(uid)) == 0) { 539 perror(uid); 540 exit(1); 541 } 542 return(pwdinfo->pw_name); 543 } 544