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