dir: Fix DRead
[openafs.git] / src / dir / buffer.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 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 #include <roken.h>
14
15 #include <lock.h>
16
17 #include "dir.h"
18
19 #ifdef AFS_64BIT_IOPS_ENV
20 #define BUFFER_FID_SIZE (9*sizeof(int) + 2*sizeof(char*))
21 #else
22 #define BUFFER_FID_SIZE (6*sizeof(int) + 2*sizeof(char*))
23 #endif
24
25 struct buffer {
26     /* fid is used for Unique cache key + i/o addressing.
27      * fid size is based on 4 + size of inode and size of pointer
28      */
29     char fid[BUFFER_FID_SIZE];
30     afs_int32 page;
31     afs_int32 accesstime;
32     struct buffer *hashNext;
33     void *data;
34     char lockers;
35     char dirty;
36     char hashIndex;
37     struct Lock lock;
38 };
39
40 static_inline dir_file_t
41 bufferDir(struct buffer *b)
42 {
43     return (dir_file_t) &b->fid;
44 }
45
46 struct Lock afs_bufferLock;
47
48 /* page size */
49 #define BUFFER_PAGE_SIZE 2048
50 /* log page size */
51 #define LOGPS 11
52 /* page hash table size */
53 #define PHSIZE 32
54 /* The hash table should be somewhat efficient even if there are only
55  * a few partitions (less than 32). So the hash for the fileserver is now
56  * based on the volume id. This means this macro is dependent upon the
57  * layout of DirHandle in viced/viced.h, vol/salvage.h and volser/salvage.h.
58  */
59 #define pHash(fid) (((afs_int32 *)fid)[0] & (PHSIZE-1))
60 #define vHash(vid) (vid & (PHSIZE-1))
61
62 /* admittedly system dependent, this is the maximum signed 32-bit value */
63 #define BUFFER_LONG_MAX   2147483647
64 #ifndef NULL
65 #define NULL 0
66 #endif
67
68 static struct buffer **Buffers;
69
70 char *BufferData;
71
72 static struct buffer *phTable[PHSIZE];  /* page hash table */
73 static struct buffer *LastBuffer;
74 int nbuffers;
75 int timecounter;
76 static int calls = 0, ios = 0;
77
78 struct buffer *newslot(dir_file_t dir, afs_int32 apage,
79                        struct buffer *lp);
80
81 /* XXX - This sucks. The correct prototypes for these functions are ...
82  *
83  * extern void FidZero(DirHandle *);
84  * extern int  FidEq(DirHandle *a, DirHandle *b);
85  * extern int  ReallyRead(DirHandle *a, int block, char *data);
86  */
87
88 extern void FidZero(dir_file_t);
89 extern int FidEq(dir_file_t, dir_file_t);
90 extern int ReallyRead(dir_file_t, int block, char *data);
91 extern int ReallyWrite(dir_file_t, int block, char *data);
92 extern void FidZap(dir_file_t);
93 extern int  FidVolEq(dir_file_t, afs_int32 vid);
94 extern void FidCpy(dir_file_t, dir_file_t fromfile);
95
96 extern void Die(char *msg);
97
98 int
99 DStat(int *abuffers, int *acalls, int *aios)
100 {
101     *abuffers = nbuffers;
102     *acalls = calls;
103     *aios = ios;
104     return 0;
105 }
106
107 /**
108  * initialize the directory package.
109  *
110  * @param[in] abuffers  size of directory buffer cache
111  *
112  * @return operation status
113  *    @retval 0 success
114  */
115 void
116 DInit(int abuffers)
117 {
118     /* Initialize the venus buffer system. */
119     int i, tsize;
120     struct buffer *tb;
121     char *tp;
122
123     Lock_Init(&afs_bufferLock);
124     /* Align each element of Buffers on a doubleword boundary */
125     tsize = (sizeof(struct buffer) + 7) & ~7;
126     tp = (char *)malloc(abuffers * tsize);
127     Buffers = (struct buffer **)malloc(abuffers * sizeof(struct buffer *));
128     BufferData = (char *)malloc(abuffers * BUFFER_PAGE_SIZE);
129     timecounter = 0;
130     LastBuffer = (struct buffer *)tp;
131     nbuffers = abuffers;
132     for (i = 0; i < PHSIZE; i++)
133         phTable[i] = 0;
134     for (i = 0; i < abuffers; i++) {
135         /* Fill in each buffer with an empty indication. */
136         tb = (struct buffer *)tp;
137         Buffers[i] = tb;
138         tp += tsize;
139         FidZero(bufferDir(tb));
140         tb->accesstime = tb->lockers = 0;
141         tb->data = &BufferData[BUFFER_PAGE_SIZE * i];
142         tb->hashIndex = 0;
143         tb->dirty = 0;
144         Lock_Init(&tb->lock);
145     }
146     return;
147 }
148
149 /**
150  * read a page out of a directory object.
151  *
152  * @param[in] fid   directory object fid
153  * @param[in] page  page in hash table to be read
154  *
155  * @return pointer to requested page in directory cache
156  *    @retval NULL read failed
157  */
158 int
159 DRead(dir_file_t fid, int page, struct DirBuffer *entry)
160 {
161     /* Read a page from the disk. */
162     struct buffer *tb, *tb2, **bufhead;
163
164     memset(entry, 0, sizeof(struct DirBuffer));
165
166     ObtainWriteLock(&afs_bufferLock);
167     calls++;
168
169 #define bufmatch(tb,fid) (tb->page == page && FidEq(bufferDir(tb), fid))
170 #define buf_Front(head,parent,p) {(parent)->hashNext = (p)->hashNext; (p)->hashNext= *(head);*(head)=(p);}
171
172     /* this apparently-complicated-looking code is simply an example of
173      * a little bit of loop unrolling, and is a standard linked-list
174      * traversal trick. It saves a few assignments at the the expense
175      * of larger code size.  This could be simplified by better use of
176      * macros.  With the use of these LRU queues, the old one-cache is
177      * probably obsolete.
178      */
179     if ((tb = phTable[pHash(fid)])) {   /* ASSMT HERE */
180         if (bufmatch(tb, fid)) {
181             ObtainWriteLock(&tb->lock);
182             tb->lockers++;
183             ReleaseWriteLock(&afs_bufferLock);
184             tb->accesstime = ++timecounter;
185             ReleaseWriteLock(&tb->lock);
186             entry->buffer = tb;
187             entry->data = tb->data;
188             return 0;
189         } else {
190             bufhead = &(phTable[pHash(fid)]);
191             while ((tb2 = tb->hashNext)) {
192                 if (bufmatch(tb2, fid)) {
193                     buf_Front(bufhead, tb, tb2);
194                     ObtainWriteLock(&tb2->lock);
195                     tb2->lockers++;
196                     ReleaseWriteLock(&afs_bufferLock);
197                     tb2->accesstime = ++timecounter;
198                     ReleaseWriteLock(&tb2->lock);
199                     entry->buffer = tb2;
200                     entry->data = tb2->data;
201                     return 0;
202                 }
203                 if ((tb = tb2->hashNext)) {     /* ASSIGNMENT HERE! */
204                     if (bufmatch(tb, fid)) {
205                         buf_Front(bufhead, tb2, tb);
206                         ObtainWriteLock(&tb->lock);
207                         tb->lockers++;
208                         ReleaseWriteLock(&afs_bufferLock);
209                         tb->accesstime = ++timecounter;
210                         ReleaseWriteLock(&tb->lock);
211                         entry->buffer = tb;
212                         entry->data = tb->data;
213                         return 0;
214                     }
215                 } else
216                     break;
217             }
218         }
219     } else
220         tb2 = NULL;
221
222     /* can't find it */
223     /* The last thing we looked at was either tb or tb2 (or nothing). That
224      * is at least the oldest buffer on one particular hash chain, so it's
225      * a pretty good place to start looking for the truly oldest buffer.
226      */
227     tb = newslot(fid, page, (tb ? tb : tb2));
228     ios++;
229     ObtainWriteLock(&tb->lock);
230     tb->lockers++;
231     ReleaseWriteLock(&afs_bufferLock);
232     if (ReallyRead(bufferDir(tb), tb->page, tb->data)) {
233         tb->lockers--;
234         FidZap(bufferDir(tb));  /* disaster */
235         ReleaseWriteLock(&tb->lock);
236         return EIO;
237     }
238     /* Note that findslot sets the page field in the buffer equal to
239      * what it is searching for.
240      */
241     ReleaseWriteLock(&tb->lock);
242     entry->buffer = tb;
243     entry->data = tb->data;
244     return 0;
245 }
246
247
248 static int
249 FixupBucket(struct buffer *ap)
250 {
251     struct buffer **lp, *tp;
252     int i;
253
254     /* first try to get it out of its current hash bucket, in which it might not be */
255     i = ap->hashIndex;
256     lp = &phTable[i];
257     for (tp = *lp; tp; tp = tp->hashNext) {
258         if (tp == ap) {
259             *lp = tp->hashNext;
260             break;
261         }
262         lp = &tp->hashNext;
263     }
264     /* now figure the new hash bucket */
265     i = pHash(ap);
266     ap->hashIndex = i;          /* remember where we are for deletion */
267     ap->hashNext = phTable[i];  /* add us to the list */
268     phTable[i] = ap;            /* at the front, since it's LRU */
269     return 0;
270 }
271
272 struct buffer *
273 newslot(dir_file_t dir, afs_int32 apage, struct buffer *lp)
274 {
275     /* Find a usable buffer slot */
276     afs_int32 i;
277     afs_int32 lt;
278     struct buffer **tbp;
279
280     if (lp && (lp->lockers == 0)) {
281         lt = lp->accesstime;
282     } else {
283         lp = 0;
284         lt = BUFFER_LONG_MAX;
285     }
286
287     tbp = Buffers;
288     for (i = 0; i < nbuffers; i++, tbp++) {
289         if ((*tbp)->lockers == 0) {
290             if ((*tbp)->accesstime < lt) {
291                 lp = (*tbp);
292                 lt = (*tbp)->accesstime;
293             }
294         }
295     }
296
297     /* There are no unlocked buffers */
298     if (lp == 0) {
299         if (lt < 0)
300             Die("accesstime counter wrapped");
301         else
302             Die("all buffers locked");
303     }
304
305     /* We do not need to lock the buffer here because it has no lockers
306      * and the afs_bufferLock prevents other threads from zapping this
307      * buffer while we are writing it out */
308     if (lp->dirty) {
309         if (ReallyWrite(bufferDir(lp), lp->page, lp->data))
310             Die("writing bogus buffer");
311         lp->dirty = 0;
312     }
313
314     /* Now fill in the header. */
315     FidZap(bufferDir(lp));
316     FidCpy(bufferDir(lp), dir); /* set this */
317     lp->page = apage;
318     lp->accesstime = ++timecounter;
319
320     FixupBucket(lp);            /* move to the right hash bucket */
321
322     return lp;
323 }
324
325 /* Release a buffer, specifying whether or not the buffer has been modified
326  * by the locker. */
327 void
328 DRelease(struct DirBuffer *entry, int flag)
329 {
330     struct buffer *bp;
331
332     bp = (struct buffer *) entry->buffer;
333     if (bp == NULL)
334         return;
335     ObtainWriteLock(&bp->lock);
336     bp->lockers--;
337     if (flag)
338         bp->dirty = 1;
339     ReleaseWriteLock(&bp->lock);
340 }
341
342 /* Return the byte within a file represented by a buffer pointer. */
343 int
344 DVOffset(struct DirBuffer *entry)
345 {
346     struct buffer *bp;
347
348     bp = entry->buffer;
349     return BUFFER_PAGE_SIZE * bp->page + (char *)entry->data - (char *)bp->data;
350 }
351
352 void
353 DZap(dir_file_t dir)
354 {
355     /* Destroy all buffers pertaining to a particular fid. */
356     struct buffer *tb;
357     ObtainReadLock(&afs_bufferLock);
358     for (tb = phTable[pHash(dir)]; tb; tb = tb->hashNext)
359         if (FidEq(bufferDir(tb), dir)) {
360             ObtainWriteLock(&tb->lock);
361             FidZap(bufferDir(tb));
362             tb->dirty = 0;
363             ReleaseWriteLock(&tb->lock);
364         }
365     ReleaseReadLock(&afs_bufferLock);
366 }
367
368 int
369 DFlushVolume(afs_int32 vid)
370 {
371     /* Flush all data and release all inode handles for a particular volume */
372     struct buffer *tb;
373     int code, rcode = 0;
374     ObtainReadLock(&afs_bufferLock);
375     for (tb = phTable[vHash(vid)]; tb; tb = tb->hashNext)
376         if (FidVolEq(bufferDir(tb), vid)) {
377             ObtainWriteLock(&tb->lock);
378             if (tb->dirty) {
379                 code = ReallyWrite(bufferDir(tb), tb->page, tb->data);
380                 if (code && !rcode)
381                     rcode = code;
382                 tb->dirty = 0;
383             }
384             FidZap(bufferDir(tb));
385             ReleaseWriteLock(&tb->lock);
386         }
387     ReleaseReadLock(&afs_bufferLock);
388     return rcode;
389 }
390
391 int
392 DFlushEntry(dir_file_t fid)
393 {
394     /* Flush pages modified by one entry. */
395     struct buffer *tb;
396     int code;
397
398     ObtainReadLock(&afs_bufferLock);
399     for (tb = phTable[pHash(fid)]; tb; tb = tb->hashNext)
400         if (FidEq(bufferDir(tb), fid) && tb->dirty) {
401             ObtainWriteLock(&tb->lock);
402             if (tb->dirty) {
403                 code = ReallyWrite(bufferDir(tb), tb->page, tb->data);
404                 if (code) {
405                     ReleaseWriteLock(&tb->lock);
406                     ReleaseReadLock(&afs_bufferLock);
407                     return code;
408                 }
409                 tb->dirty = 0;
410             }
411             ReleaseWriteLock(&tb->lock);
412         }
413     ReleaseReadLock(&afs_bufferLock);
414     return 0;
415 }
416
417 int
418 DFlush(void)
419 {
420     /* Flush all the modified buffers. */
421     int i;
422     struct buffer **tbp;
423     afs_int32 code, rcode;
424
425     rcode = 0;
426     tbp = Buffers;
427     ObtainReadLock(&afs_bufferLock);
428     for (i = 0; i < nbuffers; i++, tbp++) {
429         if ((*tbp)->dirty) {
430             ObtainWriteLock(&(*tbp)->lock);
431             (*tbp)->lockers++;
432             ReleaseReadLock(&afs_bufferLock);
433             if ((*tbp)->dirty) {
434                 code = ReallyWrite(bufferDir(*tbp), (*tbp)->page, (*tbp)->data);
435                 if (!code)
436                     (*tbp)->dirty = 0;  /* Clear the dirty flag */
437                 if (code && !rcode) {
438                     rcode = code;
439                 }
440             }
441             (*tbp)->lockers--;
442             ReleaseWriteLock(&(*tbp)->lock);
443             ObtainReadLock(&afs_bufferLock);
444         }
445     }
446     ReleaseReadLock(&afs_bufferLock);
447     return rcode;
448 }
449
450 /* Same as read, only do *not* even try to read the page,
451  * since it probably doesn't exist.
452  */
453 int
454 DNew(dir_file_t dir, int page, struct DirBuffer *entry)
455 {
456     struct buffer *tb;
457
458     memset(entry,0, sizeof(struct DirBuffer));
459
460     ObtainWriteLock(&afs_bufferLock);
461     if ((tb = newslot(dir, page, 0)) == 0) {
462         ReleaseWriteLock(&afs_bufferLock);
463         return EIO;
464     }
465     ObtainWriteLock(&tb->lock);
466     tb->lockers++;
467     ReleaseWriteLock(&afs_bufferLock);
468     ReleaseWriteLock(&tb->lock);
469
470     entry->buffer = tb;
471     entry->data = tb->data;
472
473     return 0;
474 }