1 /* 2 * Copyright (c) 1980 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 static char sccsid[] = "@(#)tape.c 5.12 (Berkeley) 02/23/91"; 9 #endif /* not lint */ 10 11 #include "dump.h" 12 #include <sys/types.h> 13 #include <sys/wait.h> 14 #include <errno.h> 15 #include <fcntl.h> 16 #include "pathnames.h" 17 18 char (*tblock)[TP_BSIZE]; /* pointer to malloc()ed buffer for tape */ 19 int writesize; /* size of malloc()ed buffer for tape */ 20 long lastspclrec = -1; /* tape block number of last written header */ 21 int trecno = 0; /* next record to write in current block */ 22 extern int ntrec; /* blocking factor on tape */ 23 extern int cartridge; 24 extern int read(), write(); 25 #ifdef RDUMP 26 extern char *host; 27 int rmtopen(), rmtwrite(); 28 void rmtclose(); 29 #endif RDUMP 30 31 int atomic(); 32 void doslave(), enslave(), flusht(), killall(); 33 34 /* 35 * Concurrent dump mods (Caltech) - disk block reading and tape writing 36 * are exported to several slave processes. While one slave writes the 37 * tape, the others read disk blocks; they pass control of the tape in 38 * a ring via flock(). The parent process traverses the filesystem and 39 * sends spclrec()'s and lists of daddr's to the slaves via pipes. 40 */ 41 struct req { /* instruction packets sent to slaves */ 42 daddr_t dblk; 43 int count; 44 } *req; 45 int reqsiz; 46 47 #define SLAVES 3 /* 1 slave writing, 1 reading, 1 for slack */ 48 int slavefd[SLAVES]; /* pipes from master to each slave */ 49 int slavepid[SLAVES]; /* used by killall() */ 50 int rotor; /* next slave to be instructed */ 51 int master; /* pid of master, for sending error signals */ 52 int tenths; /* length of tape used per block written */ 53 54 int 55 alloctape() 56 { 57 int pgoff = getpagesize() - 1; 58 59 writesize = ntrec * TP_BSIZE; 60 reqsiz = ntrec * sizeof(struct req); 61 /* 62 * CDC 92181's and 92185's make 0.8" gaps in 1600-bpi start/stop mode 63 * (see DEC TU80 User's Guide). The shorter gaps of 6250-bpi require 64 * repositioning after stopping, i.e, streaming mode, where the gap is 65 * variable, 0.30" to 0.45". The gap is maximal when the tape stops. 66 */ 67 tenths = writesize/density + (cartridge ? 16 : density == 625 ? 5 : 8); 68 /* 69 * Allocate tape buffer contiguous with the array of instruction 70 * packets, so flusht() can write them together with one write(). 71 * Align tape buffer on page boundary to speed up tape write(). 72 */ 73 req = (struct req *)malloc(reqsiz + writesize + pgoff); 74 if (req == NULL) 75 return(0); 76 tblock = (char (*)[TP_BSIZE]) (((long)&req[ntrec] + pgoff) &~ pgoff); 77 req = (struct req *)tblock - ntrec; 78 return(1); 79 } 80 81 82 void 83 taprec(dp) 84 char *dp; 85 { 86 req[trecno].dblk = (daddr_t)0; 87 req[trecno].count = 1; 88 *(union u_spcl *)(*tblock++) = *(union u_spcl *)dp; /* movc3 */ 89 lastspclrec = spcl.c_tapea; 90 trecno++; 91 spcl.c_tapea++; 92 if (trecno >= ntrec) 93 flusht(); 94 } 95 96 void 97 dmpblk(blkno, size) 98 daddr_t blkno; 99 int size; 100 { 101 int avail, tpblks, dblkno; 102 103 dblkno = fsbtodb(sblock, blkno); 104 tpblks = size >> tp_bshift; 105 while ((avail = MIN(tpblks, ntrec - trecno)) > 0) { 106 req[trecno].dblk = dblkno; 107 req[trecno].count = avail; 108 trecno += avail; 109 spcl.c_tapea += avail; 110 if (trecno >= ntrec) 111 flusht(); 112 dblkno += avail << (tp_bshift - dev_bshift); 113 tpblks -= avail; 114 } 115 } 116 117 int nogripe = 0; 118 119 void 120 tperror() 121 { 122 if (pipeout) { 123 msg("Tape write error on %s\n", tape); 124 quit("Cannot recover\n"); 125 /* NOTREACHED */ 126 } 127 msg("Tape write error %d feet into tape %d\n", asize/120L, tapeno); 128 broadcast("TAPE ERROR!\n"); 129 if (!query("Do you want to restart?")) 130 dumpabort(); 131 msg("This tape will rewind. After it is rewound,\n"); 132 msg("replace the faulty tape with a new one;\n"); 133 msg("this dump volume will be rewritten.\n"); 134 killall(); 135 nogripe = 1; 136 close_rewind(); 137 Exit(X_REWRITE); 138 } 139 140 void 141 sigpipe() 142 { 143 144 quit("Broken pipe\n"); 145 } 146 147 #ifdef RDUMP 148 /* 149 * compatibility routine 150 */ 151 void 152 tflush(i) 153 int i; 154 { 155 156 for (i = 0; i < ntrec; i++) 157 spclrec(); 158 } 159 #endif RDUMP 160 161 void 162 flusht() 163 { 164 int siz = (char *)tblock - (char *)req; 165 166 if (atomic(write, slavefd[rotor], req, siz) != siz) 167 quit("error writing command pipe: %s\n", strerror(errno)); 168 if (++rotor >= SLAVES) 169 rotor = 0; 170 tblock = (char (*)[TP_BSIZE]) &req[ntrec]; 171 trecno = 0; 172 asize += tenths; 173 blockswritten += ntrec; 174 if (!pipeout && asize > tsize) { 175 close_rewind(); 176 otape(); 177 } 178 timeest(); 179 } 180 181 void 182 trewind() 183 { 184 int f; 185 186 if (pipeout) 187 return; 188 for (f = 0; f < SLAVES; f++) 189 close(slavefd[f]); 190 while (wait((int *)NULL) >= 0) /* wait for any signals from slaves */ 191 /* void */; 192 msg("Tape rewinding\n"); 193 #ifdef RDUMP 194 if (host) { 195 rmtclose(); 196 while (rmtopen(tape, 0) < 0) 197 sleep(10); 198 rmtclose(); 199 return; 200 } 201 #endif RDUMP 202 close(to); 203 while ((f = open(tape, 0)) < 0) 204 sleep (10); 205 close(f); 206 } 207 208 void 209 close_rewind() 210 { 211 trewind(); 212 if (!nogripe) { 213 msg("Change Tapes: Mount tape #%d\n", tapeno+1); 214 broadcast("CHANGE TAPES!\7\7\n"); 215 } 216 while (!query("Is the new tape mounted and ready to go?")) 217 if (query("Do you want to abort?")) { 218 dumpabort(); 219 /*NOTREACHED*/ 220 } 221 } 222 223 /* 224 * We implement taking and restoring checkpoints on the tape level. 225 * When each tape is opened, a new process is created by forking; this 226 * saves all of the necessary context in the parent. The child 227 * continues the dump; the parent waits around, saving the context. 228 * If the child returns X_REWRITE, then it had problems writing that tape; 229 * this causes the parent to fork again, duplicating the context, and 230 * everything continues as if nothing had happened. 231 */ 232 233 void 234 otape() 235 { 236 int parentpid; 237 int childpid; 238 int status; 239 int waitpid; 240 sig_t interrupt; 241 int blks, i; 242 243 interrupt = signal(SIGINT, SIG_IGN); 244 parentpid = getpid(); 245 246 restore_check_point: 247 (void)signal(SIGINT, interrupt); 248 /* 249 * All signals are inherited... 250 */ 251 childpid = fork(); 252 if (childpid < 0) { 253 msg("Context save fork fails in parent %d\n", parentpid); 254 Exit(X_ABORT); 255 } 256 if (childpid != 0) { 257 /* 258 * PARENT: 259 * save the context by waiting 260 * until the child doing all of the work returns. 261 * don't catch the interrupt 262 */ 263 signal(SIGINT, SIG_IGN); 264 #ifdef TDEBUG 265 msg("Tape: %d; parent process: %d child process %d\n", 266 tapeno+1, parentpid, childpid); 267 #endif TDEBUG 268 while ((waitpid = wait(&status)) != childpid) 269 msg("Parent %d waiting for child %d has another child %d return\n", 270 parentpid, childpid, waitpid); 271 if (status & 0xFF) { 272 msg("Child %d returns LOB status %o\n", 273 childpid, status&0xFF); 274 } 275 status = (status >> 8) & 0xFF; 276 #ifdef TDEBUG 277 switch(status) { 278 case X_FINOK: 279 msg("Child %d finishes X_FINOK\n", childpid); 280 break; 281 case X_ABORT: 282 msg("Child %d finishes X_ABORT\n", childpid); 283 break; 284 case X_REWRITE: 285 msg("Child %d finishes X_REWRITE\n", childpid); 286 break; 287 default: 288 msg("Child %d finishes unknown %d\n", 289 childpid, status); 290 break; 291 } 292 #endif TDEBUG 293 switch(status) { 294 case X_FINOK: 295 Exit(X_FINOK); 296 case X_ABORT: 297 Exit(X_ABORT); 298 case X_REWRITE: 299 goto restore_check_point; 300 default: 301 msg("Bad return code from dump: %d\n", status); 302 Exit(X_ABORT); 303 } 304 /*NOTREACHED*/ 305 } else { /* we are the child; just continue */ 306 #ifdef TDEBUG 307 sleep(4); /* allow time for parent's message to get out */ 308 msg("Child on Tape %d has parent %d, my pid = %d\n", 309 tapeno+1, parentpid, getpid()); 310 #endif TDEBUG 311 #ifdef RDUMP 312 while ((to = (host ? rmtopen(tape, 2) : 313 pipeout ? 1 : creat(tape, 0666))) < 0) 314 #else RDUMP 315 while ((to = pipeout ? 1 : creat(tape, 0666)) < 0) 316 #endif RDUMP 317 { 318 msg("Cannot open tape \"%s\".\n", tape); 319 if (!query("Do you want to retry the open?")) 320 dumpabort(); 321 } 322 323 enslave(); /* Share open tape file descriptor with slaves */ 324 325 asize = 0; 326 tapeno++; /* current tape sequence */ 327 newtape++; /* new tape signal */ 328 blks = 0; 329 if (spcl.c_type != TS_END) 330 for (i = 0; i < spcl.c_count; i++) 331 if (spcl.c_addr[i] != 0) 332 blks++; 333 spcl.c_count = blks + 1 - spcl.c_tapea + lastspclrec; 334 spcl.c_volume++; 335 spcl.c_type = TS_TAPE; 336 spcl.c_flags |= DR_NEWHEADER; 337 spclrec(); 338 spcl.c_flags &=~ DR_NEWHEADER; 339 if (tapeno > 1) 340 msg("Tape %d begins with blocks from ino %d\n", 341 tapeno, ino); 342 } 343 } 344 345 void 346 dumpabort() 347 { 348 if (master != 0 && master != getpid()) 349 kill(master, SIGTERM); /* Signals master to call dumpabort */ 350 else { 351 killall(); 352 msg("The ENTIRE dump is aborted.\n"); 353 } 354 Exit(X_ABORT); 355 } 356 357 void 358 Exit(status) 359 int status; 360 { 361 #ifdef TDEBUG 362 msg("pid = %d exits with status %d\n", getpid(), status); 363 #endif TDEBUG 364 exit(status); 365 } 366 367 /* 368 * could use pipe() for this if flock() worked on pipes 369 */ 370 void 371 lockfile(fd) 372 int fd[2]; 373 { 374 char tmpname[20]; 375 376 strcpy(tmpname, _PATH_LOCK); 377 mktemp(tmpname); 378 if ((fd[1] = creat(tmpname, 0400)) < 0) 379 quit("cannot create lockfile %s: %s\n", 380 tmpname, strerror(errno)); 381 if ((fd[0] = open(tmpname, 0)) < 0) 382 quit("cannot reopen lockfile %s: %s\n", 383 tmpname, strerror(errno)); 384 (void) unlink(tmpname); 385 } 386 387 void 388 enslave() 389 { 390 int first[2], prev[2], next[2], cmd[2]; /* file descriptors */ 391 register int i, j; 392 393 master = getpid(); 394 signal(SIGTERM, dumpabort); /* Slave sends SIGTERM on dumpabort() */ 395 signal(SIGPIPE, sigpipe); 396 signal(SIGUSR1, tperror); /* Slave sends SIGUSR1 on tape errors */ 397 lockfile(first); 398 for (i = 0; i < SLAVES; i++) { 399 if (i == 0) { 400 prev[0] = first[1]; 401 prev[1] = first[0]; 402 } else { 403 prev[0] = next[0]; 404 prev[1] = next[1]; 405 flock(prev[1], LOCK_EX); 406 } 407 if (i < SLAVES - 1) { 408 lockfile(next); 409 } else { 410 next[0] = first[0]; 411 next[1] = first[1]; /* Last slave loops back */ 412 } 413 if (pipe(cmd) < 0 || (slavepid[i] = fork()) < 0) 414 quit("too many slaves, %d (recompile smaller): %s\n", 415 i, strerror(errno)); 416 slavefd[i] = cmd[1]; 417 if (slavepid[i] == 0) { /* Slave starts up here */ 418 for (j = 0; j <= i; j++) 419 close(slavefd[j]); 420 signal(SIGINT, SIG_IGN); /* Master handles this */ 421 doslave(cmd[0], prev, next); 422 Exit(X_FINOK); 423 } 424 close(cmd[0]); 425 if (i > 0) { 426 close(prev[0]); 427 close(prev[1]); 428 } 429 } 430 close(first[0]); 431 close(first[1]); 432 master = 0; rotor = 0; 433 } 434 435 void 436 killall() 437 { 438 register int i; 439 440 for (i = 0; i < SLAVES; i++) 441 if (slavepid[i] > 0) 442 kill(slavepid[i], SIGKILL); 443 } 444 445 /* 446 * Synchronization - each process has a lockfile, and shares file 447 * descriptors to the following process's lockfile. When our write 448 * completes, we release our lock on the following process's lock- 449 * file, allowing the following process to lock it and proceed. We 450 * get the lock back for the next cycle by swapping descriptors. 451 */ 452 void 453 doslave(cmd, prev, next) 454 register int cmd, prev[2], next[2]; 455 { 456 register int nread, toggle = 0; 457 458 close(fi); 459 if ((fi = open(disk, 0)) < 0) /* Need our own seek pointer */ 460 quit("slave couldn't reopen disk: %s\n", strerror(errno)); 461 /* 462 * Get list of blocks to dump, read the blocks into tape buffer 463 */ 464 while ((nread = atomic(read, cmd, req, reqsiz)) == reqsiz) { 465 register struct req *p = req; 466 for (trecno = 0; trecno < ntrec; trecno += p->count, p += p->count) { 467 if (p->dblk) { 468 bread(p->dblk, tblock[trecno], 469 p->count * TP_BSIZE); 470 } else { 471 if (p->count != 1 || atomic(read, cmd, 472 tblock[trecno], TP_BSIZE) != TP_BSIZE) 473 quit("master/slave protocol botched.\n"); 474 } 475 } 476 flock(prev[toggle], LOCK_EX); /* Wait our turn */ 477 478 #ifdef RDUMP 479 if ((host ? rmtwrite(tblock[0], writesize) 480 : write(to, tblock[0], writesize)) != writesize) { 481 #else RDUMP 482 if (write(to, tblock[0], writesize) != writesize) { 483 #endif RDUMP 484 kill(master, SIGUSR1); 485 for (;;) 486 sigpause(0); 487 } 488 toggle ^= 1; 489 flock(next[toggle], LOCK_UN); /* Next slave's turn */ 490 } /* Also jolts him awake */ 491 if (nread != 0) 492 quit("error reading command pipe: %s\n", strerror(errno)); 493 } 494 495 /* 496 * Since a read from a pipe may not return all we asked for, 497 * or a write may not write all we ask if we get a signal, 498 * loop until the count is satisfied (or error). 499 */ 500 int 501 atomic(func, fd, buf, count) 502 int (*func)(), fd, count; 503 char *buf; 504 { 505 int got, need = count; 506 507 while ((got = (*func)(fd, buf, need)) > 0 && (need -= got) > 0) 508 buf += got; 509 return (got < 0 ? got : count - need); 510 } 511