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