summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpfg <pfg@FreeBSD.org>2013-07-25 15:34:20 +0000
committerpfg <pfg@FreeBSD.org>2013-07-25 15:34:20 +0000
commit3cce3ff56878674874392576e3759685b4172777 (patch)
tree11b800c450940f0841d8e739196ebf8b23659a35
parent0ad2a46f33944b5fedbda54167af5739c8c5ed84 (diff)
downloadFreeBSD-src-3cce3ff56878674874392576e3759685b4172777.zip
FreeBSD-src-3cce3ff56878674874392576e3759685b4172777.tar.gz
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
-rw-r--r--sys/fs/ext2fs/ext2_lookup.c180
1 files changed, 99 insertions, 81 deletions
diff --git a/sys/fs/ext2fs/ext2_lookup.c b/sys/fs/ext2fs/ext2_lookup.c
index 4072254..31d416f 100644
--- a/sys/fs/ext2fs/ext2_lookup.c
+++ b/sys/fs/ext2fs/ext2_lookup.c
@@ -128,33 +128,40 @@ ext2_is_dot_entry(struct componentname *cnp)
/*
* 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);
}
OpenPOWER on IntegriCloud