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