808c65e2425bf5bc57f18812d189ecce2823ce1b
[openafs.git] / src / budb / db_dump.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /* dump the database
11  * Dump is made to a local file. Structures are dumped in network byte order
12  * for transportability between hosts
13  */
14
15 #include <afsconfig.h>
16 #include <afs/param.h>
17
18 RCSID
19     ("$Header$");
20
21 #ifdef AFS_NT40_ENV
22 #include <winsock2.h>
23 #else
24 #include <netinet/in.h>
25 #include <sys/param.h>
26 #endif
27 #include <afs/stds.h>
28 #include <sys/types.h>
29 #include <ubik.h>
30 #include <lock.h>
31 #include <string.h>
32
33 #include "database.h"
34 #include "budb.h"
35 #include "globals.h"
36 #include "error_macros.h"
37 #include "budb_errs.h"
38 #include "afs/audit.h"
39
40
41 /* dump ubik database - routines to scan the database and dump all
42  *      the information
43  */
44
45 /* -----------------------
46  * synchronization on pipe
47  * -----------------------
48  */
49
50 /* interlocking for database dump */
51
52 dumpSyncT dumpSync;
53 dumpSyncP dumpSyncPtr = &dumpSync;
54
55
56 /* canWrite
57  *      check if we should dump more of the database. Waits for the reader
58  *      to drain the information before allowing the writer to proceed.
59  * exit:
60  *      1 - ok to write
61  */
62
63 afs_int32
64 canWrite(fid)
65      int fid;
66 {
67     afs_int32 code = 0;
68     extern dumpSyncP dumpSyncPtr;
69
70     ObtainWriteLock(&dumpSyncPtr->ds_lock);
71
72     /* let the pipe drain */
73     while (dumpSyncPtr->ds_bytes > 0) {
74         if (dumpSyncPtr->ds_readerStatus == DS_WAITING) {
75             dumpSyncPtr->ds_readerStatus = 0;
76 #if defined(AFS_PTHREAD_ENV) && defined(UBIK_PTHREAD_ENV)
77             assert(pthread_cond_broadcast(&dumpSyncPtr->ds_readerStatus_cond) == 0);
78 #else
79             code = LWP_SignalProcess(&dumpSyncPtr->ds_readerStatus);
80             if (code)
81                 LogError(code, "canWrite: Signal delivery failed\n");
82 #endif
83         }
84         dumpSyncPtr->ds_writerStatus = DS_WAITING;
85         ReleaseWriteLock(&dumpSyncPtr->ds_lock);
86 #if defined(AFS_PTHREAD_ENV) && defined(UBIK_PTHREAD_ENV)
87         assert(pthread_mutex_lock(&dumpSyncPtr->ds_writerStatus_mutex) == 0);
88         assert(pthread_cond_wait(&dumpSyncPtr->ds_writerStatus_cond, &dumpSyncPtr->ds_writerStatus_mutex) == 0);
89         assert(pthread_mutex_unlock(&dumpSyncPtr->ds_writerStatus_mutex) == 0);
90 #else
91         LWP_WaitProcess(&dumpSyncPtr->ds_writerStatus);
92 #endif
93         ObtainWriteLock(&dumpSyncPtr->ds_lock);
94     }
95     return (1);
96 }
97
98
99 /* haveWritten
100  *      record the fact that nbytes have been written. Signal the reader
101  *      to proceed, and unlock.
102  * exit:
103  *      no return value
104  */
105
106 void
107 haveWritten(nbytes)
108      afs_int32 nbytes;
109 {
110     afs_int32 code = 0;
111     extern dumpSyncP dumpSyncPtr;
112
113     dumpSyncPtr->ds_bytes += nbytes;
114     if (dumpSyncPtr->ds_readerStatus == DS_WAITING) {
115         dumpSyncPtr->ds_readerStatus = 0;
116 #if defined(AFS_PTHREAD_ENV) && defined(UBIK_PTHREAD_ENV)
117         assert(pthread_cond_broadcast(&dumpSyncPtr->ds_readerStatus_cond) == 0);
118 #else
119         code = LWP_SignalProcess(&dumpSyncPtr->ds_readerStatus);
120         if (code)
121             LogError(code, "haveWritten: Signal delivery failed\n");
122 #endif
123     }
124     ReleaseWriteLock(&dumpSyncPtr->ds_lock);
125 }
126
127 /* doneWriting
128  *      wait for the reader to drain all the information, and then set the
129  *      done flag.
130  */
131
132 void
133 doneWriting(error)
134      afs_int32 error;
135 {
136     afs_int32 code = 0;
137
138     /* wait for the reader */
139     ObtainWriteLock(&dumpSyncPtr->ds_lock);
140     while (dumpSyncPtr->ds_readerStatus != DS_WAITING) {
141         LogDebug(4, "doneWriting: waiting for Reader\n");
142         dumpSyncPtr->ds_writerStatus = DS_WAITING;
143         ReleaseWriteLock(&dumpSyncPtr->ds_lock);
144 #if defined(AFS_PTHREAD_ENV) && defined(UBIK_PTHREAD_ENV)
145         assert(pthread_mutex_lock(&dumpSyncPtr->ds_writerStatus_mutex) == 0);
146         assert(pthread_cond_wait(&dumpSyncPtr->ds_writerStatus_cond, &dumpSyncPtr->ds_writerStatus_mutex) == 0);
147         assert(pthread_mutex_unlock(&dumpSyncPtr->ds_writerStatus_mutex) == 0);
148 #else
149         LWP_WaitProcess(&dumpSyncPtr->ds_writerStatus);
150 #endif
151         ObtainWriteLock(&dumpSyncPtr->ds_lock);
152     }
153
154     LogDebug(4, "doneWriting: setting done\n");
155
156     /* signal that we are done */
157     if (error)
158         dumpSyncPtr->ds_writerStatus = DS_DONE_ERROR;
159     else
160         dumpSyncPtr->ds_writerStatus = DS_DONE;
161     dumpSyncPtr->ds_readerStatus = 0;
162 #if defined(AFS_PTHREAD_ENV) && defined(UBIK_PTHREAD_ENV)
163     assert(pthread_cond_broadcast(&dumpSyncPtr->ds_readerStatus_cond) == 0);
164 #else
165     code = LWP_NoYieldSignal(&dumpSyncPtr->ds_readerStatus);
166     if (code)
167         LogError(code, "doneWriting: Signal delivery failed\n");
168 #endif
169     ReleaseWriteLock(&dumpSyncPtr->ds_lock);
170 }
171
172 /* notes:
173  *      ut - setup and pass down
174  */
175
176 /* writeStructHeader
177  *      write header appropriate for requested structure type
178  */
179
180 afs_int32
181 writeStructHeader(fid, type)
182      int fid;
183      afs_int32 type;
184 {
185     struct structDumpHeader hostDumpHeader, netDumpHeader;
186
187     hostDumpHeader.type = type;
188     hostDumpHeader.structversion = 1;
189
190
191     switch (type) {
192     case SD_DBHEADER:
193         hostDumpHeader.size = sizeof(struct DbHeader);
194         break;
195
196     case SD_DUMP:
197         hostDumpHeader.size = sizeof(struct budb_dumpEntry);
198         break;
199
200     case SD_TAPE:
201         hostDumpHeader.size = sizeof(struct budb_tapeEntry);
202         break;
203
204     case SD_VOLUME:
205         hostDumpHeader.size = sizeof(struct budb_volumeEntry);
206         break;
207
208     case SD_END:
209         hostDumpHeader.size = 0;
210         break;
211
212     default:
213         LogError(0, "writeStructHeader: invalid type %d\n", type);
214         BUDB_EXIT(1);
215     }
216
217     structDumpHeader_hton(&hostDumpHeader, &netDumpHeader);
218
219     if (canWrite(fid) <= 0)
220         return (BUDB_DUMPFAILED);
221     if (write(fid, &netDumpHeader, sizeof(netDumpHeader)) !=
222         sizeof(netDumpHeader))
223         return (BUDB_DUMPFAILED);
224     haveWritten(sizeof(netDumpHeader));
225
226     return (0);
227 }
228
229 /* writeTextHeader
230  *      write header appropriate for requested structure type
231  */
232
233 afs_int32
234 writeTextHeader(fid, type)
235      int fid;
236      afs_int32 type;
237 {
238     struct structDumpHeader hostDumpHeader, netDumpHeader;
239
240     hostDumpHeader.structversion = 1;
241
242     switch (type) {
243     case TB_DUMPSCHEDULE:
244         hostDumpHeader.type = SD_TEXT_DUMPSCHEDULE;
245         break;
246
247     case TB_VOLUMESET:
248         hostDumpHeader.type = SD_TEXT_VOLUMESET;
249         break;
250
251     case TB_TAPEHOSTS:
252         hostDumpHeader.type = SD_TEXT_TAPEHOSTS;
253         break;
254
255     default:
256         LogError(0, "writeTextHeader: invalid type %d\n", type);
257         BUDB_EXIT(1);
258     }
259
260     hostDumpHeader.size = ntohl(db.h.textBlock[type].size);
261     structDumpHeader_hton(&hostDumpHeader, &netDumpHeader);
262
263     if (canWrite(fid) <= 0)
264         return (BUDB_DUMPFAILED);
265
266     if (write(fid, &netDumpHeader, sizeof(netDumpHeader)) !=
267         sizeof(netDumpHeader))
268         return (BUDB_DUMPFAILED);
269
270     haveWritten(sizeof(netDumpHeader));
271
272     return (0);
273 }
274
275 afs_int32
276 writeDbHeader(fid)
277      int fid;
278 {
279     struct DbHeader header;
280     afs_int32 curtime;
281     afs_int32 code = 0, tcode;
282
283     extern struct memoryDB db;
284
285     /* check the memory database header for integrity */
286     if (db.h.version != db.h.checkVersion)
287         ERROR(BUDB_DATABASEINCONSISTENT);
288
289     curtime = time(0);
290
291     /* copy selected fields. Source is in xdr format. */
292     header.dbversion = db.h.version;
293     header.created = htonl(curtime);
294     strcpy(header.cell, "");
295     header.lastDumpId = db.h.lastDumpId;
296     header.lastInstanceId = db.h.lastInstanceId;
297     header.lastTapeId = db.h.lastTapeId;
298
299     tcode = writeStructHeader(fid, SD_DBHEADER);
300     if (tcode)
301         ERROR(tcode);
302
303     if (canWrite(fid) <= 0)
304         ERROR(BUDB_DUMPFAILED);
305
306     if (write(fid, &header, sizeof(header)) != sizeof(header))
307         ERROR(BUDB_DUMPFAILED);
308
309     haveWritten(sizeof(header));
310
311   error_exit:
312     return (code);
313 }
314
315 /* writeDump
316  *      write out a dump entry structure
317  */
318
319 afs_int32
320 writeDump(fid, dumpPtr)
321      int fid;
322      dbDumpP dumpPtr;
323 {
324     struct budb_dumpEntry dumpEntry;
325     afs_int32 code = 0, tcode;
326
327     tcode = dumpToBudbDump(dumpPtr, &dumpEntry);
328     if (tcode)
329         ERROR(tcode);
330
331     writeStructHeader(fid, SD_DUMP);
332
333     if (canWrite(fid) <= 0)
334         ERROR(BUDB_DUMPFAILED);
335
336     if (write(fid, &dumpEntry, sizeof(dumpEntry)) != sizeof(dumpEntry))
337         ERROR(BUDB_DUMPFAILED);
338     haveWritten(sizeof(dumpEntry));
339
340   error_exit:
341     return (code);
342 }
343
344 afs_int32
345 writeTape(fid, tapePtr, dumpid)
346      int fid;
347      struct tape *tapePtr;
348      afs_int32 dumpid;
349 {
350     struct budb_tapeEntry tapeEntry;
351     afs_int32 code = 0, tcode;
352
353     tcode = writeStructHeader(fid, SD_TAPE);
354     if (tcode)
355         ERROR(tcode);
356
357     tapeToBudbTape(tapePtr, &tapeEntry);
358
359     tapeEntry.dump = htonl(dumpid);
360
361     if (canWrite(fid) <= 0)
362         ERROR(BUDB_DUMPFAILED);
363
364     if (write(fid, &tapeEntry, sizeof(tapeEntry)) != sizeof(tapeEntry))
365         ERROR(BUDB_DUMPFAILED);
366
367     haveWritten(sizeof(tapeEntry));
368
369   error_exit:
370     return (code);
371 }
372
373 /* combines volFragment and volInfo */
374
375 afs_int32
376 writeVolume(ut, fid, volFragmentPtr, volInfoPtr, dumpid, tapeName)
377      struct ubik_trans *ut;
378      int fid;
379      struct volFragment *volFragmentPtr;
380      struct volInfo *volInfoPtr;
381      afs_int32 dumpid;
382      char *tapeName;
383 {
384     struct budb_volumeEntry budbVolume;
385     afs_int32 code = 0;
386
387     volsToBudbVol(volFragmentPtr, volInfoPtr, &budbVolume);
388
389     budbVolume.dump = htonl(dumpid);
390     strcpy(budbVolume.tape, tapeName);
391
392     writeStructHeader(fid, SD_VOLUME);
393
394     if (canWrite(fid) <= 0)
395         ERROR(BUDB_DUMPFAILED);
396
397     if (write(fid, &budbVolume, sizeof(budbVolume)) != sizeof(budbVolume))
398         ERROR(BUDB_DUMPFAILED);
399
400     haveWritten(sizeof(budbVolume));
401
402   error_exit:
403     return (code);
404 }
405
406 /* -------------------
407  * handlers for the text blocks
408  * -------------------
409  */
410
411 /* checkLock
412  *      make sure a text lock is NOT held
413  * exit:
414  *      0 - not held
415  *      n - error
416  */
417
418 afs_int32
419 checkLock(textType)
420      afs_int32 textType;
421 {
422     db_lockP lockPtr;
423
424     if ((textType < 0) || (textType > TB_NUM - 1))
425         return (BUDB_BADARGUMENT);
426
427     lockPtr = &db.h.textLocks[textType];
428
429     if (lockPtr->lockState != 0)
430         return (BUDB_LOCKED);
431     return (0);
432 }
433
434 /* checkText
435  *      check the integrity of the specified text type
436  */
437
438 checkText(ut, textType)
439      struct ubik_trans *ut;
440      afs_int32 textType;
441 {
442     struct textBlock *tbPtr;
443     afs_int32 nBytes = 0;       /* accumulated actual size */
444     afs_int32 size;
445     struct block block;
446     dbadr blockAddr;
447
448     afs_int32 code = 0;
449
450     tbPtr = &db.h.textBlock[textType];
451     blockAddr = ntohl(tbPtr->textAddr);
452     size = ntohl(tbPtr->size);
453
454     while (blockAddr != 0) {
455         /* read the block */
456         code =
457             cdbread(ut, text_BLOCK, blockAddr, (char *)&block, sizeof(block));
458         if (code)
459             ERROR(code);
460
461         /* check its type */
462         if (block.h.type != text_BLOCK)
463             ERROR(BUDB_DATABASEINCONSISTENT);
464
465         /* add up the size */
466         nBytes += BLOCK_DATA_SIZE;
467
468         blockAddr = ntohl(block.h.next);
469     }
470
471     /* ensure that we have at least the expected amount of text */
472     if (nBytes < size)
473         ERROR(BUDB_DATABASEINCONSISTENT);
474
475   error_exit:
476     return (code);
477 }
478
479 /* writeText
480  * entry:
481  *      textType - type of text block, e.g. TB_DUMPSCHEDULE
482  */
483
484 afs_int32
485 writeText(ut, fid, textType)
486      struct ubik_trans *ut;
487      int fid;
488      int textType;
489 {
490     struct textBlock *tbPtr;
491     afs_int32 textSize, writeSize;
492     dbadr dbAddr;
493     struct block block;
494     afs_int32 code = 0;
495
496     /* check lock is free */
497     code = checkLock(textType);
498     if (code)
499         ERROR(code);
500
501     /* ensure that this block has the correct type */
502     code = checkText(ut, textType);
503     if (code) {
504         LogError(0, "writeText: text type %d damaged\n", textType);
505         ERROR(code);
506     }
507
508     tbPtr = &db.h.textBlock[textType];
509     textSize = ntohl(tbPtr->size);
510     dbAddr = ntohl(tbPtr->textAddr);
511
512     if (!dbAddr)
513         goto error_exit;        /* Don't save anything if no blocks */
514
515     writeTextHeader(fid, textType);
516
517     while (dbAddr) {
518         code = cdbread(ut, text_BLOCK, dbAddr, (char *)&block, sizeof(block));
519         if (code)
520             ERROR(code);
521
522         writeSize = MIN(textSize, BLOCK_DATA_SIZE);
523         if (!writeSize)
524             break;
525
526         if (canWrite(fid) <= 0)
527             ERROR(BUDB_DUMPFAILED);
528
529         if (write(fid, &block.a[0], writeSize) != writeSize)
530             ERROR(BUDB_IO);
531
532         haveWritten(writeSize);
533         textSize -= writeSize;
534
535         dbAddr = ntohl(block.h.next);
536     }
537
538   error_exit:
539     return (code);
540 }
541
542 #define MAXAPPENDS 200
543
544 afs_int32
545 writeDatabase(ut, fid)
546      struct ubik_trans *ut;
547      int fid;
548 {
549     dbadr dbAddr, dbAppAddr;
550     struct dump diskDump, apDiskDump;
551     dbadr tapeAddr;
552     struct tape diskTape;
553     dbadr volFragAddr;
554     struct volFragment diskVolFragment;
555     struct volInfo diskVolInfo;
556     int length, hash;
557     int old = 0;
558     int entrySize;
559     afs_int32 code = 0, tcode;
560     afs_int32 appDumpAddrs[MAXAPPENDS], numaddrs, appcount, j;
561
562     struct memoryHashTable *mht;
563
564     LogDebug(4, "writeDatabase:\n");
565
566     /* write out a header identifying this database etc */
567     tcode = writeDbHeader(fid);
568     if (tcode) {
569         LogError(tcode, "writeDatabase: Can't write Header\n");
570         ERROR(tcode);
571     }
572
573     /* write out the tree of dump structures */
574
575     mht = ht_GetType(HT_dumpIden_FUNCTION, &entrySize);
576     if (!mht) {
577         LogError(tcode, "writeDatabase: Can't get dump type\n");
578         ERROR(BUDB_BADARGUMENT);
579     }
580
581     for (old = 0; old <= 1; old++) {
582         /*oldnew */
583         /* only two states, old or not old */
584         length = (old ? mht->oldLength : mht->length);
585         if (!length)
586             continue;
587
588         for (hash = 0; hash < length; hash++) {
589             /*hashBuckets */
590             /* dump all the dumps in this hash bucket
591              */
592             for (dbAddr = ht_LookupBucket(ut, mht, hash, old); dbAddr; dbAddr = ntohl(diskDump.idHashChain)) {  /*initialDumps */
593                 /* now check if this dump had any errors/inconsistencies.
594                  * If so, don't dump it
595                  */
596                 if (badEntry(dbAddr)) {
597                     LogError(0,
598                              "writeDatabase: Damaged dump entry at addr 0x%x\n",
599                              dbAddr);
600                     Log("     Skipping remainder of dumps on hash chain %d\n",
601                         hash);
602                     break;
603                 }
604
605                 tcode =
606                     cdbread(ut, dump_BLOCK, dbAddr, &diskDump,
607                             sizeof(diskDump));
608                 if (tcode) {
609                     LogError(tcode,
610                              "writeDatabase: Can't read dump entry (addr 0x%x)\n",
611                              dbAddr);
612                     Log("     Skipping remainder of dumps on hash chain %d\n",
613                         hash);
614                     break;
615                 }
616
617                 /* Skip appended dumps, only start with initial dumps */
618                 if (diskDump.initialDumpID != 0)
619                     continue;
620
621                 /* Skip appended dumps, only start with initial dumps. Then
622                  * follow the appended dump chain so they are in order for restore.
623                  */
624                 appcount = numaddrs = 0;
625                 for (dbAppAddr = dbAddr; dbAppAddr;
626                      dbAppAddr = ntohl(apDiskDump.appendedDumpChain)) {
627                     /*appendedDumps */
628                     /* Check to see if we have a circular loop of appended dumps */
629                     for (j = 0; j < numaddrs; j++) {
630                         if (appDumpAddrs[j] == dbAppAddr)
631                             break;      /* circular loop */
632                     }
633                     if (j < numaddrs) { /* circular loop */
634                         Log("writeDatabase: Circular loop found in appended dumps\n");
635                         Log("Skipping rest of appended dumps of dumpID %u\n",
636                             ntohl(diskDump.id));
637                         break;
638                     }
639                     if (numaddrs >= MAXAPPENDS)
640                         numaddrs = MAXAPPENDS - 1;      /* don't overflow */
641                     appDumpAddrs[numaddrs] = dbAppAddr;
642                     numaddrs++;
643
644                     /* If we dump a 1000 appended dumps, assume a loop */
645                     if (appcount >= 5 * MAXAPPENDS) {
646                         Log("writeDatabase: Potential circular loop of appended dumps\n");
647                         Log("Skipping rest of appended dumps of dumpID %u. Dumped %d\n", ntohl(diskDump.id), appcount);
648                         break;
649                     }
650                     appcount++;
651
652                     /* Read the dump entry */
653                     if (dbAddr == dbAppAddr) {
654                         /* First time through, don't need to read the dump entry again */
655                         memcpy(&apDiskDump, &diskDump, sizeof(diskDump));
656                     } else {
657                         if (badEntry(dbAppAddr)) {
658                             LogError(0,
659                                      "writeDatabase: Damaged appended dump entry at addr 0x%x\n",
660                                      dbAddr);
661                             Log("     Skipping this and remainder of appended dumps of initial DumpID %u\n", ntohl(diskDump.id));
662                             break;
663                         }
664
665                         tcode =
666                             cdbread(ut, dump_BLOCK, dbAppAddr, &apDiskDump,
667                                     sizeof(apDiskDump));
668                         if (tcode) {
669                             LogError(tcode,
670                                      "writeDatabase: Can't read appended dump entry (addr 0x%x)\n",
671                                      dbAppAddr);
672                             Log("     Skipping this and remainder of appended dumps of initial DumpID %u\n", ntohl(diskDump.id));
673                             break;
674                         }
675
676                         /* Verify that this appended dump points to the initial dump */
677                         if (ntohl(apDiskDump.initialDumpID) !=
678                             ntohl(diskDump.id)) {
679                             LogError(0,
680                                      "writeDatabase: Appended dumpID %u does not reference initial dumpID %u\n",
681                                      ntohl(apDiskDump.id),
682                                      ntohl(diskDump.id));
683                             Log("     Skipping this appended dump\n");
684                             continue;
685                         }
686                     }
687
688                     /* Save the dump entry */
689                     tcode = writeDump(fid, &apDiskDump);
690                     if (tcode) {
691                         LogError(tcode,
692                                  "writeDatabase: Can't write dump entry\n");
693                         ERROR(tcode);
694                     }
695
696                     /* For each tape on this dump
697                      */
698                     for (tapeAddr = ntohl(apDiskDump.firstTape); tapeAddr; tapeAddr = ntohl(diskTape.nextTape)) {       /*tapes */
699                         /* read the tape entry */
700                         tcode =
701                             cdbread(ut, tape_BLOCK, tapeAddr, &diskTape,
702                                     sizeof(diskTape));
703                         if (tcode) {
704                             LogError(tcode,
705                                      "writeDatabase: Can't read tape entry (addr 0x%x) of dumpID %u\n",
706                                      tapeAddr, ntohl(apDiskDump.id));
707                             Log("     Skipping this and remaining tapes in the dump (and all their volumes)\n");
708                             break;
709                         }
710
711                         /* Save the tape entry */
712                         tcode =
713                             writeTape(fid, &diskTape, ntohl(apDiskDump.id));
714                         if (tcode) {
715                             LogError(tcode,
716                                      "writeDatabase: Can't write tape entry\n");
717                             ERROR(tcode);
718                         }
719
720                         /* For each volume on this tape.
721                          */
722                         for (volFragAddr = ntohl(diskTape.firstVol); volFragAddr; volFragAddr = ntohl(diskVolFragment.sameTapeChain)) { /*volumes */
723                             /* Read the volume Fragment entry */
724                             tcode =
725                                 cdbread(ut, volFragment_BLOCK, volFragAddr,
726                                         &diskVolFragment,
727                                         sizeof(diskVolFragment));
728                             if (tcode) {
729                                 LogError(tcode,
730                                          "writeDatabase: Can't read volfrag entry (addr 0x%x) of dumpID %u\n",
731                                          volFragAddr, ntohl(apDiskDump.id));
732                                 Log("     Skipping this and remaining volumes on tape '%s'\n", diskTape.name);
733                                 break;
734                             }
735
736                             /* Read the volume Info entry */
737                             tcode =
738                                 cdbread(ut, volInfo_BLOCK,
739                                         ntohl(diskVolFragment.vol),
740                                         &diskVolInfo, sizeof(diskVolInfo));
741                             if (tcode) {
742                                 LogError(tcode,
743                                          "writeDatabase: Can't read volinfo entry (addr 0x%x) of dumpID %u\n",
744                                          ntohl(diskVolFragment.vol),
745                                          ntohl(apDiskDump.id));
746                                 Log("     Skipping volume on tape '%s'\n",
747                                     diskTape.name);
748                                 continue;
749                             }
750
751                             /* Save the volume entry */
752                             tcode =
753                                 writeVolume(ut, fid, &diskVolFragment,
754                                             &diskVolInfo,
755                                             ntohl(apDiskDump.id),
756                                             diskTape.name);
757                             if (tcode) {
758                                 LogError(tcode,
759                                          "writeDatabase: Can't write volume entry\n");
760                                 ERROR(tcode);
761                             }
762                         }       /*volumes */
763                     }           /*tapes */
764                 }               /*appendedDumps */
765             }                   /*initialDumps */
766         }                       /*hashBuckets */
767     }                           /*oldnew */
768
769     /* write out the textual configuration information */
770     tcode = writeText(ut, fid, TB_DUMPSCHEDULE);
771     if (tcode) {
772         LogError(tcode, "writeDatabase: Can't write dump schedule\n");
773         ERROR(tcode);
774     }
775     tcode = writeText(ut, fid, TB_VOLUMESET);
776     if (tcode) {
777         LogError(tcode, "writeDatabase: Can't write volume set\n");
778         ERROR(tcode);
779     }
780     tcode = writeText(ut, fid, TB_TAPEHOSTS);
781     if (tcode) {
782         LogError(tcode, "writeDatabase: Can't write tape hosts\n");
783         ERROR(tcode);
784     }
785
786     tcode = writeStructHeader(fid, SD_END);
787     if (tcode) {
788         LogError(tcode, "writeDatabase: Can't write end savedb\n");
789         ERROR(tcode);
790     }
791
792   error_exit:
793     doneWriting(code);
794     return (code);
795 }
796
797
798 #ifdef notdef
799
800 afs_int32
801 canWrite(fid)
802      int fid;
803 {
804     afs_int32 in, out, except;
805     struct timeval tp;
806     afs_int32 code;
807
808     tp.tv_sec = 0;
809     tp.tv_usec = 0;
810
811     out = (1 << fid);
812     in = 0;
813     except = 0;
814
815     code = IOMGR_Select(32, &in, &out, &except, &tp);
816     return (code);
817 }
818
819 #endif /* notdef */