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