1 /*
2 * Written by Paul Popelka (paulp@uts.amdahl.com)
3 *
4 * You can do anything you want with this software,
5 * just don't say you wrote it,
6 * and don't remove this notice.
7 *
8 * This software is provided "as is".
9 *
10 * The author supplies this software to be publicly
11 * redistributed on the understanding that the author
12 * is not responsible for the correct functioning of
13 * this software in any circumstances and is not liable
14 * for any damages caused by this software.
15 *
16 * October 1992
17 *
18 * $Id: dosfs_lookup.c,v 1.1 94/10/19 23:46:49 bill Exp $
19 */
20
21 #include "sys/param.h"
22 #include "sys/file.h"
23 #include "sys/time.h"
24 #include "ucred.h"
25 #include "sys/errno.h"
26 #include "uio.h"
27 #include "namei.h"
28 #include "buf.h"
29 #include "vnode.h"
30 #include "sys/mount.h"
31
32 #include "bpb.h"
33 #include "direntry.h"
34 #include "denode.h"
35 #include "dosfs_mount.h"
36 #include "fat.h"
37
38 /*
39 * When we search a directory the blocks containing directory
40 * entries are read and examined. The directory entries
41 * contain information that would normally be in the inode
42 * of a unix filesystem. This means that some of a directory's
43 * contents may also be in memory resident denodes (sort of
44 * an inode). This can cause problems if we are searching
45 * while some other process is modifying a directory. To
46 * prevent one process from accessing incompletely modified
47 * directory information we depend upon being the soul owner
48 * of a directory block. bread/brelse provide this service.
49 * This being the case, when a process modifies a directory
50 * it must first acquire the disk block that contains the
51 * directory entry to be modified. Then update the disk
52 * block and the denode, and then write the disk block out
53 * to disk. This way disk blocks containing directory
54 * entries and in memory denode's will be in synch.
55 */
56 int
pcfs_lookup(vdp,ndp,p)57 pcfs_lookup(vdp, ndp, p)
58 struct vnode *vdp; /* vnode of directory to search */
59 struct nameidata *ndp;
60 struct proc *p;
61 {
62 daddr_t bn;
63 int flag;
64 int error;
65 int lockparent;
66 int wantparent;
67 int slotstatus;
68 #define NONE 0
69 #define FOUND 1
70 int slotoffset;
71 int slotcluster;
72 int frcn;
73 u_long cluster;
74 int rootreloff;
75 int diroff;
76 int isadir; /* ~0 if found direntry is a directory */
77 u_long scn; /* starting cluster number */
78 struct denode *dp;
79 struct denode *pdp;
80 struct denode *tdp;
81 struct pcfsmount *pmp;
82 struct buf *bp = 0;
83 struct direntry *dep;
84 u_char dosfilename[12];
85
86 #if defined(PCFSDEBUG)
87 printf("pcfs_lookup(): looking for %s\n", ndp->ni_ptr);
88 #endif /* defined(PCFSDEBUG) */
89 ndp->ni_dvp = vdp;
90 ndp->ni_vp = NULL;
91 dp = VTODE(vdp);
92 pmp = dp->de_pmp;
93 lockparent = ndp->ni_nameiop & LOCKPARENT;
94 flag = ndp->ni_nameiop & OPMASK;
95 wantparent = ndp->ni_nameiop & (LOCKPARENT | WANTPARENT);
96 #if defined(PCFSDEBUG)
97 printf("pcfs_lookup(): vdp %08x, dp %08x, Attr %02x\n",
98 vdp, dp, dp->de_Attributes);
99 #endif /* defined(PCFSDEBUG) */
100
101 /*
102 * Be sure vdp is a directory. Since dos filesystems
103 * don't have the concept of execute permission anybody
104 * can search a directory.
105 */
106 if ((dp->de_Attributes & ATTR_DIRECTORY) == 0)
107 return ENOTDIR;
108
109 /*
110 * See if the component of the pathname we are looking for
111 * is in the directory cache. If so then do a few things
112 * and return.
113 */
114 if (error = cache_lookup(ndp)) {
115 int vpid;
116
117 if (error == ENOENT)
118 return error;
119 #ifdef PARANOID
120 if (vdp == ndp->ni_rdir && ndp->ni_isdotdot)
121 panic("pcfs_lookup: .. thru root");
122 #endif /* PARANOID */
123 pdp = dp;
124 vdp = ndp->ni_vp;
125 dp = VTODE(vdp);
126 vpid = vdp->v_id;
127 if (pdp == dp) {
128 VREF(vdp);
129 error = 0;
130 } else if (ndp->ni_isdotdot) {
131 DEUNLOCK(pdp);
132 error = vget(vdp);
133 if (!error && lockparent && *ndp->ni_next == '\0')
134 DELOCK(pdp);
135 } else {
136 error = vget(vdp);
137 if (!lockparent || error || *ndp->ni_next != '\0')
138 DEUNLOCK(pdp);
139 }
140
141 if (!error) {
142 if (vpid == vdp->v_id) {
143 #if defined(PCFSDEBUG)
144 printf("pcfs_lookup(): cache hit, vnode %08x, file %s\n", vdp, dp->de_Name);
145 #endif /* defined(PCFSDEBUG) */
146 return 0;
147 }
148 deput(dp);
149 if (lockparent && pdp != dp && *ndp->ni_next == '\0')
150 DEUNLOCK(pdp);
151 }
152 DELOCK(pdp);
153 dp = pdp;
154 vdp = DETOV(dp);
155 ndp->ni_vp = NULL;
156 }
157
158 /*
159 * If they are going after the . or .. entry in the
160 * root directory, they won't find it. DOS filesystems
161 * don't have them in the root directory. So, we fake it.
162 * deget() is in on this scam too.
163 */
164 if ((vdp->v_flag & VROOT) && ndp->ni_ptr[0] == '.' &&
165 (ndp->ni_namelen == 1 ||
166 (ndp->ni_namelen == 2 && ndp->ni_ptr[1] == '.'))) {
167 isadir = ATTR_DIRECTORY;
168 scn = PCFSROOT;
169 #if defined(PCFSDEBUG)
170 printf("pcfs_lookup(): looking for . or .. in root directory\n");
171 #endif /* defined(PCFSDEBUG) */
172 cluster == PCFSROOT;
173 diroff = PCFSROOT_OFS;
174 goto foundroot;
175 }
176
177 /*
178 * Don't search for free slots unless we are creating
179 * a filename and we are at the end of the pathname.
180 */
181 slotstatus = FOUND;
182 if ((flag == CREATE || flag == RENAME) && *ndp->ni_next == '\0') {
183 slotstatus = NONE;
184 slotoffset = -1;
185 }
186
187 unix2dosfn((u_char *)ndp->ni_ptr, dosfilename, ndp->ni_namelen);
188 dosfilename[11] = 0;
189 #if defined(PCFSDEBUG)
190 printf("pcfs_lookup(): dos version of filename %s, length %d\n",
191 dosfilename, ndp->ni_namelen);
192 #endif /* defined(PCFSDEBUG) */
193 /*
194 * Search the directory pointed at by vdp for the
195 * name pointed at by ndp->ni_ptr.
196 */
197 tdp = NULL;
198 /*
199 * The outer loop ranges over the clusters that make
200 * up the directory. Note that the root directory is
201 * different from all other directories. It has a
202 * fixed number of blocks that are not part of the
203 * pool of allocatable clusters. So, we treat it a
204 * little differently.
205 * The root directory starts at "cluster" 0.
206 */
207 rootreloff = 0;
208 for (frcn = 0; ; frcn++) {
209 if (error = pcbmap(dp, frcn, &bn, &cluster)) {
210 if (error == E2BIG)
211 break;
212 return error;
213 }
214 if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, &bp))
215 return error;
216 for (diroff = 0; diroff < pmp->pm_depclust; diroff++) {
217 dep = (struct direntry *)bp->b_un.b_addr + diroff;
218
219 /*
220 * If the slot is empty and we are still looking for
221 * an empty then remember this one. If the slot is
222 * not empty then check to see if it matches what we
223 * are looking for. If the slot has never been filled
224 * with anything, then the remainder of the directory
225 * has never been used, so there is no point in searching
226 * it.
227 */
228 if (dep->deName[0] == SLOT_EMPTY ||
229 dep->deName[0] == SLOT_DELETED) {
230 if (slotstatus != FOUND) {
231 slotstatus = FOUND;
232 if (cluster == PCFSROOT)
233 slotoffset = rootreloff;
234 else
235 slotoffset = diroff;
236 slotcluster = cluster;
237 }
238 if (dep->deName[0] == SLOT_EMPTY) {
239 brelse(bp);
240 goto notfound;
241 }
242 } else {
243 /* Ignore volume labels (anywhere, not just
244 * the root directory). */
245 if ((dep->deAttributes & ATTR_VOLUME) == 0 &&
246 memcmp(dosfilename, dep->deName, 11) == 0) {
247 #if defined(PCFSDEBUG)
248 printf("pcfs_lookup(): match diroff %d, rootreloff %d\n", diroff, rootreloff);
249 #endif /* defined(PCFSDEBUG) */
250 /*
251 * Remember where this directory entry came from
252 * for whoever did this lookup.
253 * If this is the root directory we are interested
254 * in the offset relative to the beginning of the
255 * directory (not the beginning of the cluster).
256 */
257 if (cluster == PCFSROOT)
258 diroff = rootreloff;
259 ndp->ni_pcfs.pcfs_offset = diroff;
260 ndp->ni_pcfs.pcfs_cluster = cluster;
261 goto found;
262 }
263 }
264 rootreloff++;
265 } /* for (diroff = 0; .... */
266 /*
267 * Release the buffer holding the directory cluster
268 * just searched.
269 */
270 brelse(bp);
271 } /* for (frcn = 0; ; frcn++) */
272 notfound:;
273 /*
274 * We hold no disk buffers at this point.
275 */
276
277 /*
278 * If we get here we didn't find the entry we were looking
279 * for. But that's ok if we are creating or renaming and
280 * are at the end of the pathname and the directory hasn't
281 * been removed.
282 */
283 #if defined(PCFSDEBUG)
284 printf("pcfs_lookup(): flag %d, refcnt %d, slotstatus %d\n",
285 flag, dp->de_refcnt, slotstatus);
286 printf(" slotoffset %d, slotcluster %d\n",
287 slotoffset, slotcluster);
288 #endif /* defined(PCFSDEBUG) */
289 if ((flag == CREATE || flag == RENAME) &&
290 *ndp->ni_next == '\0' && dp->de_refcnt != 0) {
291 if (slotstatus == NONE) {
292 ndp->ni_pcfs.pcfs_offset = 0;
293 ndp->ni_pcfs.pcfs_cluster = 0;
294 ndp->ni_pcfs.pcfs_count = 0;
295 } else {
296 #if defined(PCFSDEBUG)
297 printf("pcfs_lookup(): saving empty slot location\n");
298 #endif /* defined(PCFSDEBUG) */
299 ndp->ni_pcfs.pcfs_offset = slotoffset;
300 ndp->ni_pcfs.pcfs_cluster = slotcluster;
301 ndp->ni_pcfs.pcfs_count = 1;
302 }
303 /* dp->de_flag |= DEUPD; /* never update dos directories */
304 ndp->ni_nameiop |= SAVENAME;
305 if (!lockparent) /* leave searched dir locked? */
306 DEUNLOCK(dp);
307 }
308 /*
309 * Insert name in cache as non-existant if not
310 * trying to create it.
311 */
312 if (ndp->ni_makeentry && flag != CREATE)
313 cache_enter(ndp);
314 return ENOENT;
315
316 found:;
317 /*
318 * NOTE: We still have the buffer with matched
319 * directory entry at this point.
320 */
321 isadir = dep->deAttributes & ATTR_DIRECTORY;
322 scn = dep->deStartCluster;
323
324 foundroot:;
325 /*
326 * If we entered at foundroot, then we are looking
327 * for the . or .. entry of the filesystems root
328 * directory. isadir and scn were setup before
329 * jumping here. And, bp is null. There is no buf header.
330 */
331
332 /*
333 * If deleting and at the end of the path, then
334 * if we matched on "." then don't deget() we would
335 * probably panic(). Otherwise deget() the directory
336 * entry.
337 */
338 if (flag == DELETE && ndp->ni_next == '\0') {
339 if (dp->de_StartCluster == scn &&
340 isadir) { /* "." */
341 VREF(vdp);
342 ndp->ni_vp = vdp;
343 if (bp) brelse(bp);
344 return 0;
345 }
346 error = deget(pmp, cluster, diroff, dep, &tdp);
347 if (error) {
348 if (bp) brelse(bp);
349 return error;
350 }
351 ndp->ni_vp = DETOV(tdp);
352 if (!lockparent)
353 DEUNLOCK(dp);
354 if (bp) brelse(bp);
355 return 0;
356 }
357
358 /*
359 * If renaming.
360 */
361 if (flag == RENAME && wantparent && *ndp->ni_next == '\0') {
362 if (dp->de_StartCluster == scn &&
363 isadir) {
364 if (bp) brelse(bp);
365 return EISDIR;
366 }
367 error = deget(pmp, cluster, diroff, dep, &tdp);
368 if (error) {
369 if (bp) brelse(bp);
370 return error;
371 }
372 ndp->ni_vp = DETOV(tdp);
373 ndp->ni_nameiop |= SAVENAME;
374 if (!lockparent)
375 DEUNLOCK(dp);
376 if (bp) brelse(bp);
377 return 0;
378 }
379
380 /*
381 * ?
382 */
383 pdp = dp;
384 if (ndp->ni_isdotdot) {
385 DEUNLOCK(pdp);
386 error = deget(pmp, cluster, diroff, dep, &tdp);
387 if (error) {
388 DELOCK(pdp);
389 if (bp) brelse(bp);
390 return error;
391 }
392 if (lockparent && *ndp->ni_next == '\0')
393 DELOCK(pdp);
394 ndp->ni_vp = DETOV(tdp);
395 } else if (dp->de_StartCluster == scn &&
396 isadir) { /* "." */
397 VREF(vdp);
398 ndp->ni_vp = vdp;
399 } else {
400 error = deget(pmp, cluster, diroff, dep, &tdp);
401 if (error) {
402 if (bp) brelse(bp);
403 return error;
404 }
405 if (!lockparent || *ndp->ni_next != '\0')
406 DEUNLOCK(pdp);
407 ndp->ni_vp = DETOV(tdp);
408 }
409 if (bp) brelse(bp);
410
411 /*
412 * Insert name in cache if wanted.
413 */
414 if (ndp->ni_makeentry)
415 cache_enter(ndp);
416 return 0;
417 }
418
419 /*
420 * dep - directory to copy into the directory
421 * ndp - nameidata structure containing info on
422 * where to put the directory entry in the directory.
423 * depp - return the address of the denode for the
424 * created directory entry if depp != 0
425 */
426 int
createde(dep,ndp,depp)427 createde(dep, ndp, depp)
428 struct denode *dep;
429 struct nameidata *ndp;
430 struct denode **depp;
431 {
432 int bn;
433 int error;
434 u_long dirclust, diroffset;
435 struct direntry *ndep;
436 struct denode *ddep = VTODE(ndp->ni_dvp); /* directory to add to */
437 struct pcfsmount *pmp = dep->de_pmp;
438 struct buf *bp;
439 #if defined(PCFSDEBUG)
440 printf("createde(dep %08x, ndp %08x, depp %08x)\n", dep, ndp, depp);
441 #endif /* defined(PCFSDEBUG) */
442
443 /*
444 * If no space left in the directory then allocate
445 * another cluster and chain it onto the end of the
446 * file. There is one exception to this. That is,
447 * if the root directory has no more space it can NOT
448 * be expanded. extendfile() checks for and fails attempts to
449 * extend the root directory. We just return an error
450 * in that case.
451 */
452 if (ndp->ni_pcfs.pcfs_count == 0) {
453 if (error = extendfile(ddep, &bp, &dirclust))
454 return error;
455 ndep = (struct direntry *)bp->b_un.b_addr;
456 /*
457 * Let caller know where we put the directory entry.
458 */
459 ndp->ni_pcfs.pcfs_cluster = dirclust;
460 ndp->ni_pcfs.pcfs_offset = diroffset = 0;
461 }
462
463 else {
464 /*
465 * There is space in the existing directory. So,
466 * we just read in the cluster with space. Copy
467 * the new directory entry in. Then write it to
468 * disk.
469 * NOTE: DOS directories do not get smaller as
470 * clusters are emptied.
471 */
472 dirclust = ndp->ni_pcfs.pcfs_cluster;
473 diroffset = ndp->ni_pcfs.pcfs_offset;
474
475 error = readep(pmp, dirclust, diroffset, &bp, &ndep);
476 if (error)
477 return error;
478 }
479 *ndep = dep->de_de;
480 /*
481 * If they want us to return with the denode gotten.
482 */
483 if (depp) {
484 error = deget(pmp, dirclust, diroffset, ndep, depp);
485 if (error)
486 return error;
487 }
488 if (error = bwrite(bp))
489 /*deput()?*/
490 return error;
491 return 0;
492 }
493
494 /*
495 * Read in a directory entry and mark it as being deleted.
496 */
497 int
markdeleted(pmp,dirclust,diroffset)498 markdeleted(pmp, dirclust, diroffset)
499 struct pcfsmount *pmp;
500 u_long dirclust;
501 u_long diroffset;
502 {
503 int error;
504 struct direntry *ep;
505 struct buf *bp;
506
507 error = readep(pmp, dirclust, diroffset, &bp, &ep);
508 if (error)
509 return error;
510 ep->deName[0] = SLOT_DELETED;
511 return bwrite(bp);
512 }
513
514 /*
515 * Remove a directory entry.
516 * At this point the file represented by the directory
517 * entry to be removed is still full length until no
518 * one has it open. When the file no longer being
519 * used pcfs_inactive() is called and will truncate
520 * the file to 0 length. When the vnode containing
521 * the denode is needed for some other purpose by
522 * VFS it will call pcfs_reclaim() which will remove
523 * the denode from the denode cache.
524 */
525 int
removede(ndp)526 removede(ndp)
527 struct nameidata *ndp;
528 {
529 struct denode *dep = VTODE(ndp->ni_vp); /* the file being removed */
530 struct pcfsmount *pmp = dep->de_pmp;
531 int error;
532
533 #if defined(PCFSDEBUG)
534 /*printf("removede(): filename %s\n", dep->de_Name);
535 printf("rmde(): dep %08x, ndpcluster %d, ndpoffset %d\n",
536 dep, ndp->ni_pcfs.pcfs_cluster, ndp->ni_pcfs.pcfs_offset);*/
537 #endif /* defined(PCFSDEBUG) */
538
539 /*
540 * Read the directory block containing the directory
541 * entry we are to make free. The nameidata structure
542 * holds the cluster number and directory entry index
543 * number of the entry to free.
544 */
545 error = markdeleted(pmp, ndp->ni_pcfs.pcfs_cluster,
546 ndp->ni_pcfs.pcfs_offset);
547
548 dep->de_refcnt--;
549 return error;
550 }
551
552 /*
553 * Be sure a directory is empty except for "." and "..".
554 * Return 1 if empty, return 0 if not empty or error.
555 */
556 int
dosdirempty(dep)557 dosdirempty(dep)
558 struct denode *dep;
559 {
560 int dei;
561 int error;
562 u_long cn;
563 daddr_t bn;
564 struct buf *bp;
565 struct pcfsmount *pmp = dep->de_pmp;
566 struct direntry *dentp;
567
568 /*
569 * Since the filesize field in directory entries for a directory
570 * is zero, we just have to feel our way through the directory
571 * until we hit end of file.
572 */
573 for (cn = 0;; cn++) {
574 error = pcbmap(dep, cn, &bn, 0);
575 if (error == E2BIG)
576 return 1; /* it's empty */
577 error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED,
578 &bp);
579 if (error)
580 return error;
581 dentp = (struct direntry *)bp->b_un.b_addr;
582 for (dei = 0; dei < pmp->pm_depclust; dei++) {
583 if (dentp->deName[0] != SLOT_DELETED) {
584 /*
585 * In dos directories an entry whose name starts with SLOT_EMPTY (0)
586 * starts the beginning of the unused part of the directory, so we
587 * can just return that it is empty.
588 */
589 if (dentp->deName[0] == SLOT_EMPTY) {
590 brelse(bp);
591 return 1;
592 }
593 /*
594 * Any names other than "." and ".." in a directory mean
595 * it is not empty.
596 */
597 if (memcmp(dentp->deName, ". ", 11) &&
598 memcmp(dentp->deName, ".. ", 11)) {
599 brelse(bp);
600 #if defined(PCFSDEBUG)
601 printf("dosdirempty(): entry %d found %02x, %02x\n", dei, dentp->deName[0],
602 dentp->deName[1]);
603 #endif /* defined(PCFSDEBUG) */
604 return 0; /* not empty */
605 }
606 }
607 dentp++;
608 }
609 brelse(bp);
610 }
611 /*NOTREACHED*/
612 }
613
614 /*
615 * Check to see if the directory described by target is
616 * in some subdirectory of source. This prevents something
617 * like the following from succeeding and leaving a bunch
618 * or files and directories orphaned.
619 * mv /a/b/c /a/b/c/d/e/f
620 * Where c and f are directories.
621 * source - the inode for /a/b/c
622 * target - the inode for /a/b/c/d/e/f
623 * Returns 0 if target is NOT a subdirectory of source.
624 * Otherwise returns a non-zero error number.
625 * The target inode is always unlocked on return.
626 */
627 int
doscheckpath(source,target)628 doscheckpath(source, target)
629 struct denode *source;
630 struct denode *target;
631 {
632 daddr_t scn;
633 struct denode dummy;
634 struct pcfsmount *pmp;
635 struct direntry *ep;
636 struct denode *dep;
637 struct buf *bp = NULL;
638 int error = 0;
639
640 dep = target;
641 if ((target->de_Attributes & ATTR_DIRECTORY) == 0 ||
642 (source->de_Attributes & ATTR_DIRECTORY) == 0) {
643 error = ENOTDIR;
644 goto out;
645 }
646 if (dep->de_StartCluster == source->de_StartCluster) {
647 error = EEXIST;
648 goto out;
649 }
650 if (dep->de_StartCluster == PCFSROOT)
651 goto out;
652 for (;;) {
653 if ((dep->de_Attributes & ATTR_DIRECTORY) == 0) {
654 error = ENOTDIR;
655 goto out;
656 }
657 pmp = dep->de_pmp;
658 scn = dep->de_StartCluster;
659 error = bread(pmp->pm_devvp, cntobn(pmp, scn),
660 pmp->pm_bpcluster, NOCRED, &bp);
661 if (error) {
662 break;
663 }
664 ep = (struct direntry *)bp->b_un.b_addr + 1;
665 if ((ep->deAttributes & ATTR_DIRECTORY) == 0 ||
666 memcmp(ep->deName, ".. ", 11) != 0) {
667 error = ENOTDIR;
668 break;
669 }
670 if (ep->deStartCluster == source->de_StartCluster) {
671 error = EINVAL;
672 break;
673 }
674 if (ep->deStartCluster == PCFSROOT)
675 break;
676 deput(dep);
677 /* NOTE: deget() clears dep on error */
678 error = deget(pmp, ep->deStartCluster, 0, ep, &dep);
679 brelse(bp);
680 bp = NULL;
681 if (error)
682 break;
683 }
684 out:;
685 if (bp)
686 brelse(bp);
687 if (error == ENOTDIR)
688 printf("doscheckpath(): .. not a directory?\n");
689 if (dep != NULL)
690 deput(dep);
691 return error;
692 }
693
694 /*
695 * Read in the disk block containing the directory entry
696 * (dirclu, dirofs) and return the address of the buf header,
697 * and the address of the directory entry within the block.
698 */
699 int
readep(pmp,dirclu,dirofs,bpp,epp)700 readep(pmp, dirclu, dirofs, bpp, epp)
701 struct pcfsmount *pmp;
702 u_long dirclu, dirofs;
703 struct buf **bpp;
704 struct direntry **epp;
705 {
706 int error;
707 daddr_t bn;
708
709 bn = detobn(pmp, dirclu, dirofs);
710 if (error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, NOCRED, bpp)) {
711 *bpp = NULL;
712 return error;
713 }
714 if (epp)
715 *epp = bptoep(pmp, *bpp, dirofs);
716 return 0;
717 }
718
719
720 /*
721 * Read in the disk block containing the directory entry
722 * dep came from and return the address of the buf header,
723 * and the address of the directory entry within the block.
724 */
725 int
readde(dep,bpp,epp)726 readde(dep, bpp, epp)
727 struct denode *dep;
728 struct buf **bpp;
729 struct direntry **epp;
730 {
731 return readep(dep->de_pmp, dep->de_dirclust, dep->de_diroffset,
732 bpp, epp);
733 }
734