/* * this is exactly what we do here - the problem is that the conversion * will blow up some entries by four bytes, so it can't be done in place. * This is too bad. Right now the conversion is done entry by entry, the * converted entry is sent via uiomove. * * XXX allocate a buffer, convert as many entries as possible, then send * the whole buffer to uiomove * * ext2_readdir(struct vnode *a_vp, struct uio *a_uio, struct ucred *a_cred) */ int ext2_readdir(struct vop_readdir_args *ap) { struct uio *uio = ap->a_uio; int count, error; struct ext2_dir_entry_2 *edp, *dp; int ncookies; struct uio auio; struct iovec aiov; caddr_t dirbuf; int DIRBLKSIZ = VTOI(ap->a_vp)->i_e2fs->s_blocksize; int readcnt, retval; off_t startoffset = uio->uio_offset; if ((error = vn_lock(ap->a_vp, LK_EXCLUSIVE | LK_RETRY)) != 0) return(error); count = uio->uio_resid; /* * Avoid complications for partial directory entries by adjusting * the i/o to end at a block boundary. Don't give up (like ufs * does) if the initial adjustment gives a negative count, since * many callers don't supply a large enough buffer. The correct * 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; if (count > MAXBSIZE) /* limit to a reasonable size */ count = MAXBSIZE; #ifdef EXT2FS_DEBUG kprintf("ext2_readdir: uio_offset = %lld, uio_resid = %d, count = %d\n", uio->uio_offset, uio->uio_resid, count); #endif auio = *uio; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_resid = count; auio.uio_segflg = UIO_SYSSPACE; aiov.iov_len = count; dirbuf = kmalloc(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 ext2_dir_entry_2 *)&dirbuf[readcnt]; ncookies = 0; for (dp = (struct ext2_dir_entry_2 *)dirbuf; !error && uio->uio_resid > 0 && dp < edp; ) { /*- * "New" ext2fs directory entries differ in 3 ways * from ufs on-disk ones: * - the name is not necessarily NUL-terminated. * - the file type field always exists and always * follows the name length field. * - the file type is encoded in a different way. * * "Old" ext2fs directory entries need no special * conversions, since they binary compatible with * "new" entries having a file type of 0 (i.e., * EXT2_FT_UNKNOWN). Splitting the old name length * field didn't make a mess like it did in ufs, * because ext2fs uses a machine-dependent disk * layout. */ if (dp->rec_len <= 0) { error = EIO; break; } retval = vop_write_dirent(&error, uio, dp->inode, FTTODT(dp->file_type), dp->name_len, dp->name); if (retval) break; /* advance dp */ dp = (struct ext2_dir_entry_2 *)((char *)dp + dp->rec_len); if (!error) ncookies++; } /* we need to correct uio_offset */ uio->uio_offset = startoffset + (caddr_t)dp - dirbuf; if (!error && ap->a_ncookies != NULL) { off_t *cookiep, *cookies, *ecookies; off_t off; if (uio->uio_segflg != UIO_SYSSPACE || uio->uio_iovcnt != 1) panic("ext2fs_readdir: unexpected uio from NFS server"); if (ncookies) { cookies = kmalloc(ncookies * sizeof(off_t), M_TEMP, M_WAITOK); } else { cookies = kmalloc(sizeof(off_t), M_TEMP, M_WAITOK); } off = startoffset; for (dp = (struct ext2_dir_entry_2 *)dirbuf, cookiep = cookies, ecookies = cookies + ncookies; cookiep < ecookies; dp = (struct ext2_dir_entry_2 *)((caddr_t) dp + dp->rec_len)) { off += dp->rec_len; *cookiep++ = off; } *ap->a_ncookies = ncookies; *ap->a_cookies = cookies; } } kfree(dirbuf, M_TEMP); if (ap->a_eofflag) *ap->a_eofflag = VTOI(ap->a_vp)->i_size <= uio->uio_offset; vn_unlock(ap->a_vp); return (error); }
/* * 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 uio *uio = ap->a_uio; int count, error; struct ext2fs_direct_2 *edp, *dp; int ncookies; struct dirent dstdp; struct uio auio; struct iovec aiov; caddr_t dirbuf; int DIRBLKSIZ = VTOI(ap->a_vp)->i_e2fs->e2fs_bsize; int readcnt; off_t startoffset = uio->uio_offset; count = uio->uio_resid; /* * Avoid complications for partial directory entries by adjusting * the i/o to end at a block boundary. Don't give up (like ufs * does) if the initial adjustment gives a negative count, since * many callers don't supply a large enough buffer. The correct * 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; ) { /*- * "New" ext2fs directory entries differ in 3 ways * from ufs on-disk ones: * - the name is not necessarily NUL-terminated. * - the file type field always exists and always * follows the name length field. * - the file type is encoded in a different way. * * "Old" ext2fs directory entries need no special * conversions, since they are binary compatible * with "new" entries having a file type of 0 (i.e., * EXT2_FT_UNKNOWN). Splitting the old name length * field didn't make a mess like it did in ufs, * 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_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; 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; } *ap->a_ncookies = ncookies; *ap->a_cookies = cookies; } }