Author: pfg
Date: Thu Jul 25 15:34:20 2013
New Revision: 253651
URL: http://svnweb.freebsd.org/changeset/base/253651

Log:
  ext2fs: Don't assume that on-disk format of a directory is the same
  as in <sys/dirent.h>
  
  ext2_readdir() has always been very fs specific and different
  with respect to its ufs_ counterpart. Recent changes from UFS
  have made it possible to share more closely the implementation.
  
  MFUFS r252438:
  Always start parsing at DIRBLKSIZ aligned offset, skip first entries if
  uio_offset is not DIRBLKSIZ aligned. Return EINVAL if buffer is too
  small for single entry.
  
  Preallocate buffer for cookies.
  
  Skip entries with zero inode number.
  
  Reviewed by:  gleb, Zheng Liu
  MFC after:    1 month

Modified:
  head/sys/fs/ext2fs/ext2_lookup.c

Modified: head/sys/fs/ext2fs/ext2_lookup.c
==============================================================================
--- head/sys/fs/ext2fs/ext2_lookup.c    Thu Jul 25 15:08:41 2013        
(r253650)
+++ head/sys/fs/ext2fs/ext2_lookup.c    Thu Jul 25 15:34:20 2013        
(r253651)
@@ -128,33 +128,40 @@ ext2_is_dot_entry(struct componentname *
 
 /*
  * Vnode op for reading directories.
- *
- * This function has to convert directory entries from the on-disk
- * format to the format defined by <sys/dirent.h>.  Unfortunately, the
- * conversion will blow up some entries by four bytes, so it can't be
- * done in place.  Instead, the conversion is done entry by entry and
- * the converted entry is sent via uiomove.
- *
- * XXX allocate a buffer, convert as many entries as possible, then send
- * the whole buffer to uiomove
  */
 int
 ext2_readdir(struct vop_readdir_args *ap)
 {
+       struct vnode *vp = ap->a_vp;
        struct uio *uio = ap->a_uio;
-       int count, error;
-
-       struct ext2fs_direct_2 *edp, *dp;
-       int ncookies;
+       struct buf *bp;
+       struct inode *ip;
+       struct ext2fs_direct_2 *dp, *edp;
+       u_long *cookies;
        struct dirent dstdp;
-       struct uio auio;
-       struct iovec aiov;
-       caddr_t dirbuf;
+       off_t offset, startoffset;
+       size_t readcnt, skipcnt;
+       ssize_t startresid;
+       int ncookies;
        int DIRBLKSIZ = VTOI(ap->a_vp)->i_e2fs->e2fs_bsize;
-       int readcnt;
-       off_t startoffset = uio->uio_offset;
+       int error;
 
-       count = uio->uio_resid;
+       ip = VTOI(vp);
+       if (ap->a_ncookies != NULL) {
+               ncookies = uio->uio_resid;
+               if (uio->uio_offset >= ip->i_size)
+                       ncookies = 0;
+               else if (ip->i_size - uio->uio_offset < ncookies)
+                       ncookies = ip->i_size - uio->uio_offset;
+               ncookies = ncookies / (offsetof(struct ext2fs_direct_2,
+                   e2d_namlen) + 4) + 1;
+               cookies = malloc(ncookies * sizeof(*cookies), M_TEMP, M_WAITOK);
+               *ap->a_ncookies = ncookies;
+               *ap->a_cookies = cookies;
+       } else {
+               ncookies = 0;
+               cookies = NULL;
+       }
        /*
         * Avoid complications for partial directory entries by adjusting
         * the i/o to end at a block boundary.  Don't give up (like ufs
@@ -163,25 +170,33 @@ ext2_readdir(struct vop_readdir_args *ap
         * size is a little larger than DIRBLKSIZ to allow for expansion
         * of directory entries, but some callers just use 512.
         */
-       count -= (uio->uio_offset + count) & (DIRBLKSIZ -1);
-       if (count <= 0)
-               count += DIRBLKSIZ;
-       auio = *uio;
-       auio.uio_iov = &aiov;
-       auio.uio_iovcnt = 1;
-       auio.uio_resid = count;
-       auio.uio_segflg = UIO_SYSSPACE;
-       aiov.iov_len = count;
-       dirbuf = malloc(count, M_TEMP, M_WAITOK);
-       aiov.iov_base = dirbuf;
-       error = VOP_READ(ap->a_vp, &auio, 0, ap->a_cred);
-       if (error == 0) {
-               readcnt = count - auio.uio_resid;
-               edp = (struct ext2fs_direct_2 *)&dirbuf[readcnt];
-               ncookies = 0;
-               bzero(&dstdp, offsetof(struct dirent, d_name));
-               for (dp = (struct ext2fs_direct_2 *)dirbuf;
-                   !error && uio->uio_resid > 0 && dp < edp; ) {
+       if (uio->uio_offset < 0)
+               offset = startoffset = uio->uio_offset + DIRBLKSIZ;
+       else
+               offset = startoffset = uio->uio_offset;
+       startresid = uio->uio_resid;
+       error = 0;
+       while (error == 0 && uio->uio_resid > 0 &&
+           uio->uio_offset < ip->i_size) {
+               error = ext2_blkatoff(vp, uio->uio_offset, NULL, &bp);
+               if (error)
+                       break;
+               if (bp->b_offset + bp->b_bcount > ip->i_size)
+                       readcnt = ip->i_size - bp->b_offset;
+               else
+                       readcnt = bp->b_bcount;
+               skipcnt = (size_t)(uio->uio_offset - bp->b_offset) &
+                   ~(size_t)(DIRBLKSIZ - 1);
+               offset = bp->b_offset + skipcnt;
+               dp = (struct ext2fs_direct_2 *)&bp->b_data[skipcnt];
+               edp = (struct ext2fs_direct_2 *)&bp->b_data[readcnt];
+               while (error == 0 && uio->uio_resid > 0 && dp < edp) {
+                       if (dp->e2d_reclen <= offsetof(struct ext2fs_direct_2,
+                           e2d_namlen) || (caddr_t)dp + dp->e2d_reclen >
+                           (caddr_t)edp) {
+                               error = EIO;
+                               break;
+                       }
                        /*-
                         * "New" ext2fs directory entries differ in 3 ways
                         * from ufs on-disk ones:
@@ -198,57 +213,60 @@ ext2_readdir(struct vop_readdir_args *ap
                         * because ext2fs uses a machine-independent disk
                         * layout.
                         */
-                       dstdp.d_fileno = dp->e2d_ino;
-                       dstdp.d_type = FTTODT(dp->e2d_type);
                        dstdp.d_namlen = dp->e2d_namlen;
+                       dstdp.d_type = FTTODT(dp->e2d_type);
+                       if (offsetof(struct ext2fs_direct_2, e2d_namlen) +
+                           dstdp.d_namlen > dp->e2d_reclen) {
+                               error = EIO;
+                               break;
+                       }
+                       if (offset < startoffset || dp->e2d_ino == 0)
+                               goto nextentry;
+                       dstdp.d_fileno = dp->e2d_ino;
                        dstdp.d_reclen = GENERIC_DIRSIZ(&dstdp);
                        bcopy(dp->e2d_name, dstdp.d_name, dstdp.d_namlen);
-                       bzero(dstdp.d_name + dstdp.d_namlen,
-                           dstdp.d_reclen - offsetof(struct dirent, d_name) -
-                           dstdp.d_namlen);
-
-                       if (dp->e2d_reclen > 0) {
-                               if(dstdp.d_reclen <= uio->uio_resid) {
-                                       /* advance dp */
-                                       dp = (struct ext2fs_direct_2 *)
-                                           ((char *)dp + dp->e2d_reclen);
-                                       error =
-                                         uiomove(&dstdp, dstdp.d_reclen, uio);
-                                       if (!error)
-                                               ncookies++;
-                               } else
-                                       break;
-                       } else {
-                               error = EIO;
+                       dstdp.d_name[dstdp.d_namlen] = '\0';
+                       if (dstdp.d_reclen > uio->uio_resid) {
+                               if (uio->uio_resid == startresid)
+                                       error = EINVAL;
+                               else
+                                       error = EJUSTRETURN;
                                break;
                        }
-               }
-               /* we need to correct uio_offset */
-               uio->uio_offset = startoffset + (caddr_t)dp - dirbuf;
-
-               if (!error && ap->a_ncookies != NULL) {
-                       u_long *cookiep, *cookies, *ecookies;
-                       off_t off;
-
-                       if (uio->uio_segflg != UIO_SYSSPACE || uio->uio_iovcnt 
!= 1)
-                               panic("ext2_readdir: unexpected uio from NFS 
server");
-                       cookies = malloc(ncookies * sizeof(u_long), M_TEMP,
-                              M_WAITOK);
-                       off = startoffset;
-                       for (dp = (struct ext2fs_direct_2 *)dirbuf,
-                            cookiep = cookies, ecookies = cookies + ncookies;
-                            cookiep < ecookies;
-                            dp = (struct ext2fs_direct_2 *)((caddr_t) dp + 
dp->e2d_reclen)) {
-                               off += dp->e2d_reclen;
-                               *cookiep++ = (u_long) off;
+                       /* Advance dp. */
+                       error = uiomove((caddr_t)&dstdp, dstdp.d_reclen, uio);
+                       if (error)
+                               break;
+                       if (cookies != NULL) {
+                               KASSERT(ncookies > 0,
+                                   ("ext2_readdir: cookies buffer too small"));
+                               *cookies = offset + dp->e2d_reclen;
+                               cookies++;
+                               ncookies--;
                        }
-                       *ap->a_ncookies = ncookies;
-                       *ap->a_cookies = cookies;
+nextentry:
+                       offset += dp->e2d_reclen;
+                       dp = (struct ext2fs_direct_2 *)((caddr_t)dp +
+                          dp->e2d_reclen);
+               }
+               bqrelse(bp);
+               uio->uio_offset = offset;
+       }
+       /* We need to correct uio_offset. */
+       uio->uio_offset = offset;
+       if (error == EJUSTRETURN)
+               error = 0;
+       if (ap->a_ncookies != NULL) {
+               if (error == 0) {
+                       ap->a_ncookies -= ncookies;
+               } else {
+                       free(*ap->a_cookies, M_TEMP);
+                       *ap->a_ncookies = 0;
+                       *ap->a_cookies = NULL;
                }
        }
-       free(dirbuf, M_TEMP);
-       if (ap->a_eofflag)
-               *ap->a_eofflag = VTOI(ap->a_vp)->i_size <= uio->uio_offset;
+       if (error == 0 && ap->a_eofflag)
+               *ap->a_eofflag = ip->i_size <= uio->uio_offset;
        return (error);
 }
 
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to