libafs: Tidy up iovec allocation and trimming
[openafs.git] / src / afs / VNOPS / afs_vnop_read.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 /*
11  * Implements:
12  * afs_MemRead
13  * afs_PrefetchChunk
14  * afs_UFSRead
15  * 
16  */
17
18 #include <afsconfig.h>
19 #include "afs/param.h"
20
21
22 #include "afs/sysincludes.h"    /* Standard vendor system headers */
23 #include "afsincludes.h"        /* Afs-based standard headers */
24 #include "afs/afs_stats.h"      /* statistics */
25 #include "afs/afs_cbqueue.h"
26 #include "afs/nfsclient.h"
27 #include "afs/afs_osidnlc.h"
28 #include "afs/afs_osi.h"
29
30
31 extern char afs_zeros[AFS_ZEROS];
32
33 /* Imported variables */
34 extern afs_rwlock_t afs_xdcache;
35 extern unsigned char *afs_indexFlags;
36 extern afs_hyper_t *afs_indexTimes;     /* Dcache entry Access times */
37 extern afs_hyper_t afs_indexCounter;    /* Fake time for marking index */
38
39
40 /* Forward declarations */
41 void afs_PrefetchChunk(struct vcache *avc, struct dcache *adc,
42                        afs_ucred_t *acred, struct vrequest *areq);
43
44 int
45 afs_read(struct vcache *avc, struct uio *auio, afs_ucred_t *acred,
46          int noLock)
47 {
48     afs_size_t totalLength;
49     afs_size_t transferLength;
50     afs_size_t filePos;
51     afs_size_t offset, len, tlen;
52     afs_int32 trimlen;
53     struct dcache *tdc = 0;
54     afs_int32 error, trybusy = 1;
55     struct uio *tuiop = NULL;
56     afs_int32 code;
57     struct vrequest treq;
58
59     AFS_STATCNT(afs_read);
60
61     if (avc->vc_error)
62         return EIO;
63
64     AFS_DISCON_LOCK();
65
66     /* check that we have the latest status info in the vnode cache */
67     if ((code = afs_InitReq(&treq, acred)))
68         goto out;
69
70     if (!noLock) {
71         if (!avc)
72             osi_Panic("null avc in afs_GenericRead");
73
74         code = afs_VerifyVCache(avc, &treq);
75         if (code) {
76             code = afs_CheckCode(code, &treq, 8);       /* failed to get it */
77             goto out;
78         }
79     }
80 #ifndef AFS_VM_RDWR_ENV
81     if (AFS_NFSXLATORREQ(acred)) {
82         if (!afs_AccessOK
83             (avc, PRSFS_READ, &treq,
84              CHECK_MODE_BITS | CMB_ALLOW_EXEC_AS_READ)) {
85             code = afs_CheckCode(EACCES, &treq, 9);
86             goto out;
87         }
88     }
89 #endif
90
91     totalLength = AFS_UIO_RESID(auio);
92     filePos = AFS_UIO_OFFSET(auio);
93     afs_Trace4(afs_iclSetp, CM_TRACE_READ, ICL_TYPE_POINTER, avc,
94                ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(filePos), ICL_TYPE_INT32,
95                totalLength, ICL_TYPE_OFFSET,
96                ICL_HANDLE_OFFSET(avc->f.m.Length));
97     error = 0;
98     transferLength = 0;
99     if (!noLock)
100         ObtainReadLock(&avc->lock);
101 #if     defined(AFS_TEXT_ENV) && !defined(AFS_VM_RDWR_ENV)
102     if (avc->flushDV.high == AFS_MAXDV && avc->flushDV.low == AFS_MAXDV) {
103         hset(avc->flushDV, avc->f.m.DataVersion);
104     }
105 #endif
106
107     /*
108      * Locks held:
109      * avc->lock(R)
110      */
111     if (filePos >= avc->f.m.Length) {
112         if (len > AFS_ZEROS)
113             len = sizeof(afs_zeros);    /* and in 0 buffer */
114         len = 0;
115         trimlen = len;
116         tuiop = afsio_partialcopy(auio, trimlen);
117         AFS_UIOMOVE(afs_zeros, trimlen, UIO_READ, tuiop, code);
118     }
119
120     while (avc->f.m.Length > 0 && totalLength > 0) {
121         /* read all of the cached info */
122         if (filePos >= avc->f.m.Length)
123             break;              /* all done */
124         if (noLock) {
125             if (tdc) {
126                 ReleaseReadLock(&tdc->lock);
127                 afs_PutDCache(tdc);
128             }
129             tdc = afs_FindDCache(avc, filePos);
130             if (tdc) {
131                 ObtainReadLock(&tdc->lock);
132                 offset = filePos - AFS_CHUNKTOBASE(tdc->f.chunk);
133                 len = tdc->validPos - filePos;
134             }
135         } else {
136             /* a tricky question: does the presence of the DFFetching flag
137              * mean that we're fetching the latest version of the file?  No.
138              * The server could update the file as soon as the fetch responsible
139              * for the setting of the DFFetching flag completes.
140              * 
141              * However, the presence of the DFFetching flag (visible under
142              * a dcache read lock since it is set and cleared only under a
143              * dcache write lock) means that we're fetching as good a version
144              * as was known to this client at the time of the last call to
145              * afs_VerifyVCache, since the latter updates the stat cache's
146              * m.DataVersion field under a vcache write lock, and from the
147              * time that the DFFetching flag goes on in afs_GetDCache (before
148              * the fetch starts), to the time it goes off (after the fetch
149              * completes), afs_GetDCache keeps at least a read lock on the
150              * vcache entry.
151              * 
152              * This means that if the DFFetching flag is set, we can use that
153              * data for any reads that must come from the current version of
154              * the file (current == m.DataVersion).
155              * 
156              * Another way of looking at this same point is this: if we're
157              * fetching some data and then try do an afs_VerifyVCache, the
158              * VerifyVCache operation will not complete until after the
159              * DFFetching flag is turned off and the dcache entry's f.versionNo
160              * field is updated.
161              * 
162              * Note, by the way, that if DFFetching is set,
163              * m.DataVersion > f.versionNo (the latter is not updated until
164              * after the fetch completes).
165              */
166             if (tdc) {
167                 ReleaseReadLock(&tdc->lock);
168                 afs_PutDCache(tdc);     /* before reusing tdc */
169             }
170             tdc = afs_GetDCache(avc, filePos, &treq, &offset, &len, 2);
171             if (!tdc) {
172                 error = ENETDOWN;
173                 break;
174             }
175
176             ObtainReadLock(&tdc->lock);
177             /* now, first try to start transfer, if we'll need the data.  If
178              * data already coming, we don't need to do this, obviously.  Type
179              * 2 requests never return a null dcache entry, btw.
180              */
181             if (!(tdc->dflags & DFFetching)
182                 && !hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
183                 /* have cache entry, it is not coming in now,
184                  * and we'll need new data */
185               tagain:
186                 if (trybusy && !afs_BBusy()) {
187                     struct brequest *bp;
188                     /* daemon is not busy */
189                     ObtainSharedLock(&tdc->mflock, 665);
190                     if (!(tdc->mflags & DFFetchReq)) {
191                         /* start the daemon (may already be running, however) */
192                         UpgradeSToWLock(&tdc->mflock, 666);
193                         tdc->mflags |= DFFetchReq;
194                         bp = afs_BQueue(BOP_FETCH, avc, B_DONTWAIT, 0, acred,
195                                         (afs_size_t) filePos, (afs_size_t) 0,
196                                         tdc, NULL, NULL);
197                         if (!bp) {
198                             /* Bkg table full; retry deadlocks */
199                             tdc->mflags &= ~DFFetchReq;
200                             trybusy = 0;        /* Avoid bkg daemon since they're too busy */
201                             ReleaseWriteLock(&tdc->mflock);
202                             goto tagain;
203                         }
204                         ConvertWToSLock(&tdc->mflock);
205                         /* don't use bp pointer! */
206                     }
207                     code = 0;
208                     ConvertSToRLock(&tdc->mflock);
209                     while (!code && tdc->mflags & DFFetchReq) {
210                         afs_Trace4(afs_iclSetp, CM_TRACE_DCACHEWAIT,
211                                    ICL_TYPE_STRING, __FILE__, ICL_TYPE_INT32,
212                                    __LINE__, ICL_TYPE_POINTER, tdc,
213                                    ICL_TYPE_INT32, tdc->dflags);
214                         /* don't need waiting flag on this one */
215                         ReleaseReadLock(&tdc->mflock);
216                         ReleaseReadLock(&tdc->lock);
217                         ReleaseReadLock(&avc->lock);
218                         code = afs_osi_SleepSig(&tdc->validPos);
219                         ObtainReadLock(&avc->lock);
220                         ObtainReadLock(&tdc->lock);
221                         ObtainReadLock(&tdc->mflock);
222                     }
223                     ReleaseReadLock(&tdc->mflock);
224                     if (code) {
225                         error = code;
226                         break;
227                     }
228                 }
229             }
230             /* now data may have started flowing in (if DFFetching is on).  If
231              * data is now streaming in, then wait for some interesting stuff.
232              */
233             code = 0;
234             while (!code && (tdc->dflags & DFFetching)
235                    && tdc->validPos <= filePos) {
236                 /* too early: wait for DFFetching flag to vanish,
237                  * or data to appear */
238                 afs_Trace4(afs_iclSetp, CM_TRACE_DCACHEWAIT, ICL_TYPE_STRING,
239                            __FILE__, ICL_TYPE_INT32, __LINE__,
240                            ICL_TYPE_POINTER, tdc, ICL_TYPE_INT32,
241                            tdc->dflags);
242                 ReleaseReadLock(&tdc->lock);
243                 ReleaseReadLock(&avc->lock);
244                 code = afs_osi_SleepSig(&tdc->validPos);
245                 ObtainReadLock(&avc->lock);
246                 ObtainReadLock(&tdc->lock);
247             }
248             if (code) {
249                 error = code;
250                 break;
251             }
252             /* fetching flag gone, data is here, or we never tried 
253              * (BBusy for instance) */
254             if (tdc->dflags & DFFetching) {
255                 /* still fetching, some new data is here: 
256                  * compute length and offset */
257                 offset = filePos - AFS_CHUNKTOBASE(tdc->f.chunk);
258                 len = tdc->validPos - filePos;
259             } else {
260                 /* no longer fetching, verify data version 
261                  * (avoid new GetDCache call) */
262                 if (hsame(avc->f.m.DataVersion, tdc->f.versionNo)
263                     && ((len = tdc->validPos - filePos) > 0)) {
264                     offset = filePos - AFS_CHUNKTOBASE(tdc->f.chunk);
265                 } else {
266                     /* don't have current data, so get it below */
267                     afs_Trace3(afs_iclSetp, CM_TRACE_VERSIONNO,
268                                ICL_TYPE_INT64, ICL_HANDLE_OFFSET(filePos),
269                                ICL_TYPE_HYPER, &avc->f.m.DataVersion,
270                                ICL_TYPE_HYPER, &tdc->f.versionNo);
271                     ReleaseReadLock(&tdc->lock);
272                     afs_PutDCache(tdc);
273                     tdc = NULL;
274                 }
275             }
276
277             if (!tdc) {
278                 /* If we get, it was not possible to start the
279                  * background daemon. With flag == 1 afs_GetDCache
280                  * does the FetchData rpc synchronously.
281                  */
282                 ReleaseReadLock(&avc->lock);
283                 tdc = afs_GetDCache(avc, filePos, &treq, &offset, &len, 1);
284                 ObtainReadLock(&avc->lock);
285                 if (tdc)
286                     ObtainReadLock(&tdc->lock);
287             }
288         }
289
290         afs_Trace3(afs_iclSetp, CM_TRACE_VNODEREAD, ICL_TYPE_POINTER, tdc,
291                    ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(offset),
292                    ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(len));
293         if (!tdc) {
294             error = EIO;
295             break;
296         }
297
298         /*
299          * Locks held:
300          * avc->lock(R)
301          * tdc->lock(R)
302          */
303
304         if (len > totalLength)
305             len = totalLength;  /* will read len bytes */
306         if (len <= 0) {         /* shouldn't get here if DFFetching is on */
307             /* read past the end of a chunk, may not be at next chunk yet, and yet
308              * also not at eof, so may have to supply fake zeros */
309             len = AFS_CHUNKTOSIZE(tdc->f.chunk) - offset;       /* bytes left in chunk addr space */
310             if (len > totalLength)
311                 len = totalLength;      /* and still within xfr request */
312             tlen = avc->f.m.Length - offset;    /* and still within file */
313             if (len > tlen)
314                 len = tlen;
315             if (len > AFS_ZEROS)
316                 len = sizeof(afs_zeros);        /* and in 0 buffer */
317             trimlen = len;
318             tuiop = afsio_partialcopy(auio, trimlen);
319             AFS_UIOMOVE(afs_zeros, trimlen, UIO_READ, tuiop, code);
320             if (code) {
321                 error = code;
322                 break;
323             }
324         } else {
325             /* get the data from the cache */
326
327             /* mung uio structure to be right for this transfer */
328             trimlen = len;
329             tuiop = afsio_partialcopy(auio, trimlen);
330             AFS_UIO_SETOFFSET(tuiop, offset);
331
332             code = (*(afs_cacheType->vreadUIO))(&tdc->f.inode, tuiop);
333
334             if (code) {
335                 error = code;
336                 break;
337             }
338         }
339         /* otherwise we've read some, fixup length, etc and continue with next seg */
340         len = len - AFS_UIO_RESID(tuiop);       /* compute amount really transferred */
341         trimlen = len;
342         afsio_skip(auio, trimlen);      /* update input uio structure */
343         totalLength -= len;
344         transferLength += len;
345         filePos += len;
346
347         if (len <= 0)
348             break;              /* surprise eof */
349         if (tuiop) {
350             afsio_free(tuiop);
351             tuiop = NULL;
352         }
353     }                           /* the whole while loop */
354
355     /*
356      * Locks held:
357      * avc->lock(R)
358      * tdc->lock(R) if tdc
359      */
360
361     /* if we make it here with tdc non-zero, then it is the last chunk we
362      * dealt with, and we have to release it when we're done.  We hold on
363      * to it in case we need to do a prefetch.
364      */
365     if (tdc) {
366         ReleaseReadLock(&tdc->lock);
367 #if !defined(AFS_VM_RDWR_ENV)
368         /* try to queue prefetch, if needed */
369         if (!noLock) {
370             if (!(tdc->mflags &DFNextStarted))
371                 afs_PrefetchChunk(avc, tdc, acred, &treq);
372         }
373 #endif
374         afs_PutDCache(tdc);
375     }
376     if (!noLock)
377         ReleaseReadLock(&avc->lock);
378
379     code = afs_CheckCode(error, &treq, 10);
380
381     if (tuiop)
382        afsio_free(tuiop);
383
384 out:
385     AFS_DISCON_UNLOCK();
386     return code;
387 }
388
389 /* called with the dcache entry triggering the fetch, the vcache entry involved,
390  * and a vrequest for the read call.  Marks the dcache entry as having already
391  * triggered a prefetch, starts the prefetch going and sets the DFFetchReq
392  * flag in the prefetched block, so that the next call to read knows to wait
393  * for the daemon to start doing things.
394  *
395  * This function must be called with the vnode at least read-locked, and
396  * no locks on the dcache, because it plays around with dcache entries.
397  */
398 void
399 afs_PrefetchChunk(struct vcache *avc, struct dcache *adc,
400                   afs_ucred_t *acred, struct vrequest *areq)
401 {
402     struct dcache *tdc;
403     afs_size_t offset;
404     afs_size_t j1, j2;          /* junk vbls for GetDCache to trash */
405
406     offset = adc->f.chunk + 1;  /* next chunk we'll need */
407     offset = AFS_CHUNKTOBASE(offset);   /* base of next chunk */
408     ObtainReadLock(&adc->lock);
409     ObtainSharedLock(&adc->mflock, 662);
410     if (offset < avc->f.m.Length && !(adc->mflags & DFNextStarted)
411         && !afs_BBusy()) {
412         struct brequest *bp;
413
414         UpgradeSToWLock(&adc->mflock, 663);
415         adc->mflags |= DFNextStarted;   /* we've tried to prefetch for this guy */
416         ReleaseWriteLock(&adc->mflock);
417         ReleaseReadLock(&adc->lock);
418
419         tdc = afs_GetDCache(avc, offset, areq, &j1, &j2, 2);    /* type 2 never returns 0 */
420         /*
421          * In disconnected mode, type 2 can return 0 because it doesn't
422          * make any sense to allocate a dcache we can never fill
423          */
424          if (tdc == NULL)
425              return;
426
427         ObtainSharedLock(&tdc->mflock, 651);
428         if (!(tdc->mflags & DFFetchReq)) {
429             /* ask the daemon to do the work */
430             UpgradeSToWLock(&tdc->mflock, 652);
431             tdc->mflags |= DFFetchReq;  /* guaranteed to be cleared by BKG or GetDCache */
432             /* last parm (1) tells bkg daemon to do an afs_PutDCache when it is done,
433              * since we don't want to wait for it to finish before doing so ourselves.
434              */
435             bp = afs_BQueue(BOP_FETCH, avc, B_DONTWAIT, 0, acred,
436                             (afs_size_t) offset, (afs_size_t) 1, tdc,
437                             (void *)0, (void *)0);
438             if (!bp) {
439                 /* Bkg table full; just abort non-important prefetching to avoid deadlocks */
440                 tdc->mflags &= ~DFFetchReq;
441                 ReleaseWriteLock(&tdc->mflock);
442                 afs_PutDCache(tdc);
443
444                 /*
445                  * DCLOCKXXX: This is a little sketchy, since someone else
446                  * could have already started a prefetch..  In practice,
447                  * this probably doesn't matter; at most it would cause an
448                  * extra slot in the BKG table to be used up when someone
449                  * prefetches this for the second time.
450                  */
451                 ObtainReadLock(&adc->lock);
452                 ObtainWriteLock(&adc->mflock, 664);
453                 adc->mflags &= ~DFNextStarted;
454                 ReleaseWriteLock(&adc->mflock);
455                 ReleaseReadLock(&adc->lock);
456             } else {
457                 ReleaseWriteLock(&tdc->mflock);
458             }
459         } else {
460             ReleaseSharedLock(&tdc->mflock);
461             afs_PutDCache(tdc);
462         }
463     } else {
464         ReleaseSharedLock(&adc->mflock);
465         ReleaseReadLock(&adc->lock);
466     }
467 }
468
469 int
470 afs_UFSReadUIO(afs_dcache_id_t *cacheId, struct uio *tuiop)
471 {
472     int code;
473     struct osi_file *tfile;
474
475     tfile = (struct osi_file *) osi_UFSOpen(cacheId);
476
477 #if defined(AFS_AIX41_ENV)
478     AFS_GUNLOCK();
479     code =
480         VNOP_RDWR(tfile->vnode, UIO_READ, FREAD, tuiop, NULL, NULL,
481                   NULL, afs_osi_credp);
482     AFS_GLOCK();
483 #elif defined(AFS_AIX32_ENV)
484     code =
485         VNOP_RDWR(tfile->vnode, UIO_READ, FREAD, tuiop, NULL, NULL);
486     /* Flush all JFS pages now for big performance gain in big file cases
487      * If we do something like this, must check to be sure that AFS file
488      * isn't mmapped... see afs_gn_map() for why.
489      */
490     /*
491    if (tfile->vnode->v_gnode && tfile->vnode->v_gnode->gn_seg) {
492    any different ways to do similar things:
493    so far, the best performing one is #2, but #1 might match it if we
494    straighten out the confusion regarding which pages to flush.  It 
495    really does matter.
496    1.       vm_flushp(tfile->vnode->v_gnode->gn_seg, 0, len/PAGESIZE - 1);
497    2.       vm_releasep(tfile->vnode->v_gnode->gn_seg, offset/PAGESIZE, 
498                         (len + PAGESIZE-1)/PAGESIZE);
499    3.       vms_inactive(tfile->vnode->v_gnode->gn_seg) Doesn't work correctly
500    4.       vms_delete(tfile->vnode->v_gnode->gn_seg) probably also fails
501             tfile->vnode->v_gnode->gn_seg = NULL;
502    5.       deletep
503    6.       ipgrlse
504    7.       ifreeseg
505           Unfortunately, this seems to cause frequent "cache corruption" episodes.
506             vm_releasep(tfile->vnode->v_gnode->gn_seg, offset/PAGESIZE, 
507                         (len + PAGESIZE-1)/PAGESIZE);
508           }     
509 */
510 #elif defined(AFS_AIX_ENV)
511     code =
512         VNOP_RDWR(tfile->vnode, UIO_READ, FREAD, (off_t) & offset,
513                   tuiop, NULL, NULL, -1);
514 #elif defined(AFS_SUN5_ENV)
515     AFS_GUNLOCK();
516 #ifdef AFS_SUN510_ENV
517     {
518         caller_context_t ct;
519         VOP_RWLOCK(tfile->vnode, 0, &ct);
520         code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp, &ct);
521         VOP_RWUNLOCK(tfile->vnode, 0, &ct);
522     }
523 #else
524     VOP_RWLOCK(tfile->vnode, 0);
525     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
526     VOP_RWUNLOCK(tfile->vnode, 0);
527 #endif
528     AFS_GLOCK();
529 #elif defined(AFS_SGI_ENV)
530     AFS_GUNLOCK();
531     AFS_VOP_RWLOCK(tfile->vnode, VRWLOCK_READ);
532     AFS_VOP_READ(tfile->vnode, tuiop, IO_ISLOCKED, afs_osi_credp,
533                  code);
534     AFS_VOP_RWUNLOCK(tfile->vnode, VRWLOCK_READ);
535     AFS_GLOCK();
536 #elif defined(AFS_HPUX100_ENV)
537     AFS_GUNLOCK();
538     code = VOP_RDWR(tfile->vnode, tuiop, UIO_READ, 0, afs_osi_credp);
539     AFS_GLOCK();
540 #elif defined(AFS_LINUX20_ENV)
541     AFS_GUNLOCK();
542     code = osi_rdwr(tfile, tuiop, UIO_READ);
543     AFS_GLOCK();
544 #elif defined(AFS_DARWIN80_ENV)
545     AFS_GUNLOCK();
546     code = VNOP_READ(tfile->vnode, tuiop, 0, afs_osi_ctxtp);
547     AFS_GLOCK();
548 #elif defined(AFS_DARWIN_ENV)
549     AFS_GUNLOCK();
550     VOP_LOCK(tfile->vnode, LK_EXCLUSIVE, current_proc());
551     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
552     VOP_UNLOCK(tfile->vnode, 0, current_proc());
553     AFS_GLOCK();
554 #elif defined(AFS_FBSD80_ENV)
555     AFS_GUNLOCK();
556     VOP_LOCK(tfile->vnode, LK_EXCLUSIVE);
557     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
558     VOP_UNLOCK(tfile->vnode, 0);
559     AFS_GLOCK();
560 #elif defined(AFS_FBSD_ENV)
561     AFS_GUNLOCK();
562     VOP_LOCK(tfile->vnode, LK_EXCLUSIVE, curthread);
563     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
564     VOP_UNLOCK(tfile->vnode, 0, curthread);
565     AFS_GLOCK();
566 #elif defined(AFS_NBSD_ENV)
567     tuiop->uio_rw = UIO_READ;
568     AFS_GUNLOCK();
569     VOP_LOCK(tfile->vnode, LK_EXCLUSIVE);
570     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
571 # if defined(AFS_NBSD60_ENV)
572     VOP_UNLOCK(tfile->vnode);
573 # else
574     VOP_UNLOCK(tfile->vnode, 0);
575 # endif
576     AFS_GLOCK();
577 #elif defined(AFS_XBSD_ENV)
578     AFS_GUNLOCK();
579     VOP_LOCK(tfile->vnode, LK_EXCLUSIVE, curproc);
580     code = VOP_READ(tfile->vnode, tuiop, 0, afs_osi_credp);
581     VOP_UNLOCK(tfile->vnode, 0, curproc);
582     AFS_GLOCK();
583 #else
584     code = VOP_RDWR(tfile->vnode, tuiop, UIO_READ, 0, afs_osi_credp);
585 #endif
586     osi_UFSClose(tfile);
587
588     return code;
589 }