diff options
-rw-r--r-- | sys/compat/linux/linux_file.c | 87 |
1 files changed, 54 insertions, 33 deletions
diff --git a/sys/compat/linux/linux_file.c b/sys/compat/linux/linux_file.c index dafdb3a..6643e71 100644 --- a/sys/compat/linux/linux_file.c +++ b/sys/compat/linux/linux_file.c @@ -303,9 +303,20 @@ struct l_dirent64 { char d_name[LINUX_NAME_MAX + 1]; }; -#define LINUX_RECLEN(de,namlen) \ - ALIGN((((char *)&(de)->d_name - (char *)de) + (namlen) + 1)) +/* + * Linux uses the last byte in the dirent buffer to store d_type, + * at least glibc-2.7 requires it. That is why l_dirent is padded with 2 bytes. + */ +#define LINUX_RECLEN(namlen) \ + roundup((offsetof(struct l_dirent, d_name) + (namlen) + 2), \ + sizeof(l_ulong)) + +#define LINUX_RECLEN64(namlen) \ + roundup((offsetof(struct l_dirent64, d_name) + (namlen) + 1), \ + sizeof(uint64_t)) +#define LINUX_MAXRECLEN max(LINUX_RECLEN(LINUX_NAME_MAX), \ + LINUX_RECLEN64(LINUX_NAME_MAX)) #define LINUX_DIRBLKSIZ 512 static int @@ -318,12 +329,13 @@ getdents_common(struct thread *td, struct linux_getdents64_args *args, int len, reclen; /* BSD-format */ caddr_t outp; /* Linux-format */ int resid, linuxreclen=0; /* Linux-format */ + caddr_t lbuf; /* Linux-format */ struct file *fp; struct uio auio; struct iovec aiov; off_t off; - struct l_dirent linux_dirent; - struct l_dirent64 linux_dirent64; + struct l_dirent *linux_dirent; + struct l_dirent64 *linux_dirent64; int buflen, error, eofflag, nbytes, justone; u_long *cookies = NULL, *cookiep; int ncookies, vfslocked; @@ -359,6 +371,7 @@ getdents_common(struct thread *td, struct linux_getdents64_args *args, buflen = max(LINUX_DIRBLKSIZ, nbytes); buflen = min(buflen, MAXBSIZE); buf = malloc(buflen, M_TEMP, M_WAITOK); + lbuf = malloc(LINUX_MAXRECLEN, M_TEMP, M_WAITOK | M_ZERO); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); again: @@ -436,8 +449,8 @@ again: } linuxreclen = (is64bit) - ? LINUX_RECLEN(&linux_dirent64, bdp->d_namlen) - : LINUX_RECLEN(&linux_dirent, bdp->d_namlen); + ? LINUX_RECLEN64(bdp->d_namlen) + : LINUX_RECLEN(bdp->d_namlen); if (reclen > len || resid < linuxreclen) { outp++; @@ -446,34 +459,41 @@ again: if (justone) { /* readdir(2) case. */ - linux_dirent.d_ino = bdp->d_fileno; - linux_dirent.d_off = (l_off_t)linuxreclen; - linux_dirent.d_reclen = (l_ushort)bdp->d_namlen; - strcpy(linux_dirent.d_name, bdp->d_name); - error = copyout(&linux_dirent, outp, linuxreclen); - } else { - if (is64bit) { - linux_dirent64.d_ino = bdp->d_fileno; - linux_dirent64.d_off = (cookiep) - ? (l_off_t)*cookiep - : (l_off_t)(off + reclen); - linux_dirent64.d_reclen = - (l_ushort)linuxreclen; - linux_dirent64.d_type = bdp->d_type; - strcpy(linux_dirent64.d_name, bdp->d_name); - error = copyout(&linux_dirent64, outp, - linuxreclen); - } else { - linux_dirent.d_ino = bdp->d_fileno; - linux_dirent.d_off = (cookiep) - ? (l_off_t)*cookiep - : (l_off_t)(off + reclen); - linux_dirent.d_reclen = (l_ushort)linuxreclen; - strcpy(linux_dirent.d_name, bdp->d_name); - error = copyout(&linux_dirent, outp, - linuxreclen); - } + linux_dirent = (struct l_dirent*)lbuf; + linux_dirent->d_ino = bdp->d_fileno; + linux_dirent->d_off = (l_off_t)linuxreclen; + linux_dirent->d_reclen = (l_ushort)bdp->d_namlen; + strlcpy(linux_dirent->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent, d_name)); + error = copyout(linux_dirent, outp, linuxreclen); } + if (is64bit) { + linux_dirent64 = (struct l_dirent64*)lbuf; + linux_dirent64->d_ino = bdp->d_fileno; + linux_dirent64->d_off = (cookiep) + ? (l_off_t)*cookiep + : (l_off_t)(off + reclen); + linux_dirent64->d_reclen = (l_ushort)linuxreclen; + linux_dirent64->d_type = bdp->d_type; + strlcpy(linux_dirent64->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent64, d_name)); + error = copyout(linux_dirent64, outp, linuxreclen); + } else if (!justone) { + linux_dirent = (struct l_dirent*)lbuf; + linux_dirent->d_ino = bdp->d_fileno; + linux_dirent->d_off = (cookiep) + ? (l_off_t)*cookiep + : (l_off_t)(off + reclen); + linux_dirent->d_reclen = (l_ushort)linuxreclen; + /* + * Copy d_type to last byte of l_dirent buffer + */ + lbuf[linuxreclen-1] = bdp->d_type; + strlcpy(linux_dirent->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent, d_name)-1); + error = copyout(linux_dirent, outp, linuxreclen); + } + if (error) goto out; @@ -509,6 +529,7 @@ out: VFS_UNLOCK_GIANT(vfslocked); fdrop(fp, td); free(buf, M_TEMP); + free(lbuf, M_TEMP); return (error); } |