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