tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

fixing compat_12 getdents



the last problem I found with running the i386 1.0 "ls" on i386/amd64 is that
some of the compat wrappers for getdents/getdirentries don't actually translate
the data to the old format.  the attached patch fixes that, but I'm wondering
about one aspect of the translation.  the current version of
compat_30_sys_getdents() returns EINVAL if the 64-bit inode number doesn't fit
in 32 bits, but if the name is too large to fit in the old-format buffer
it just truncates the name.  I changed it to return EINVAL in this case
as well, would anyone like to argue in favor of keeping the truncation?

also, EINVAL doesn't seem like a great error code for this condition.
it's not an input parameter that's causing the error, but rather that
the required output format cannot express the data to be returned.
I think solaris uses EOVERFLOW for this kind of situation, and ERANGE
doesn't seem too bad either.  any opinions on that?

this patch also takes care to zero any parts of the buffer that are not
initialized by the translation.  this isn't really a problem since the
buffer is fully initialized by vn_readdir(), but it seems sloppy to
let parts of the native format data leak through.

there are a bunch of other compat functions which could be changed to use
the new compat_getdents(), but this patch does not include that.

-Chuck
Index: sys/compat/common/compat_util.c
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/common/compat_util.c,v
retrieving revision 1.44
diff -u -p -r1.44 compat_util.c
--- sys/compat/common/compat_util.c     19 Nov 2010 06:44:35 -0000      1.44
+++ sys/compat/common/compat_util.c     10 Dec 2012 16:06:45 -0000
@@ -74,8 +74,11 @@ __KERNEL_RCSID(0, "$NetBSD: compat_util.
 #include <sys/vnode.h>
 #include <sys/syslog.h>
 #include <sys/mount.h>
+#include <sys/dirent.h>
+#include <sys/ktrace.h>
 
 #include <compat/common/compat_util.h>
+#include <compat/sys/dirent.h>
 
 void
 emul_find_root(struct lwp *l, struct exec_package *epp)
@@ -229,3 +232,92 @@ compat_elf_check_interp(struct exec_pack
        }
        return error;
 }
+
+/*
+ * Convert a buffer of native dirents to dirent12s.
+ */
+int
+compat_native_to_dirent12(char *buf, int *donep)
+{
+       struct dirent *ndp, *nndp, *endp;
+       struct dirent12 *odp;
+       char *nameend;
+
+       odp = (struct dirent12 *)(void *)buf;
+       ndp = (struct dirent *)(void *)buf;
+       endp = (struct dirent *)(void *)&buf[*donep];
+
+       /*
+        * In-place conversion. This works because odp
+        * is smaller than ndp, but it has to be done
+        * in the right sequence.
+        */
+       for (; ndp < endp; ndp = nndp) {
+               nndp = _DIRENT_NEXT(ndp);
+               odp->d_fileno = (u_int32_t)ndp->d_fileno;
+               if (odp->d_fileno != ndp->d_fileno)
+                       return EINVAL;
+               if (ndp->d_namlen > sizeof(odp->d_name) - 1)
+                       return EINVAL;
+               odp->d_namlen = ndp->d_namlen;
+               odp->d_type = ndp->d_type;
+               memcpy(odp->d_name, ndp->d_name, (size_t)odp->d_namlen);
+               nameend = &odp->d_name[odp->d_namlen];
+               odp->d_reclen = _DIRENT_SIZE(odp);
+               odp = _DIRENT_NEXT(odp);
+               memset(nameend, 0, (char *)odp - nameend);
+       }
+       *donep = ((char *)(void *)odp) - buf;
+       return 0;
+}
+
+/*
+ * Read a buffer of dirents and optionally convert them to another format.
+ * The conversion is done in place and thus must not increase the size
+ * of an entry.
+ */
+int
+compat_getdents(struct lwp *l, register_t *retval, int fd, char *ubuf,
+    size_t ubufsz, int (*xlate)(char *, int *), off_t *offp)
+{
+       file_t *fp;
+       char *buf;
+       int error, done;
+       size_t bufsz;
+
+       /* Limit the size of the kernel buffer used for vn_readdir() */
+       bufsz = MIN(MAXBSIZE, ubufsz);
+
+       /* fd_getvnode() will use the descriptor for us */
+       if ((error = fd_getvnode(fd, &fp)) != 0)
+               return error;
+       if ((fp->f_flag & FREAD) == 0) {
+               error = EBADF;
+               goto out;
+       }
+       if (offp) {
+               *offp = fp->f_offset;
+       }
+       buf = kmem_alloc(bufsz, KM_SLEEP);
+
+       error = vn_readdir(fp, buf, UIO_SYSSPACE, bufsz, &done, l, NULL, NULL);
+       if (error || done == 0) {
+               goto freeout;
+       }
+       if (xlate) {
+               error = (*xlate)(buf, &done);
+               if (error) {
+                       goto freeout;
+               }
+       }
+       error = copyout(buf, ubuf, done);
+       ktrgenio(fd, UIO_READ, buf, done, error);
+
+freeout:
+       kmem_free(buf, bufsz);
+       *retval = done;
+
+out:
+       fd_putfile(fd);
+       return error;
+}
Index: sys/compat/common/compat_util.h
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/common/compat_util.h,v
retrieving revision 1.22
diff -u -p -r1.22 compat_util.h
--- sys/compat/common/compat_util.h     14 Dec 2009 04:09:38 -0000      1.22
+++ sys/compat/common/compat_util.h     9 Dec 2012 12:46:41 -0000
@@ -79,6 +79,9 @@ unsigned long emul_flags_translate(const
                                   unsigned long in, unsigned long *leftover);
 
 void compat_offseterr(struct vnode *, const char *);
+int compat_native_to_dirent12(char *, int *);
+int compat_getdents(struct lwp *, register_t *, int, char *, size_t,
+    int (*)(char *, int *), off_t *);
 
 int compat_elf_check_interp(struct exec_package *, char *, const char *);
 
Index: sys/compat/common/vfs_syscalls_12.c
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/common/vfs_syscalls_12.c,v
retrieving revision 1.29
diff -u -p -r1.29 vfs_syscalls_12.c
--- sys/compat/common/vfs_syscalls_12.c 19 Jan 2011 10:21:16 -0000      1.29
+++ sys/compat/common/vfs_syscalls_12.c 5 Nov 2012 01:57:24 -0000
@@ -56,6 +56,7 @@ __KERNEL_RCSID(0, "$NetBSD: vfs_syscalls
 #include <sys/syscallargs.h>
 
 #include <compat/sys/stat.h>
+#include <compat/common/compat_util.h>
 
 /*
  * Convert from a new to an old stat structure.
@@ -96,27 +97,16 @@ compat_12_sys_getdirentries(struct lwp *
                syscallarg(u_int) count;
                syscallarg(long *) basep;
        } */
-       struct file *fp;
-       int error, done;
+       off_t off;
+       int error;
        long loff;
 
-       /* fd_getvnode() will use the descriptor for us */
-       if ((error = fd_getvnode(SCARG(uap, fd), &fp)) != 0)
-               return error;
-       if ((fp->f_flag & FREAD) == 0) {
-               error = EBADF;
-               goto out;
+       error = compat_getdents(l, retval, SCARG(uap, fd), SCARG(uap, buf),
+           SCARG(uap, count), compat_native_to_dirent12, &off);
+       if (error == 0) {
+               loff = (long)off;
+               error = copyout(&loff, SCARG(uap, basep), sizeof(long));
        }
-
-       loff = fp->f_offset;
-
-       error = vn_readdir(fp, SCARG(uap, buf), UIO_USERSPACE,
-                       SCARG(uap, count), &done, l, 0, 0);
-
-       error = copyout(&loff, SCARG(uap, basep), sizeof(long));
-       *retval = done;
- out:
-       fd_putfile(SCARG(uap, fd));
        return error;
 }
 
Index: sys/compat/common/vfs_syscalls_30.c
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/common/vfs_syscalls_30.c,v
retrieving revision 1.33
diff -u -p -r1.33 vfs_syscalls_30.c
--- sys/compat/common/vfs_syscalls_30.c 19 Nov 2010 06:44:35 -0000      1.33
+++ sys/compat/common/vfs_syscalls_30.c 26 Oct 2012 16:11:23 -0000
@@ -203,125 +203,10 @@ compat_30_sys_getdents(struct lwp *l, co
                syscallarg(char *) buf;
                syscallarg(size_t) count;
        } */
-       struct dirent *bdp;
-       struct vnode *vp;
-       char *inp, *tbuf;       /* BSD-format */
-       int len, reclen;        /* BSD-format */
-       char *outp;             /* NetBSD-3.0-format */
-       int resid;      
-       struct file *fp;
-       struct uio auio;
-       struct iovec aiov;
-       struct dirent12 idb;
-       off_t off;              /* true file offset */
-       int buflen, error, eofflag;
-       off_t *cookiebuf = NULL, *cookie;
-       int ncookies;
-
-       /* fd_getvnode() will use the descriptor for us */
-       if ((error = fd_getvnode(SCARG(uap, fd), &fp)) != 0)
-               return error;
-
-       if ((fp->f_flag & FREAD) == 0) {
-               error = EBADF;
-               goto out1;
-       }
-
-       vp = fp->f_data;
-       if (vp->v_type != VDIR) {
-               error = EINVAL;
-               goto out1;
-       }
-
-       buflen = min(MAXBSIZE, SCARG(uap, count));
-       tbuf = malloc(buflen, M_TEMP, M_WAITOK);
-       vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
-       off = fp->f_offset;
-again:
-       aiov.iov_base = tbuf;
-       aiov.iov_len = buflen;
-       auio.uio_iov = &aiov;
-       auio.uio_iovcnt = 1;
-       auio.uio_rw = UIO_READ;
-       auio.uio_resid = buflen;
-       auio.uio_offset = off;
-       UIO_SETUP_SYSSPACE(&auio);
-       /*
-         * First we read into the malloc'ed buffer, then
-         * we massage it into user space, one record at a time.
-         */
-       error = VOP_READDIR(vp, &auio, fp->f_cred, &eofflag, &cookiebuf,
-           &ncookies);
-       if (error)
-               goto out;
-
-       inp = tbuf;
-       outp = SCARG(uap, buf);
-       resid = SCARG(uap, count);
-       if ((len = buflen - auio.uio_resid) == 0)
-               goto eof;
-
-       for (cookie = cookiebuf; len > 0; len -= reclen) {
-               bdp = (struct dirent *)inp;
-               reclen = bdp->d_reclen;
-               if (reclen & _DIRENT_ALIGN(bdp))
-                       panic("netbsd30_getdents: bad reclen %d", reclen);
-               if (cookie)
-                       off = *cookie++; /* each entry points to the next */
-               else
-                       off += reclen;
-               if ((off >> 32) != 0) {
-                       compat_offseterr(vp, "netbsd30_getdents");
-                       error = EINVAL;
-                       goto out;
-               }
-               if (bdp->d_namlen >= sizeof(idb.d_name))
-                       idb.d_namlen = sizeof(idb.d_name) - 1;
-               else
-                       idb.d_namlen = bdp->d_namlen;
-               idb.d_reclen = _DIRENT_SIZE(&idb);
-               if (reclen > len || resid < idb.d_reclen) {
-                       /* entry too big for buffer, so just stop */
-                       outp++;
-                       break;
-               }
-               /*
-                * Massage in place to make a NetBSD-3.0-shaped dirent
-                * (otherwise we have to worry about touching user memory
-                * outside of the copyout() call).
-                */
-               idb.d_fileno = (u_int32_t)bdp->d_fileno;
-               idb.d_type = bdp->d_type;
-               (void)memcpy(idb.d_name, bdp->d_name, idb.d_namlen);
-               memset(idb.d_name + idb.d_namlen, 0,
-                   idb.d_reclen - _DIRENT_NAMEOFF(&idb) - idb.d_namlen);
-               if ((error = copyout(&idb, outp, idb.d_reclen)) != 0)
-                       goto out;
-               /* advance past this real entry */
-               inp += reclen;
-               /* advance output past NetBSD-3.0-shaped entry */
-               outp += idb.d_reclen;
-               resid -= idb.d_reclen;
-       }
-
-       /* if we squished out the whole block, try again */
-       if (outp == SCARG(uap, buf)) {
-               if (cookiebuf)
-                       free(cookiebuf, M_TEMP);
-               cookiebuf = NULL;
-               goto again;
-       }
-       fp->f_offset = off;     /* update the vnode offset */
+       int error;
 
-eof:
-       *retval = SCARG(uap, count) - resid;
-out:
-       VOP_UNLOCK(vp);
-       if (cookiebuf)
-               free(cookiebuf, M_TEMP);
-       free(tbuf, M_TEMP);
-out1:
-       fd_putfile(SCARG(uap, fd));
+       error = compat_getdents(l, retval, SCARG(uap, fd), SCARG(uap, buf),
+           SCARG(uap, count), compat_native_to_dirent12, NULL);
        return error;
 }
 
Index: sys/compat/netbsd32/netbsd32_compat_30.c
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/netbsd32/netbsd32_compat_30.c,v
retrieving revision 1.30
diff -u -p -r1.30 netbsd32_compat_30.c
--- sys/compat/netbsd32/netbsd32_compat_30.c    23 Apr 2010 15:19:20 -0000      
1.30
+++ sys/compat/netbsd32/netbsd32_compat_30.c    26 Oct 2012 16:11:23 -0000
@@ -53,7 +53,7 @@ __KERNEL_RCSID(0, "$NetBSD: netbsd32_com
 #include <compat/netbsd32/netbsd32_syscallargs.h>
 #include <compat/netbsd32/netbsd32_conv.h>
 #include <compat/sys/mount.h>
-
+#include <compat/common/compat_util.h>
 
 int
 compat_30_netbsd32_getdents(struct lwp *l, const struct 
compat_30_netbsd32_getdents_args *uap, register_t *retval)
@@ -63,31 +63,11 @@ compat_30_netbsd32_getdents(struct lwp *
                syscallarg(netbsd32_charp) buf;
                syscallarg(netbsd32_size_t) count;
        } */
-       file_t *fp;
-       int error, done;
-       char  *buf;
-       netbsd32_size_t count;
-
-       /* Limit the size on any kernel buffers used by VOP_READDIR */
-       count = min(MAXBSIZE, SCARG(uap, count));
-
-       /* fd_getvnode() will use the descriptor for us */
-       if ((error = fd_getvnode(SCARG(uap, fd), &fp)) != 0)
-               return (error);
-       if ((fp->f_flag & FREAD) == 0) {
-               error = EBADF;
-               goto out;
-       }
-       buf = kmem_alloc(count, KM_SLEEP);
-       error = vn_readdir(fp, buf, UIO_SYSSPACE, count, &done, l, 0, 0);
-       if (error == 0) {
-               *retval = netbsd32_to_dirent12(buf, done);
-               error = copyout(buf, SCARG_P32(uap, buf), *retval);
-       }
-       kmem_free(buf, count);
- out:
-       fd_putfile(SCARG(uap, fd));
-       return (error);
+       int error;
+
+       error = compat_getdents(l, retval, SCARG(uap, fd), SCARG_P32(uap, buf),
+           SCARG(uap, count), compat_native_to_dirent12, NULL);
+       return error;
 }
 
 int
Index: sys/compat/netbsd32/netbsd32_conv.h
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/netbsd32/netbsd32_conv.h,v
retrieving revision 1.26
diff -u -p -r1.26 netbsd32_conv.h
--- sys/compat/netbsd32/netbsd32_conv.h 6 Mar 2011 17:08:34 -0000       1.26
+++ sys/compat/netbsd32/netbsd32_conv.h 26 Oct 2012 16:11:23 -0000
@@ -726,37 +726,6 @@ netbsd32_to_sigevent(const struct netbsd
        ev->sigev_notify_attributes = 
NETBSD32PTR64(ev32->sigev_notify_attributes);
 }
 
-static __inline int
-netbsd32_to_dirent12(char *buf, int nbytes)
-{
-       struct dirent *ndp, *nndp, *endp;
-       struct dirent12 *odp;
-
-       odp = (struct dirent12 *)(void *)buf;
-       ndp = (struct dirent *)(void *)buf;
-       endp = (struct dirent *)(void *)&buf[nbytes];
-
-       /*
-        * In-place conversion. This works because odp
-        * is smaller than ndp, but it has to be done
-        * in the right sequence.
-        */
-       for (; ndp < endp; ndp = nndp) {
-               nndp = _DIRENT_NEXT(ndp);
-               odp->d_fileno = (u_int32_t)ndp->d_fileno;
-               if (ndp->d_namlen >= sizeof(odp->d_name))
-                       odp->d_namlen = sizeof(odp->d_name) - 1;
-               else
-                       odp->d_namlen = (u_int8_t)ndp->d_namlen;
-               odp->d_type = ndp->d_type;
-               (void)memcpy(odp->d_name, ndp->d_name, (size_t)odp->d_namlen);
-               odp->d_name[odp->d_namlen] = '\0';
-               odp->d_reclen = _DIRENT_SIZE(odp);
-               odp = _DIRENT_NEXT(odp);
-       }
-       return ((char *)(void *)odp) - buf;
-}
-
 static inline int
 netbsd32_copyin_plistref(netbsd32_pointer_t n32p, struct plistref *p)
 {
Index: sys/compat/netbsd32/netbsd32_fs.c
===================================================================
RCS file: /home/chs/netbsd/cvs/src/sys/compat/netbsd32/netbsd32_fs.c,v
retrieving revision 1.63
diff -u -p -r1.63 netbsd32_fs.c
--- sys/compat/netbsd32/netbsd32_fs.c   6 Mar 2012 07:37:05 -0000       1.63
+++ sys/compat/netbsd32/netbsd32_fs.c   26 Oct 2012 16:11:23 -0000
@@ -484,22 +484,13 @@ netbsd32___getdents30(struct lwp *l,
                syscallarg(netbsd32_charp) buf;
                syscallarg(netbsd32_size_t) count;
        } */
-       file_t *fp;
-       int error, done;
+       int fd = SCARG(uap, fd);
+       char *buf = SCARG_P32(uap, buf);
+       size_t count = SCARG(uap, count);
+       int error;
 
-       /* fd_getvnode() will use the descriptor for us */
-       if ((error = fd_getvnode(SCARG(uap, fd), &fp)) != 0)
-               return (error);
-       if ((fp->f_flag & FREAD) == 0) {
-               error = EBADF;
-               goto out;
-       }
-       error = vn_readdir(fp, SCARG_P32(uap, buf),
-           UIO_USERSPACE, SCARG(uap, count), &done, l, 0, 0);
-       *retval = done;
- out:
-       fd_putfile(SCARG(uap, fd));
-       return (error);
+       error = compat_getdents(l, retval, fd, buf, count, NULL, NULL);
+       return error;
 }
 
 int


Home | Main Index | Thread Index | Old Index