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