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