vgetvnode-debuglog-vio-20071228
[openafs.git] / src / vol / vnode.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  * Portions Copyright (c) 2006 Sine Nomine Associates
10  */
11
12 /*
13         System:         VICE-TWO
14         Module:         vnode.c
15         Institution:    The Information Technology Center, Carnegie-Mellon University
16
17  */
18 #include <afsconfig.h>
19 #include <afs/param.h>
20 #define MAXINT     (~(1<<((sizeof(int)*8)-1)))
21
22 RCSID
23     ("$Header$");
24
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
28 #ifdef AFS_PTHREAD_ENV
29 #include <assert.h>
30 #else /* AFS_PTHREAD_ENV */
31 #include <afs/assert.h>
32 #endif /* AFS_PTHREAD_ENV */
33
34 #include <rx/xdr.h>
35 #include <afs/afsint.h>
36 #include "nfs.h"
37 #include <afs/errors.h>
38 #include "lock.h"
39 #include "lwp.h"
40 #include <afs/afssyscalls.h>
41 #include "ihandle.h"
42 #include "vnode.h"
43 #include "volume.h"
44 #include "partition.h"
45 #include "salvsync.h"
46 #if defined(AFS_SGI_ENV)
47 #include "sys/types.h"
48 #include "fcntl.h"
49 #undef min
50 #undef max
51 #include "stdlib.h"
52 #endif
53 #ifdef AFS_NT40_ENV
54 #include <fcntl.h>
55 #include "ntops.h"
56 #else
57 #include <sys/file.h>
58 #ifdef  AFS_SUN5_ENV
59 #include <sys/fcntl.h>
60 #endif
61 #include <unistd.h>
62 #endif /* AFS_NT40_ENV */
63 #include <sys/stat.h>
64
65 /*@printflike@*/ extern void Log(const char *format, ...);
66
67 /*@printflike@*/ extern void Abort(const char *format, ...);
68
69
70 struct VnodeClassInfo VnodeClassInfo[nVNODECLASSES];
71
72 private int moveHash(register Vnode * vnp, bit32 newHash);
73 private void StickOnLruChain_r(register Vnode * vnp,
74                                register struct VnodeClassInfo *vcp);
75
76 extern int LogLevel;
77
78 #define BAD_IGET        -1000
79
80 /* There are two separate vnode queue types defined here:
81  * Each hash conflict chain -- is singly linked, with a single head
82  * pointer. New entries are added at the beginning. Old
83  * entries are removed by linear search, which generally
84  * only occurs after a disk read).
85  * LRU chain -- is doubly linked, single head pointer.
86  * Entries are added at the head, reclaimed from the tail,
87  * or removed from anywhere in the queue.
88  */
89
90
91 /* Vnode hash table.  Find hash chain by taking lower bits of
92  * (volume_hash_offset + vnode).
93  * This distributes the root inodes of the volumes over the
94  * hash table entries and also distributes the vnodes of
95  * volumes reasonably fairly.  The volume_hash_offset field
96  * for each volume is established as the volume comes on line
97  * by using the VOLUME_HASH_OFFSET macro.  This distributes the
98  * volumes fairly among the cache entries, both when servicing
99  * a small number of volumes and when servicing a large number.
100  */
101
102 /* logging stuff for finding bugs */
103 #define THELOGSIZE      5120
104 static afs_int32 theLog[THELOGSIZE];
105 static afs_int32 vnLogPtr = 0;
106 void
107 VNLog(aop, anparms, av1, av2, av3, av4)
108      afs_int32 aop, anparms;
109      afs_int32 av1, av2, av3, av4;
110 {
111     register afs_int32 temp;
112     afs_int32 data[4];
113
114     /* copy data to array */
115     data[0] = av1;
116     data[1] = av2;
117     data[2] = av3;
118     data[3] = av4;
119     if (anparms > 4)
120         anparms = 4;            /* do bounds checking */
121
122     temp = (aop << 16) | anparms;
123     theLog[vnLogPtr++] = temp;
124     if (vnLogPtr >= THELOGSIZE)
125         vnLogPtr = 0;
126     for (temp = 0; temp < anparms; temp++) {
127         theLog[vnLogPtr++] = data[temp];
128         if (vnLogPtr >= THELOGSIZE)
129             vnLogPtr = 0;
130     }
131 }
132
133 /* VolumeHashOffset -- returns a new value to be stored in the
134  * volumeHashOffset of a Volume structure.  Called when a
135  * volume is initialized.  Sets the volumeHashOffset so that
136  * vnode cache entries are distributed reasonably between
137  * volumes (the root vnodes of the volumes will hash to
138  * different values, and spacing is maintained between volumes
139  * when there are not many volumes represented), and spread
140  * equally amongst vnodes within a single volume.
141  */
142 int
143 VolumeHashOffset_r(void)
144 {
145     static int nextVolumeHashOffset = 0;
146     /* hashindex Must be power of two in size */
147 #   define hashShift 3
148 #   define hashMask ((1<<hashShift)-1)
149     static byte hashindex[1 << hashShift] =
150         { 0, 128, 64, 192, 32, 160, 96, 224 };
151     int offset;
152     offset = hashindex[nextVolumeHashOffset & hashMask]
153         + (nextVolumeHashOffset >> hashShift);
154     nextVolumeHashOffset++;
155     return offset;
156 }
157
158 /* Change hashindex (above) if you change this constant */
159 #define VNODE_HASH_TABLE_SIZE 256
160 private Vnode *VnodeHashTable[VNODE_HASH_TABLE_SIZE];
161 #define VNODE_HASH(volumeptr,vnodenumber)\
162     ((volumeptr->vnodeHashOffset + vnodenumber)&(VNODE_HASH_TABLE_SIZE-1))
163
164 /*
165  * new support to secondarily hash vnodes by volume id
166  */
167 #define VNVOLUME_HASH(volumeId) (volumeId&(VolumeHashTable.Mask))
168
169 #include "rx/rx_queue.h"
170 typedef struct VnodeHashByVolumeChainHead {
171     struct rx_queue queue;
172     int len;
173     /* someday we could put a per-chain lock here... */
174 #ifdef AFS_DEMAND_ATTACH_FS
175     int busy;
176     pthread_cond_t chain_busy_cv;
177 #endif /* AFS_DEMAND_ATTACH_FS */
178 } VnodeHashByVolumeChainHead;
179 private VnodeHashByVolumeChainHead *VnodeHashByVolumeTable = NULL;
180
181 void
182 VInitVnHashByVolume(void)
183 {
184     register int i;
185
186     VnodeHashByVolumeTable = (VnodeHashByVolumeChainHead *) calloc(VolumeHashTable.Size, 
187                                                                    sizeof(VnodeHashByVolumeChainHead));
188     assert(VnodeHashByVolumeTable != NULL);
189     
190     for (i=0; i < VolumeHashTable.Size; i++) {
191         queue_Init(&VnodeHashByVolumeTable[i]);
192 #ifdef AFS_DEMAND_ATTACH_FS
193         assert(pthread_cond_init(&VnodeHashByVolumeTable[i].chain_busy_cv, NULL) == 0);
194 #endif /* AFS_DEMAND_ATTACH_FS */
195     }
196 }
197
198 static void
199 AddToVnHashByVolumeTable(register Vnode * vnp)
200 {
201     VnodeHashByVolumeChainHead * head;
202
203     if (queue_IsOnQueue(vnp))
204         return;
205
206     head = &VnodeHashByVolumeTable[VNVOLUME_HASH(vnp->volumePtr->hashid)];
207
208 #ifdef AFS_DEMAND_ATTACH_FS
209     while (head->busy) {
210         /* if the hash table is busy, wait */
211         assert(pthread_cond_wait(&head->chain_busy_cv, &vol_glock_mutex) == 0);
212     }
213 #endif /* AFS_DEMAND_ATTACH_FS */
214
215     head->len++;
216     queue_Append(head, vnp);
217 }
218
219 /* for demand-attach, caller MUST hold a ref count on vp */
220 static void
221 DeleteFromVnHashByVolumeTable(register Vnode * vnp)
222 {
223     VnodeHashByVolumeChainHead * head;
224
225     if (!queue_IsOnQueue(vnp))
226         return;
227
228     head = &VnodeHashByVolumeTable[VNVOLUME_HASH(vnp->volumePtr->hashid)];
229
230 #ifdef AFS_DEMAND_ATTACH_FS
231     while (head->busy) {
232         /* if the hash table is busy, wait */
233         assert(pthread_cond_wait(&head->chain_busy_cv, &vol_glock_mutex) == 0);
234     }
235 #endif /* AFS_DEMAND_ATTACH_FS */
236
237     head->len--;
238     queue_Remove(vnp);
239 }
240
241 /* Code to invalidate a vnode entry.  Called when we've damaged a vnode, and want
242     to prevent future VGetVnode's from applying to it.  Leaves it in the same hash bucket
243     but that shouldn't be important.  */
244 void
245 VInvalidateVnode_r(register struct Vnode *avnode)
246 {
247     avnode->changed_newTime = 0;        /* don't let it get flushed out again */
248     avnode->changed_oldTime = 0;
249     avnode->delete = 0;         /* it isn't deleted, erally */
250     avnode->cacheCheck = 0;     /* invalid: prevents future vnode searches from working */
251 }
252
253 /* Not normally called by general client; called by volume.c */
254 int
255 VInitVnodes(VnodeClass class, int nVnodes)
256 {
257     byte *va;
258     register struct VnodeClassInfo *vcp = &VnodeClassInfo[class];
259
260     vcp->allocs = vcp->gets = vcp->reads = vcp->writes = 0;
261     vcp->cacheSize = nVnodes;
262     switch (class) {
263     case vSmall:
264         assert(CHECKSIZE_SMALLVNODE);
265         vcp->lruHead = NULL;
266         vcp->residentSize = SIZEOF_SMALLVNODE;
267         vcp->diskSize = SIZEOF_SMALLDISKVNODE;
268         vcp->magic = SMALLVNODEMAGIC;
269         break;
270     case vLarge:
271         vcp->lruHead = NULL;
272         vcp->residentSize = SIZEOF_LARGEVNODE;
273         vcp->diskSize = SIZEOF_LARGEDISKVNODE;
274         vcp->magic = LARGEVNODEMAGIC;
275         break;
276     }
277     {
278         int s = vcp->diskSize - 1;
279         int n = 0;
280         while (s)
281             s >>= 1, n++;
282         vcp->logSize = n;
283     }
284
285     if (nVnodes == 0)
286         return 0;
287
288     va = (byte *) calloc(nVnodes, vcp->residentSize);
289     assert(va != NULL);
290     while (nVnodes--) {
291         Vnode *vnp = (Vnode *) va;
292         vnp->nUsers = 0;        /* no context switches */
293         Lock_Init(&vnp->lock);
294         vnp->changed_oldTime = 0;
295         vnp->changed_newTime = 0;
296         vnp->volumePtr = NULL;
297         vnp->cacheCheck = 0;
298         vnp->delete = vnp->vnodeNumber = 0;
299 #ifdef AFS_PTHREAD_ENV
300         vnp->writer = (pthread_t) 0;
301 #else /* AFS_PTHREAD_ENV */
302         vnp->writer = (PROCESS) 0;
303 #endif /* AFS_PTHREAD_ENV */
304         vnp->hashIndex = 0;
305         vnp->handle = NULL;
306         if (vcp->lruHead == NULL)
307             vcp->lruHead = vnp->lruNext = vnp->lruPrev = vnp;
308         else {
309             vnp->lruNext = vcp->lruHead;
310             vnp->lruPrev = vcp->lruHead->lruPrev;
311             vcp->lruHead->lruPrev = vnp;
312             vnp->lruPrev->lruNext = vnp;
313             vcp->lruHead = vnp;
314         }
315         va += vcp->residentSize;
316     }
317     return 0;
318 }
319
320
321 /* allocate an *unused* vnode from the LRU chain, going backwards of course.  It shouldn't
322     be necessary to specify that nUsers == 0 since if it is in the list, nUsers
323     should be 0.  Things shouldn't be in lruq unless no one is using them.  */
324 Vnode *
325 VGetFreeVnode_r(struct VnodeClassInfo * vcp)
326 {
327     register Vnode *vnp;
328
329     vnp = vcp->lruHead->lruPrev;
330     if (vnp->nUsers != 0 || CheckLock(&vnp->lock))
331         Abort("locked vnode in lruq");
332     VNLog(1, 2, vnp->vnodeNumber, (afs_int32) vnp);
333     IH_RELEASE(vnp->handle);
334     return vnp;
335 }
336
337 static mlkReason = 0;
338 static mlkLastAlloc = 0;
339 static mlkLastOver = 0;
340 static mlkLastDelete = 0;
341
342 Vnode *
343 VAllocVnode(Error * ec, Volume * vp, VnodeType type)
344 {
345     Vnode *retVal;
346     VOL_LOCK;
347     retVal = VAllocVnode_r(ec, vp, type);
348     VOL_UNLOCK;
349     return retVal;
350 }
351
352 Vnode *
353 VAllocVnode_r(Error * ec, Volume * vp, VnodeType type)
354 {
355     register Vnode *vnp;
356     VnodeId vnodeNumber;
357     int newHash, bitNumber;
358     register struct VnodeClassInfo *vcp;
359     VnodeClass class;
360     Unique unique;
361
362     *ec = 0;
363     if (programType == fileServer && !V_inUse(vp)) {
364         if (vp->specialStatus) {
365             *ec = vp->specialStatus;
366         } else {
367             *ec = VOFFLINE;
368         }
369         return NULL;
370     }
371     class = vnodeTypeToClass(type);
372     vcp = &VnodeClassInfo[class];
373
374     if (!VolumeWriteable(vp)) {
375         *ec = (bit32) VREADONLY;
376         return NULL;
377     }
378
379     unique = vp->nextVnodeUnique++;
380     if (!unique)
381         unique = vp->nextVnodeUnique++;
382
383     if (vp->nextVnodeUnique > V_uniquifier(vp)) {
384         VUpdateVolume_r(ec, vp, VOL_UPDATE_WAIT);
385         if (*ec)
386             return NULL;
387     }
388
389     if (programType == fileServer) {
390         VAddToVolumeUpdateList_r(ec, vp);
391         if (*ec)
392             return NULL;
393     }
394
395     /* Find a slot in the bit map */
396     bitNumber = VAllocBitmapEntry_r(ec, vp, &vp->vnodeIndex[class],
397                                     VOL_ALLOC_BITMAP_WAIT);
398     if (*ec)
399         return NULL;
400     vnodeNumber = bitNumberToVnodeNumber(bitNumber, class);
401
402  vnrehash:
403     VNLog(2, 1, vnodeNumber);
404     /* Prepare to move it to the new hash chain */
405     newHash = VNODE_HASH(vp, vnodeNumber);
406     for (vnp = VnodeHashTable[newHash];
407          vnp && (vnp->vnodeNumber != vnodeNumber || vnp->volumePtr != vp
408                  || vnp->volumePtr->cacheCheck != vnp->cacheCheck);
409          vnp = vnp->hashNext);
410     if (vnp) {
411         /* slot already exists.  May even not be in lruq (consider store file locking a file being deleted)
412          * so we may have to wait for it below */
413         VNLog(3, 2, vnodeNumber, (afs_int32) vnp);
414
415         /* If first user, remove it from the LRU chain.  We can assume that
416          * there is at least one item in the queue */
417         if (++vnp->nUsers == 1) {
418             if (vnp == vcp->lruHead)
419                 vcp->lruHead = vcp->lruHead->lruNext;
420             vnp->lruPrev->lruNext = vnp->lruNext;
421             vnp->lruNext->lruPrev = vnp->lruPrev;
422             if (vnp == vcp->lruHead || vcp->lruHead == NULL)
423                 Abort("VGetVnode: lru chain addled!\n");
424             /* This won't block */
425             ObtainWriteLock(&vnp->lock);
426         } else {
427             /* follow locking hierarchy */
428             VOL_UNLOCK;
429             ObtainWriteLock(&vnp->lock);
430             VOL_LOCK;
431             if (vnp->volumePtr->cacheCheck != vnp->cacheCheck) {
432                 ReleaseWriteLock(&vnp->lock);
433                 goto vnrehash;
434             }
435         }
436 #ifdef AFS_PTHREAD_ENV
437         vnp->writer = pthread_self();
438 #else /* AFS_PTHREAD_ENV */
439         LWP_CurrentProcess(&vnp->writer);
440 #endif /* AFS_PTHREAD_ENV */
441     } else {
442         vnp = VGetFreeVnode_r(vcp);
443         /* Remove vnode from LRU chain and grab a write lock */
444         if (vnp == vcp->lruHead)
445             vcp->lruHead = vcp->lruHead->lruNext;
446         vnp->lruPrev->lruNext = vnp->lruNext;
447         vnp->lruNext->lruPrev = vnp->lruPrev;
448         if (vnp == vcp->lruHead || vcp->lruHead == NULL)
449             Abort("VGetVnode: lru chain addled!\n");
450         /* Initialize the header fields so noone allocates another
451          * vnode with the same number */
452         vnp->vnodeNumber = vnodeNumber;
453         vnp->volumePtr = vp;
454         vnp->cacheCheck = vp->cacheCheck;
455         vnp->nUsers = 1;
456         /* This will never block */
457         ObtainWriteLock(&vnp->lock);
458 #ifdef AFS_PTHREAD_ENV
459         vnp->writer = pthread_self();
460 #else /* AFS_PTHREAD_ENV */
461         LWP_CurrentProcess(&vnp->writer);
462 #endif /* AFS_PTHREAD_ENV */
463         /* Sanity check:  is this vnode really not in use? */
464         {
465             int size;
466             IHandle_t *ihP = vp->vnodeIndex[class].handle;
467             FdHandle_t *fdP;
468             off_t off = vnodeIndexOffset(vcp, vnodeNumber);
469
470             /* XXX we have a potential race here if two threads
471              * allocate new vnodes at the same time, and they
472              * both decide it's time to extend the index
473              * file size... */
474
475             VOL_UNLOCK;
476             fdP = IH_OPEN(ihP);
477             if (fdP == NULL) {
478                 Log("VAllocVnode: can't open index file!\n");
479                 goto error_encountered;
480             }
481             if ((size = FDH_SIZE(fdP)) < 0) {
482                 Log("VAllocVnode: can't stat index file!\n");
483                 goto error_encountered;
484             }
485             if (FDH_SEEK(fdP, off, SEEK_SET) < 0) {
486                 Log("VAllocVnode: can't seek on index file!\n");
487                 goto error_encountered;
488             }
489             if (off + vcp->diskSize <= size) {
490                 if (FDH_READ(fdP, &vnp->disk, vcp->diskSize) != vcp->diskSize) {
491                     Log("VAllocVnode: can't read index file!\n");
492                     goto error_encountered;
493                 }
494                 if (vnp->disk.type != vNull) {
495                     Log("VAllocVnode:  addled bitmap or index!\n");
496                     goto error_encountered;
497                 }
498             } else {
499                 /* growing file - grow in a reasonable increment */
500                 char *buf = (char *)malloc(16 * 1024);
501                 if (!buf)
502                     Abort("VAllocVnode: malloc failed\n");
503                 memset(buf, 0, 16 * 1024);
504                 (void)FDH_WRITE(fdP, buf, 16 * 1024);
505                 free(buf);
506             }
507             FDH_CLOSE(fdP);
508             fdP = NULL;
509             VOL_LOCK;
510             goto sane;
511
512         error_encountered:
513 #ifdef AFS_DEMAND_ATTACH_FS
514             VOL_LOCK;
515             VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
516             if (fdP)
517                 FDH_CLOSE(fdP);
518             VInvalidateVnode_r(vnp);
519             StickOnLruChain_r(vnp, vcp);
520             return NULL;
521 #else
522             assert(1 == 2);
523 #endif
524
525         }
526     sane:
527         VNLog(4, 2, vnodeNumber, (afs_int32) vnp);
528         AddToVnHashByVolumeTable(vnp);
529         moveHash(vnp, newHash);
530     }
531
532     VNLog(5, 1, (afs_int32) vnp);
533 #ifdef AFS_PTHREAD_ENV
534     vnp->writer = pthread_self();
535 #else /* AFS_PTHREAD_ENV */
536     LWP_CurrentProcess(&vnp->writer);
537 #endif /* AFS_PTHREAD_ENV */
538     memset(&vnp->disk, 0, sizeof(vnp->disk));
539     vnp->changed_newTime = 0;   /* set this bit when vnode is updated */
540     vnp->changed_oldTime = 0;   /* set this on CopyOnWrite. */
541     vnp->delete = 0;
542     vnp->disk.vnodeMagic = vcp->magic;
543     vnp->disk.type = type;
544     vnp->disk.uniquifier = unique;
545     vnp->handle = NULL;
546     vcp->allocs++;
547     vp->header->diskstuff.filecount++;
548     return vnp;
549 }
550
551 Vnode *
552 VGetVnode(Error * ec, Volume * vp, VnodeId vnodeNumber, int locktype)
553 {                               /* READ_LOCK or WRITE_LOCK, as defined in lock.h */
554     Vnode *retVal;
555     VOL_LOCK;
556     retVal = VGetVnode_r(ec, vp, vnodeNumber, locktype);
557     VOL_UNLOCK;
558     return retVal;
559 }
560
561 Vnode *
562 VGetVnode_r(Error * ec, Volume * vp, VnodeId vnodeNumber, int locktype)
563 {                               /* READ_LOCK or WRITE_LOCK, as defined in lock.h */
564     register Vnode *vnp;
565     int newHash;
566     VnodeClass class;
567     struct VnodeClassInfo *vcp;
568
569     *ec = 0;
570     mlkReason = 0;              /* last call didn't fail */
571
572     if (vnodeNumber == 0) {
573         *ec = VNOVNODE;
574         mlkReason = 1;
575         return NULL;
576     }
577
578     VNLog(100, 1, vnodeNumber);
579     if (programType == fileServer && !V_inUse(vp)) {
580         *ec = (vp->specialStatus ? vp->specialStatus : VOFFLINE);
581
582         /* If the volume is VBUSY (being cloned or dumped) and this is
583          * a READ operation, then don't fail.
584          */
585         if ((*ec != VBUSY) || (locktype != READ_LOCK)) {
586             mlkReason = 2;
587             return NULL;
588         }
589         *ec = 0;
590     }
591     class = vnodeIdToClass(vnodeNumber);
592     vcp = &VnodeClassInfo[class];
593     if (locktype == WRITE_LOCK && !VolumeWriteable(vp)) {
594         *ec = (bit32) VREADONLY;
595         mlkReason = 3;
596         return NULL;
597     }
598
599     if (locktype == WRITE_LOCK && programType == fileServer) {
600         VAddToVolumeUpdateList_r(ec, vp);
601         if (*ec) {
602             mlkReason = 1000 + *ec;
603             return NULL;
604         }
605     }
606
607     /* See whether the vnode is in the cache. */
608     newHash = VNODE_HASH(vp, vnodeNumber);
609     for (vnp = VnodeHashTable[newHash];
610          vnp && (vnp->vnodeNumber != vnodeNumber || vnp->volumePtr != vp
611                  || vnp->volumePtr->cacheCheck != vnp->cacheCheck);
612          vnp = vnp->hashNext);
613     vcp->gets++;
614     if (vnp == NULL) {
615         int n;
616         IHandle_t *ihP = vp->vnodeIndex[class].handle;
617         FdHandle_t *fdP;
618         /* Not in cache; tentatively grab most distantly used one from the LRU
619          * chain */
620         vcp->reads++;
621         vnp = VGetFreeVnode_r(vcp);
622         /* Remove it from the old hash chain */
623         if (vnp->volumePtr)
624             DeleteFromVnHashByVolumeTable(vnp);
625         moveHash(vnp, newHash);
626         /* Remove it from the LRU chain */
627         if (vnp == vcp->lruHead)
628             vcp->lruHead = vcp->lruHead->lruNext;
629         if (vnp == vcp->lruHead || vcp->lruHead == NULL)
630             Abort("VGetVnode: lru chain addled!\n");
631         vnp->lruPrev->lruNext = vnp->lruNext;
632         vnp->lruNext->lruPrev = vnp->lruPrev;
633         /* Initialize */
634         vnp->changed_newTime = vnp->changed_oldTime = 0;
635         vnp->delete = 0;
636         vnp->vnodeNumber = vnodeNumber;
637         vnp->volumePtr = vp;
638         vnp->cacheCheck = vp->cacheCheck;
639         vnp->nUsers = 1;
640         AddToVnHashByVolumeTable(vnp);
641
642         /* This will never block */
643         ObtainWriteLock(&vnp->lock);
644 #ifdef AFS_PTHREAD_ENV
645         vnp->writer = pthread_self();
646 #else /* AFS_PTHREAD_ENV */
647         LWP_CurrentProcess(&vnp->writer);
648 #endif /* AFS_PTHREAD_ENV */
649
650         /* Read vnode from volume index */
651         VOL_UNLOCK;
652         fdP = IH_OPEN(ihP);
653         if (fdP == NULL) {
654             Log("VGetVnode: can't open index dev=%u, i=%s\n", vp->device,
655                 PrintInode(NULL, vp->vnodeIndex[class].handle->ih_ino));
656 #ifdef AFS_DEMAND_ATTACH_FS
657             VOL_LOCK;
658             VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
659             VOL_UNLOCK;
660 #endif
661             *ec = VIO;
662             mlkReason = 9;
663         } else if (FDH_SEEK(fdP, vnodeIndexOffset(vcp, vnodeNumber), SEEK_SET)
664                    < 0) {
665             Log("VGetVnode: can't seek on index file vn=%u\n", vnodeNumber);
666 #ifdef AFS_DEMAND_ATTACH_FS
667             VOL_LOCK;
668             VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
669             VOL_UNLOCK;
670 #endif
671             *ec = VIO;
672             mlkReason = 10;
673             FDH_REALLYCLOSE(fdP);
674         } else if ((n = FDH_READ(fdP, (char *)&vnp->disk, vcp->diskSize))
675                    != vcp->diskSize) {
676             /* Don't take volume off line if the inumber is out of range
677              * or the inode table is full. */
678             FDH_REALLYCLOSE(fdP);
679             VOL_LOCK;
680             if (n == BAD_IGET) {
681                 Log("VGetVnode: bad inumber %s\n",
682                     PrintInode(NULL, vp->vnodeIndex[class].handle->ih_ino));
683                 *ec = VIO;
684                 mlkReason = 4;
685             }
686             /* Check for disk errors.  Anything else just means that the vnode
687              * is not allocated */
688             if (n == -1 && errno == EIO) {
689                 Log("VGetVnode: Couldn't read vnode %u, volume %u (%s); volume needs salvage\n", vnodeNumber, V_id(vp), V_name(vp));
690 #ifdef AFS_DEMAND_ATTACH_FS
691                 if (programType == fileServer) {
692                     VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
693                     *ec = VSALVAGING;
694                 } else {
695                     VForceOffline_r(vp, 0);
696                     *ec = VSALVAGE;
697                 }
698 #else
699                 VForceOffline_r(vp, 0);
700                 *ec = VSALVAGE;
701 #endif
702                 mlkReason = 4;
703             } else {
704                 /* Probably legit; Don't knock the volume offline */
705                 if (LogLevel >= 5) 
706                     Log("VGetVnode: Couldn't read vnode %u, volume %u (%s); errno %d\n", vnodeNumber, V_id(vp), V_name(vp), errno);
707                 mlkReason = 5;
708                 *ec = VIO;
709             }
710             VInvalidateVnode_r(vnp);
711             if (vnp->nUsers-- == 1)
712                 StickOnLruChain_r(vnp, vcp);
713             ReleaseWriteLock(&vnp->lock);
714             return NULL;
715         }
716         FDH_CLOSE(fdP);
717         VOL_LOCK;
718         /* Quick check to see that the data is reasonable */
719         if (vnp->disk.vnodeMagic != vcp->magic || vnp->disk.type == vNull) {
720             if (vnp->disk.type == vNull) {
721                 *ec = VNOVNODE;
722                 mlkReason = 6;
723                 VInvalidateVnode_r(vnp);
724                 if (vnp->nUsers-- == 1)
725                     StickOnLruChain_r(vnp, vcp);
726                 ReleaseWriteLock(&vnp->lock);
727                 return NULL;    /* The vnode is not allocated */
728             } else {
729                 struct vnodeIndex *index = &vp->vnodeIndex[class];
730                 unsigned int bitNumber = vnodeIdToBitNumber(vnodeNumber);
731                 unsigned int offset = bitNumber >> 3;
732
733                 /* Test to see if vnode number is valid. */
734                 if ((offset >= index->bitmapSize)
735                     || ((*(index->bitmap + offset) & (1 << (bitNumber & 0x7)))
736                         == 0)) {
737                     Log("VGetVnode: Request for unallocated vnode %u, volume %u (%s) denied.\n", vnodeNumber, V_id(vp), V_name(vp));
738                     mlkReason = 11;
739                     *ec = VNOVNODE;
740                 } else {
741                     Log("VGetVnode: Bad magic number, vnode %u, volume %u (%s); volume needs salvage\n", vnodeNumber, V_id(vp), V_name(vp));
742 #ifdef AFS_DEMAND_ATTACH_FS
743                     if (programType == fileServer) {
744                         VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
745                         *ec = VSALVAGING;
746                     } else {
747                         vp->goingOffline = 1;
748                         *ec = VSALVAGE;
749                     }
750 #else
751                     vp->goingOffline = 1;       /* used to call VOffline, but that would mess
752                                                  * up the volume ref count if called here */
753                     *ec = VSALVAGE;
754 #endif
755                     mlkReason = 7;
756                 }
757                 VInvalidateVnode_r(vnp);
758                 if (vnp->nUsers-- == 1)
759                     StickOnLruChain_r(vnp, vcp);
760                 ReleaseWriteLock(&vnp->lock);
761                 return NULL;
762             }
763         }
764         IH_INIT(vnp->handle, V_device(vp), V_parentId(vp), VN_GET_INO(vnp));
765         ReleaseWriteLock(&vnp->lock);
766     } else {
767         VNLog(101, 2, vnodeNumber, (afs_int32) vnp);
768         if (++vnp->nUsers == 1) {
769             /* First user.  Remove it from the LRU chain.  We can assume that
770              * there is at least one item in the queue */
771             if (vnp == vcp->lruHead)
772                 vcp->lruHead = vcp->lruHead->lruNext;
773             if (vnp == vcp->lruHead || vcp->lruHead == NULL)
774                 Abort("VGetVnode: lru chain addled!\n");
775             vnp->lruPrev->lruNext = vnp->lruNext;
776             vnp->lruNext->lruPrev = vnp->lruPrev;
777         }
778     }
779     VOL_UNLOCK;
780     if (locktype == READ_LOCK)
781         ObtainReadLock(&vnp->lock);
782     else {
783         ObtainWriteLock(&vnp->lock);
784 #ifdef AFS_PTHREAD_ENV
785         vnp->writer = pthread_self();
786 #else /* AFS_PTHREAD_ENV */
787         LWP_CurrentProcess(&vnp->writer);
788 #endif /* AFS_PTHREAD_ENV */
789     }
790     VOL_LOCK;
791     /* Check that the vnode hasn't been removed while we were obtaining
792      * the lock */
793     VNLog(102, 2, vnodeNumber, (afs_int32) vnp);
794     if ((vnp->disk.type == vNull) || (vnp->cacheCheck == 0)) {
795         if (vnp->nUsers-- == 1)
796             StickOnLruChain_r(vnp, vcp);
797         if (locktype == READ_LOCK)
798             ReleaseReadLock(&vnp->lock);
799         else
800             ReleaseWriteLock(&vnp->lock);
801         *ec = VNOVNODE;
802         mlkReason = 8;
803         /* vnode is labelled correctly by now, so we don't have to invalidate it */
804         return NULL;
805     }
806     if (programType == fileServer)
807         VBumpVolumeUsage_r(vnp->volumePtr);     /* Hack; don't know where it should be
808                                                  * called from.  Maybe VGetVolume */
809     return vnp;
810 }
811
812
813 int TrustVnodeCacheEntry = 1;
814 /* This variable is bogus--when it's set to 0, the hash chains fill
815    up with multiple versions of the same vnode.  Should fix this!! */
816 void
817 VPutVnode(Error * ec, register Vnode * vnp)
818 {
819     VOL_LOCK;
820     VPutVnode_r(ec, vnp);
821     VOL_UNLOCK;
822 }
823
824 void
825 VPutVnode_r(Error * ec, register Vnode * vnp)
826 {
827     int writeLocked, offset;
828     VnodeClass class;
829     struct VnodeClassInfo *vcp;
830     int code;
831
832     *ec = 0;
833     assert(vnp->nUsers != 0);
834     class = vnodeIdToClass(vnp->vnodeNumber);
835     vcp = &VnodeClassInfo[class];
836     assert(vnp->disk.vnodeMagic == vcp->magic);
837     VNLog(200, 2, vnp->vnodeNumber, (afs_int32) vnp);
838
839     writeLocked = WriteLocked(&vnp->lock);
840     if (writeLocked) {
841 #ifdef AFS_PTHREAD_ENV
842         pthread_t thisProcess = pthread_self();
843 #else /* AFS_PTHREAD_ENV */
844         PROCESS thisProcess;
845         LWP_CurrentProcess(&thisProcess);
846 #endif /* AFS_PTHREAD_ENV */
847         VNLog(201, 2, (afs_int32) vnp,
848               ((vnp->changed_newTime) << 1) | ((vnp->
849                                                 changed_oldTime) << 1) | vnp->
850               delete);
851         if (thisProcess != vnp->writer)
852             Abort("VPutVnode: Vnode at 0x%x locked by another process!\n",
853                   vnp);
854         if (vnp->changed_oldTime || vnp->changed_newTime || vnp->delete) {
855             Volume *vp = vnp->volumePtr;
856             afs_uint32 now = FT_ApproxTime();
857             assert(vnp->cacheCheck == vp->cacheCheck);
858
859             if (vnp->delete) {
860                 /* No longer any directory entries for this vnode. Free the Vnode */
861                 memset(&vnp->disk, 0, sizeof(vnp->disk));
862                 mlkLastDelete = vnp->vnodeNumber;
863                 /* delete flag turned off further down */
864                 VNLog(202, 2, vnp->vnodeNumber, (afs_int32) vnp);
865             } else if (vnp->changed_newTime) {
866                 vnp->disk.serverModifyTime = now;
867             }
868             if (vnp->changed_newTime)
869             {
870                 V_updateDate(vp) = vp->updateTime = now;
871                 if(V_volUpCounter(vp)<MAXINT)
872                         V_volUpCounter(vp)++;
873             }
874
875             /* The vnode has been changed. Write it out to disk */
876             if (!V_inUse(vp)) {
877 #ifdef AFS_DEMAND_ATTACH_FS
878                 VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
879                 *ec = VSALVAGING;
880 #else
881                 assert(V_needsSalvaged(vp));
882                 *ec = VSALVAGE;
883 #endif
884             } else {
885                 IHandle_t *ihP = vp->vnodeIndex[class].handle;
886                 FdHandle_t *fdP;
887                 VOL_UNLOCK;
888                 fdP = IH_OPEN(ihP);
889                 if (fdP == NULL) {
890                     Log("VPutVnode: can't open index file!\n");
891                     goto error_encountered;
892                 }
893                 offset = vnodeIndexOffset(vcp, vnp->vnodeNumber);
894                 if (FDH_SEEK(fdP, offset, SEEK_SET) < 0) {
895                     Log("VPutVnode: can't seek on index file! fdp=0x%x offset=%d, errno=%d\n",
896                         fdP, offset, errno);
897                     goto error_encountered;
898                 }
899                 code = FDH_WRITE(fdP, &vnp->disk, vcp->diskSize);
900                 if (code != vcp->diskSize) {
901                     /* Don't force volume offline if the inumber is out of
902                      * range or the inode table is full.
903                      */
904                     VOL_LOCK;
905                     if (code == BAD_IGET) {
906                         Log("VPutVnode: bad inumber %s\n",
907                             PrintInode(NULL,
908                                        vp->vnodeIndex[class].handle->ih_ino));
909                         *ec = VIO;
910                     } else {
911                         Log("VPutVnode: Couldn't write vnode %u, volume %u (%s) (error %d)\n", vnp->vnodeNumber, V_id(vnp->volumePtr), V_name(vnp->volumePtr), code);
912 #ifdef AFS_DEMAND_ATTACH_FS
913                         VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
914                         *ec = VSALVAGING;
915 #else
916                         VForceOffline_r(vp, 0);
917                         *ec = VSALVAGE;
918 #endif
919                     }
920                     VOL_UNLOCK;
921                     FDH_REALLYCLOSE(fdP);
922                 } else {
923                     FDH_CLOSE(fdP);
924                 }
925                 VOL_LOCK;
926                 goto sane;
927
928             error_encountered:
929 #ifdef AFS_DEMAND_ATTACH_FS
930                 /* XXX instead of dumping core, let's try to request a salvage
931                  * and just fail the putvnode */
932                 if (fdP)
933                     FDH_CLOSE(fdP);
934                 VOL_LOCK;
935                 VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
936                 *ec = VSALVAGING;
937                 goto done;
938 #else
939                 assert(1 == 2);
940 #endif
941
942             sane:
943                 /* If the vnode is to be deleted, and we wrote the vnode out,
944                  * free its bitmap entry. Do after the vnode is written so we
945                  * don't allocate from bitmap before the vnode is written
946                  * (doing so could cause a "addled bitmap" message).
947                  */
948                 if (vnp->delete && !*ec) {
949                     if (vnp->volumePtr->header->diskstuff.filecount-- < 1)
950                         vnp->volumePtr->header->diskstuff.filecount = 0;
951                     VFreeBitMapEntry_r(ec, &vp->vnodeIndex[class],
952                                        vnodeIdToBitNumber(vnp->vnodeNumber));
953                 }
954             }
955             vcp->writes++;
956             vnp->changed_newTime = vnp->changed_oldTime = 0;
957         }
958     } else {                    /* Not write locked */
959         if (vnp->changed_newTime || vnp->changed_oldTime || vnp->delete)
960             Abort
961                 ("VPutVnode: Change or delete flag for vnode 0x%x is set but vnode is not write locked!\n",
962                  vnp);
963     }
964
965  done:
966     /* Do not look at disk portion of vnode after this point; it may
967      * have been deleted above */
968     if (vnp->nUsers-- == 1)
969         StickOnLruChain_r(vnp, vcp);
970     vnp->delete = 0;
971
972     if (writeLocked)
973         ReleaseWriteLock(&vnp->lock);
974     else
975         ReleaseReadLock(&vnp->lock);
976 }
977
978 /*
979  * Make an attempt to convert a vnode lock from write to read.
980  * Do nothing if the vnode isn't write locked or the vnode has
981  * been deleted.
982  */
983 int
984 VVnodeWriteToRead(Error * ec, register Vnode * vnp)
985 {
986     int retVal;
987     VOL_LOCK;
988     retVal = VVnodeWriteToRead_r(ec, vnp);
989     VOL_UNLOCK;
990     return retVal;
991 }
992
993 int
994 VVnodeWriteToRead_r(Error * ec, register Vnode * vnp)
995 {
996     int writeLocked;
997     VnodeClass class;
998     struct VnodeClassInfo *vcp;
999     int code;
1000 #ifdef AFS_PTHREAD_ENV
1001     pthread_t thisProcess;
1002 #else /* AFS_PTHREAD_ENV */
1003     PROCESS thisProcess;
1004 #endif /* AFS_PTHREAD_ENV */
1005
1006     *ec = 0;
1007     assert(vnp->nUsers != 0);
1008     class = vnodeIdToClass(vnp->vnodeNumber);
1009     vcp = &VnodeClassInfo[class];
1010     assert(vnp->disk.vnodeMagic == vcp->magic);
1011     writeLocked = WriteLocked(&vnp->lock);
1012     VNLog(300, 2, vnp->vnodeNumber, (afs_int32) vnp);
1013
1014     if (!writeLocked) {
1015         return 0;
1016     }
1017 #ifdef AFS_PTHREAD_ENV
1018     thisProcess = pthread_self();
1019 #else /* AFS_PTHREAD_ENV */
1020     LWP_CurrentProcess(&thisProcess);
1021 #endif /* AFS_PTHREAD_ENV */
1022
1023     VNLog(301, 2, (afs_int32) vnp,
1024           ((vnp->changed_newTime) << 1) | ((vnp->
1025                                             changed_oldTime) << 1) | vnp->
1026           delete);
1027     if (thisProcess != vnp->writer)
1028         Abort("VPutVnode: Vnode at 0x%x locked by another process!\n",
1029               (int)vnp);
1030     if (vnp->delete) {
1031         return 0;
1032     }
1033     if (vnp->changed_oldTime || vnp->changed_newTime) {
1034         Volume *vp = vnp->volumePtr;
1035         afs_uint32 now = FT_ApproxTime();
1036         assert(vnp->cacheCheck == vp->cacheCheck);
1037         if (vnp->changed_newTime)
1038             vnp->disk.serverModifyTime = now;
1039         if (vnp->changed_newTime)
1040             V_updateDate(vp) = vp->updateTime = now;
1041
1042         /* The inode has been changed.  Write it out to disk */
1043         if (!V_inUse(vp)) {
1044 #ifdef AFS_DEMAND_ATTACH_FS
1045             VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
1046             *ec = VSALVAGING;
1047 #else
1048             assert(V_needsSalvaged(vp));
1049             *ec = VSALVAGE;
1050 #endif
1051         } else {
1052             IHandle_t *ihP = vp->vnodeIndex[class].handle;
1053             FdHandle_t *fdP;
1054             off_t off = vnodeIndexOffset(vcp, vnp->vnodeNumber);
1055             VOL_UNLOCK;
1056             fdP = IH_OPEN(ihP);
1057             if (fdP == NULL) {
1058                 Log("VPutVnode: can't open index file!\n");
1059                 goto error_encountered;
1060             }
1061             code = FDH_SEEK(fdP, off, SEEK_SET);
1062             if (code < 0) {
1063                 Log("VPutVnode: can't seek on index file!\n");
1064                 goto error_encountered;
1065             }
1066             code = FDH_WRITE(fdP, &vnp->disk, vcp->diskSize);
1067             if (code != vcp->diskSize) {
1068                 /*
1069                  * Don't force volume offline if the inumber is out of
1070                  * range or the inode table is full.
1071                  */
1072                 VOL_LOCK;
1073                 if (code == BAD_IGET) {
1074                     Log("VPutVnode: bad inumber %s\n",
1075                         PrintInode(NULL,
1076                                    vp->vnodeIndex[class].handle->ih_ino));
1077                     *ec = VIO;
1078                 } else {
1079                     Log("VPutVnode: Couldn't write vnode %u, volume %u (%s)\n", vnp->vnodeNumber, V_id(vnp->volumePtr), V_name(vnp->volumePtr));
1080 #ifdef AFS_DEMAND_ATTACH_FS
1081                     VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
1082                     *ec = VSALVAGING;
1083 #else
1084                     VForceOffline_r(vp, 0);
1085                     *ec = VSALVAGE;
1086 #endif
1087                 }
1088                 VOL_UNLOCK;
1089             }
1090             FDH_CLOSE(fdP);
1091             VOL_LOCK;
1092             goto sane;
1093
1094         error_encountered:
1095 #ifdef AFS_DEMAND_ATTACH_FS
1096             if (fdP)
1097                 FDH_CLOSE(fdP);
1098             VOL_LOCK;
1099             VRequestSalvage_r(vp, SALVSYNC_ERROR, 0);
1100             *ec = VSALVAGING;
1101 #else
1102             assert(1 == 2);
1103 #endif
1104
1105         }
1106     sane:
1107         vcp->writes++;
1108         vnp->changed_newTime = vnp->changed_oldTime = 0;
1109     }
1110
1111     ConvertWriteToReadLock(&vnp->lock);
1112     return 0;
1113 }
1114
1115 /* Move the vnode, vnp, to the new hash table given by the
1116    hash table index, newHash */
1117 static int
1118 moveHash(register Vnode * vnp, bit32 newHash)
1119 {
1120     Vnode *tvnp;
1121     /* Remove it from the old hash chain */
1122     tvnp = VnodeHashTable[vnp->hashIndex];
1123     if (tvnp == vnp)
1124         VnodeHashTable[vnp->hashIndex] = vnp->hashNext;
1125     else {
1126         while (tvnp && tvnp->hashNext != vnp)
1127             tvnp = tvnp->hashNext;
1128         if (tvnp)
1129             tvnp->hashNext = vnp->hashNext;
1130     }
1131     /* Add it to the new hash chain */
1132     vnp->hashNext = VnodeHashTable[newHash];
1133     VnodeHashTable[newHash] = vnp;
1134     vnp->hashIndex = newHash;
1135     return 0;
1136 }
1137
1138 private void
1139 StickOnLruChain_r(register Vnode * vnp, register struct VnodeClassInfo *vcp)
1140 {
1141     /* Add it to the circular LRU list */
1142     if (vcp->lruHead == NULL)
1143         Abort("VPutVnode: vcp->lruHead==NULL");
1144     else {
1145         vnp->lruNext = vcp->lruHead;
1146         vnp->lruPrev = vcp->lruHead->lruPrev;
1147         vcp->lruHead->lruPrev = vnp;
1148         vnp->lruPrev->lruNext = vnp;
1149         vcp->lruHead = vnp;
1150     }
1151     /* If the vnode was just deleted, put it at the end of the chain so it
1152      * will be reused immediately */
1153     if (vnp->delete)
1154         vcp->lruHead = vnp->lruNext;
1155     /* If caching is turned off, set volumeptr to NULL to invalidate the
1156      * entry */
1157     if (!TrustVnodeCacheEntry) {
1158         DeleteFromVnHashByVolumeTable(vnp);
1159         vnp->volumePtr = NULL;
1160     }
1161 }
1162
1163 /* VCloseVnodeFiles - called when a volume is going off line. All open
1164  * files for vnodes in that volume are closed. This might be excessive,
1165  * since we may only be taking one volume of a volume group offline.
1166  */
1167 void
1168 VCloseVnodeFiles_r(Volume * vp)
1169 {
1170     int i;
1171     Vnode *vnp, *nvnp;
1172     VnodeHashByVolumeChainHead * head;
1173
1174     head = &VnodeHashByVolumeTable[VNVOLUME_HASH(vp->hashid)];
1175 #ifdef AFS_DEMAND_ATTACH_FS
1176     while (head->busy) {
1177         assert(pthread_cond_wait(&head->chain_busy_cv, &vol_glock_mutex) == 0);
1178     }
1179
1180     head->busy = 1;
1181     VOL_UNLOCK;
1182 #endif /* AFS_DEMAND_ATTACH_FS */
1183
1184     for (queue_Scan(head, vnp, nvnp, Vnode)) {
1185         if (vnp->volumePtr == vp) {
1186             IH_REALLYCLOSE(vnp->handle);
1187         }
1188     }
1189
1190 #ifdef AFS_DEMAND_ATTACH_FS
1191     VOL_LOCK;
1192     head->busy = 0;
1193     assert(pthread_cond_broadcast(&head->chain_busy_cv) == 0);
1194 #endif /* AFS_DEMAND_ATTACH_FS */
1195 }
1196
1197 /* VReleaseVnodeFiles - called when a volume is going detached. All open
1198  * files for vnodes in that volume are closed and all inode handles
1199  * for vnodes in that volume are released.
1200  */
1201 void
1202 VReleaseVnodeFiles_r(Volume * vp)
1203 {
1204     int i;
1205     Vnode *vnp, *nvnp;
1206     VnodeHashByVolumeChainHead * head;
1207
1208     head = &VnodeHashByVolumeTable[VNVOLUME_HASH(vp->hashid)];
1209
1210 #ifdef AFS_DEMAND_ATTACH_FS
1211     while (head->busy) {
1212         assert(pthread_cond_wait(&head->chain_busy_cv, &vol_glock_mutex) == 0);
1213     }
1214
1215     head->busy = 1;
1216     VOL_UNLOCK;
1217 #endif /* AFS_DEMAND_ATTACH_FS */
1218
1219     for (queue_Scan(head, vnp, nvnp, Vnode)) {
1220         if (vnp->volumePtr == vp) {
1221             IH_RELEASE(vnp->handle);
1222         }
1223     }
1224
1225 #ifdef AFS_DEMAND_ATTACH_FS
1226     VOL_LOCK;
1227     head->busy = 0;
1228     assert(pthread_cond_broadcast(&head->chain_busy_cv) == 0);
1229 #endif /* AFS_DEMAND_ATTACH_FS */
1230 }