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