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