1/* $NetBSD: linux_file64.c,v 1.52 2011/09/01 12:44:10 njoly Exp $ */ 2 3/*- 4 * Copyright (c) 1995, 1998, 2000, 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Frank van der Linden and Eric Haszlakiewicz. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32/* 33 * Linux 64bit filesystem calls. Used on 32bit archs, not used on 64bit ones. 34 */ 35 36#include <sys/cdefs.h> 37__KERNEL_RCSID(0, "$NetBSD: linux_file64.c,v 1.52 2011/09/01 12:44:10 njoly Exp $"); 38 39#include <sys/param.h> 40#include <sys/systm.h> 41#include <sys/namei.h> 42#include <sys/proc.h> 43#include <sys/dirent.h> 44#include <sys/file.h> 45#include <sys/stat.h> 46#include <sys/filedesc.h> 47#include <sys/ioctl.h> 48#include <sys/kernel.h> 49#include <sys/mount.h> 50#include <sys/malloc.h> 51#include <sys/namei.h> 52#include <sys/vfs_syscalls.h> 53#include <sys/vnode.h> 54#include <sys/tty.h> 55#include <sys/conf.h> 56 57#include <sys/syscallargs.h> 58 59#include <compat/linux/common/linux_types.h> 60#include <compat/linux/common/linux_signal.h> 61#include <compat/linux/common/linux_fcntl.h> 62#include <compat/linux/common/linux_util.h> 63#include <compat/linux/common/linux_machdep.h> 64#include <compat/linux/common/linux_dirent.h> 65#include <compat/linux/common/linux_ipc.h> 66#include <compat/linux/common/linux_sem.h> 67 68#include <compat/linux/linux_syscallargs.h> 69 70#ifndef alpha 71 72static void bsd_to_linux_stat(struct stat *, struct linux_stat64 *); 73 74/* 75 * Convert a NetBSD stat structure to a Linux stat structure. 76 * Only the order of the fields and the padding in the structure 77 * is different. linux_fakedev is a machine-dependent function 78 * which optionally converts device driver major/minor numbers 79 * (XXX horrible, but what can you do against code that compares 80 * things against constant major device numbers? sigh) 81 */ 82static void 83bsd_to_linux_stat(struct stat *bsp, struct linux_stat64 *lsp) 84{ 85 lsp->lst_dev = linux_fakedev(bsp->st_dev, 0); 86 lsp->lst_ino = bsp->st_ino; 87 lsp->lst_mode = (linux_mode_t)bsp->st_mode; 88 if (bsp->st_nlink >= (1 << 15)) 89 lsp->lst_nlink = (1 << 15) - 1; 90 else 91 lsp->lst_nlink = (linux_nlink_t)bsp->st_nlink; 92 lsp->lst_uid = bsp->st_uid; 93 lsp->lst_gid = bsp->st_gid; 94 lsp->lst_rdev = linux_fakedev(bsp->st_rdev, 1); 95 lsp->lst_size = bsp->st_size; 96 lsp->lst_blksize = bsp->st_blksize; 97 lsp->lst_blocks = bsp->st_blocks; 98 lsp->lst_atime = bsp->st_atime; 99 lsp->lst_mtime = bsp->st_mtime; 100 lsp->lst_ctime = bsp->st_ctime; 101# ifdef LINUX_STAT64_HAS_NSEC 102 lsp->lst_atime_nsec = bsp->st_atimensec; 103 lsp->lst_mtime_nsec = bsp->st_mtimensec; 104 lsp->lst_ctime_nsec = bsp->st_ctimensec; 105# endif 106# if LINUX_STAT64_HAS_BROKEN_ST_INO 107 lsp->__lst_ino = (linux_ino_t) bsp->st_ino; 108# endif 109} 110 111/* 112 * The stat functions below are plain sailing. stat and lstat are handled 113 * by one function to avoid code duplication. 114 */ 115int 116linux_sys_fstat64(struct lwp *l, const struct linux_sys_fstat64_args *uap, register_t *retval) 117{ 118 /* { 119 syscallarg(int) fd; 120 syscallarg(struct linux_stat64 *) sp; 121 } */ 122 struct linux_stat64 tmplst; 123 struct stat tmpst; 124 int error; 125 126 error = do_sys_fstat(SCARG(uap, fd), &tmpst); 127 if (error != 0) 128 return error; 129 130 bsd_to_linux_stat(&tmpst, &tmplst); 131 132 return copyout(&tmplst, SCARG(uap, sp), sizeof tmplst); 133} 134 135static int 136linux_do_stat64(struct lwp *l, const struct linux_sys_stat64_args *uap, register_t *retval, int flags) 137{ 138 struct linux_stat64 tmplst; 139 struct stat tmpst; 140 int error; 141 142 error = do_sys_stat(SCARG(uap, path), flags, &tmpst); 143 if (error != 0) 144 return error; 145 146 bsd_to_linux_stat(&tmpst, &tmplst); 147 148 return copyout(&tmplst, SCARG(uap, sp), sizeof tmplst); 149} 150 151int 152linux_sys_stat64(struct lwp *l, const struct linux_sys_stat64_args *uap, register_t *retval) 153{ 154 /* { 155 syscallarg(const char *) path; 156 syscallarg(struct linux_stat64 *) sp; 157 } */ 158 159 return linux_do_stat64(l, uap, retval, FOLLOW); 160} 161 162int 163linux_sys_lstat64(struct lwp *l, const struct linux_sys_lstat64_args *uap, register_t *retval) 164{ 165 /* { 166 syscallarg(const char *) path; 167 syscallarg(struct linux_stat64 *) sp; 168 } */ 169 170 return linux_do_stat64(l, (const void *)uap, retval, NOFOLLOW); 171} 172 173int 174linux_sys_truncate64(struct lwp *l, const struct linux_sys_truncate64_args *uap, register_t *retval) 175{ 176 /* { 177 syscallarg(const char *) path; 178 syscallarg(off_t) length; 179 } */ 180 struct sys_truncate_args ta; 181 182 /* Linux doesn't have the 'pad' pseudo-parameter */ 183 SCARG(&ta, path) = SCARG(uap, path); 184 SCARG(&ta, PAD) = 0; 185 SCARG(&ta, length) = SCARG(uap, length); 186 187 return sys_truncate(l, &ta, retval); 188} 189 190int 191linux_sys_ftruncate64(struct lwp *l, const struct linux_sys_ftruncate64_args *uap, register_t *retval) 192{ 193 /* { 194 syscallarg(unsigned int) fd; 195 syscallarg(off_t) length; 196 } */ 197 struct sys_ftruncate_args ta; 198 199 /* Linux doesn't have the 'pad' pseudo-parameter */ 200 SCARG(&ta, fd) = SCARG(uap, fd); 201 SCARG(&ta, PAD) = 0; 202 SCARG(&ta, length) = SCARG(uap, length); 203 204 return sys_ftruncate(l, &ta, retval); 205} 206#endif /* !alpha */ 207 208/* 209 * Linux 'readdir' call. This code is mostly taken from the 210 * SunOS getdents call (see compat/sunos/sunos_misc.c), though 211 * an attempt has been made to keep it a little cleaner. 212 * 213 * The d_off field contains the offset of the next valid entry, 214 * unless the older Linux getdents(2), which used to have it set 215 * to the offset of the entry itself. This function also doesn't 216 * need to deal with the old count == 1 glibc problem. 217 * 218 * Read in BSD-style entries, convert them, and copy them out. 219 * 220 * Note that this doesn't handle union-mounted filesystems. 221 */ 222int 223linux_sys_getdents64(struct lwp *l, const struct linux_sys_getdents64_args *uap, register_t *retval) 224{ 225 /* { 226 syscallarg(int) fd; 227 syscallarg(struct linux_dirent64 *) dent; 228 syscallarg(unsigned int) count; 229 } */ 230 struct dirent *bdp; 231 struct vnode *vp; 232 char *inp, *tbuf; /* BSD-format */ 233 int len, reclen; /* BSD-format */ 234 char *outp; /* Linux-format */ 235 int resid, linux_reclen = 0; /* Linux-format */ 236 file_t *fp; 237 struct uio auio; 238 struct iovec aiov; 239 struct linux_dirent64 idb; 240 off_t off; /* true file offset */ 241 int buflen, error, eofflag, nbytes; 242 struct vattr va; 243 off_t *cookiebuf = NULL, *cookie; 244 int ncookies; 245 246 /* fd_getvnode() will use the descriptor for us */ 247 if ((error = fd_getvnode(SCARG(uap, fd), &fp)) != 0) 248 return (error); 249 250 if ((fp->f_flag & FREAD) == 0) { 251 error = EBADF; 252 goto out1; 253 } 254 255 vp = (struct vnode *)fp->f_data; 256 if (vp->v_type != VDIR) { 257 error = ENOTDIR; 258 goto out1; 259 } 260 261 vn_lock(vp, LK_SHARED | LK_RETRY); 262 error = VOP_GETATTR(vp, &va, l->l_cred); 263 VOP_UNLOCK(vp); 264 if (error) 265 goto out1; 266 267 nbytes = SCARG(uap, count); 268 buflen = min(MAXBSIZE, nbytes); 269 if (buflen < va.va_blocksize) 270 buflen = va.va_blocksize; 271 tbuf = malloc(buflen, M_TEMP, M_WAITOK); 272 273 vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); 274 off = fp->f_offset; 275again: 276 aiov.iov_base = tbuf; 277 aiov.iov_len = buflen; 278 auio.uio_iov = &aiov; 279 auio.uio_iovcnt = 1; 280 auio.uio_rw = UIO_READ; 281 auio.uio_resid = buflen; 282 auio.uio_offset = off; 283 UIO_SETUP_SYSSPACE(&auio); 284 /* 285 * First we read into the malloc'ed buffer, then 286 * we massage it into user space, one record at a time. 287 */ 288 error = VOP_READDIR(vp, &auio, fp->f_cred, &eofflag, &cookiebuf, 289 &ncookies); 290 if (error) 291 goto out; 292 293 inp = tbuf; 294 outp = (void *)SCARG(uap, dent); 295 resid = nbytes; 296 if ((len = buflen - auio.uio_resid) == 0) 297 goto eof; 298 299 for (cookie = cookiebuf; len > 0; len -= reclen) { 300 bdp = (struct dirent *)inp; 301 reclen = bdp->d_reclen; 302 if (reclen & 3) 303 panic("linux_readdir"); 304 if (bdp->d_fileno == 0) { 305 inp += reclen; /* it is a hole; squish it out */ 306 if (cookie) 307 off = *cookie++; 308 else 309 off += reclen; 310 continue; 311 } 312 linux_reclen = LINUX_RECLEN(&idb, bdp->d_namlen); 313 if (reclen > len || resid < linux_reclen) { 314 /* entry too big for buffer, so just stop */ 315 outp++; 316 break; 317 } 318 if (cookie) 319 off = *cookie++; /* each entry points to next */ 320 else 321 off += reclen; 322 /* 323 * Massage in place to make a Linux-shaped dirent (otherwise 324 * we have to worry about touching user memory outside of 325 * the copyout() call). 326 */ 327 idb.d_ino = bdp->d_fileno; 328 idb.d_type = bdp->d_type; 329 idb.d_off = off; 330 idb.d_reclen = (u_short)linux_reclen; 331 strcpy(idb.d_name, bdp->d_name); 332 if ((error = copyout((void *)&idb, outp, linux_reclen))) 333 goto out; 334 /* advance past this real entry */ 335 inp += reclen; 336 /* advance output past Linux-shaped entry */ 337 outp += linux_reclen; 338 resid -= linux_reclen; 339 } 340 341 /* if we squished out the whole block, try again */ 342 if (outp == (void *)SCARG(uap, dent)) { 343 if (cookiebuf) 344 free(cookiebuf, M_TEMP); 345 cookiebuf = NULL; 346 goto again; 347 } 348 fp->f_offset = off; /* update the vnode offset */ 349 350eof: 351 *retval = nbytes - resid; 352out: 353 VOP_UNLOCK(vp); 354 if (cookiebuf) 355 free(cookiebuf, M_TEMP); 356 free(tbuf, M_TEMP); 357out1: 358 fd_putfile(SCARG(uap, fd)); 359 return error; 360} 361