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