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