LCOV - code coverage report
Current view: directory - src/shared/platform/linux - nacl_host_dir.c (source / functions) Found Hit Coverage
Test: coverage.lcov Lines: 106 86 81.1 %
Date: 2014-10-23 Functions: 0 0 -

       1                 : /*
       2                 :  * Copyright (c) 2011 The Native Client Authors. All rights reserved.
       3                 :  * Use of this source code is governed by a BSD-style license that can be
       4                 :  * found in the LICENSE file.
       5                 :  */
       6                 : 
       7                 : /*
       8                 :  * NaCl Service Runtime.  Directory descriptor / Handle abstraction.
       9                 :  *
      10                 :  * Note that we avoid using the thread-specific data / thread local
      11                 :  * storage access to the "errno" variable, and instead use the raw
      12                 :  * system call return interface of small negative numbers as errors.
      13                 :  */
      14                 : 
      15                 : #include <dirent.h>
      16                 : #include <errno.h>
      17                 : #include <fcntl.h>
      18                 : #include <limits.h>
      19                 : #include <linux/types.h>
      20                 : #include <linux/unistd.h>
      21                 : #include <stddef.h>
      22                 : #include <stdint.h>
      23                 : #include <string.h>
      24                 : #include <sys/mman.h>
      25                 : #include <sys/stat.h>
      26                 : #include <sys/types.h>
      27                 : #include <unistd.h>
      28                 : 
      29                 : #include "native_client/src/shared/platform/nacl_check.h"
      30                 : #include "native_client/src/shared/platform/nacl_host_desc.h"
      31                 : #include "native_client/src/shared/platform/nacl_host_dir.h"
      32                 : #include "native_client/src/shared/platform/nacl_log.h"
      33                 : #include "native_client/src/shared/platform/nacl_sync.h"
      34                 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
      35                 : 
      36                 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
      37                 : 
      38                 : #include "native_client/src/trusted/service_runtime/include/bits/mman.h"
      39                 : #include "native_client/src/trusted/service_runtime/include/sys/dirent.h"
      40                 : #include "native_client/src/trusted/service_runtime/include/sys/errno.h"
      41                 : #include "native_client/src/trusted/service_runtime/include/sys/fcntl.h"
      42                 : #include "native_client/src/trusted/service_runtime/include/sys/stat.h"
      43                 : 
      44                 : #ifdef _syscall3
      45                 : _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
      46                 : 
      47                 : int getdents(unsigned int fd, struct dirent* dirp, unsigned int count);
      48                 : #else
      49                 : # include <sys/syscall.h>
      50               9 : int getdents(unsigned int fd, struct dirent* dirp, unsigned int count) {
      51               9 :   return syscall(__NR_getdents, fd, dirp, count);
      52                 : }
      53                 : #endif
      54                 : 
      55                 : struct linux_dirent {  /* offsets, ILP32 and LP64 */
      56                 :   unsigned long  d_ino;             /*  0,  0 */
      57                 :   unsigned long  d_off;             /*  4,  8 */
      58                 :   unsigned short d_reclen;          /*  8, 16 */
      59                 :   char           d_name[1];         /* 10, 18 */
      60                 :   /* actual length is d_reclen - 2 - offsetof(struct linux_dirent, d_name) */
      61                 :   /*
      62                 :    * char pad;    / Zero padding byte
      63                 :    * char d_type; / File type (only since Linux 2.6.4; offset is d_reclen - 1)
      64                 :    */
      65                 : };
      66                 : 
      67                 : 
      68                 : /*
      69                 :  * from native_client/src/trusted/service_runtime/include/sys/dirent.h:
      70                 : 
      71                 : struct nacl_abi_dirent {                                 offsets, NaCl
      72                 :   nacl_abi_ino_t nacl_abi_d_ino;                                0
      73                 :   nacl_abi_off_t nacl_abi_d_off;                                8
      74                 :   uint16_t       nacl_abi_d_reclen;                            16
      75                 :   char           nacl_abi_d_name[NACL_ABI_MAXNAMLEN + 1];      18
      76                 : };
      77                 : 
      78                 :  * It would be nice if we didn't have to buffer dirent data.  Is it
      79                 :  * possible, when the untrusted NaCl module invokes getdent, to
      80                 :  * determine a (different) buffer size value to use with the host OS
      81                 :  * (Linux)?
      82                 :  *
      83                 :  * Since the actual length is d_reclen, do we know if d_reclen is
      84                 :  * padded out to a multiple of the alignment restriction for longs?
      85                 :  * Best to assume that either may hold. On x86 where unaligned
      86                 :  * accesses are okay, this might be fine; on other architectures where
      87                 :  * it isn't, then presumably d_reclen will include the padding, and
      88                 :  * d_type (which we don't use) is on the far end of the padding.
      89                 :  *
      90                 :  * Given a user-supplied buffer of N bytes to hold nacl_abi_dirent
      91                 :  * entries, what is the size of the buffer that should be supplied to
      92                 :  * the Linux kernel so that we will never end up with more information
      93                 :  * than we can copy / return back to the user?
      94                 :  *
      95                 :  * For LP64, the answer is relatively simple: everything is the same
      96                 :  * size, except that the kernel is going to use one more byte for the
      97                 :  * d_type entry.  Since copying to the nacl_abi_dirent omits that, the
      98                 :  * transfer shrinks the space needed.
      99                 :  *
     100                 :  * For ILP32, the answer is a little harder.  The linux_dirent use 8
     101                 :  * fewer bytes for the entries before d_name, but one more at the end
     102                 :  * for d_type.  So, when does the worst case expansion occur?  The
     103                 :  * number of dirent entries is multiplied by the expansion, so we need
     104                 :  * to determine the smallest dirent entry.  Assuming single character
     105                 :  * file names, a user's buffer of size N can hold int(N/20) dirents.
     106                 :  * The linux_dirent, with no alignment pads, but with d_type (we
     107                 :  * assume newer kernels only), will take 13 bytes per entry, so we had
     108                 :  * better not claim to have more than 13*int(N/20) bytes of space
     109                 :  * available to the Linux kernel.  (We don't need to check in our
     110                 :  * platform qualification check since "older" kernels are pre 2.6.4,
     111                 :  * and all reasonable Linux distributions use newer kernels.)
     112                 :  *
     113                 :  * Suppose the user gave us a buffer of 40 bytes.  This is enough for
     114                 :  * two dirent structures containing information about files each with
     115                 :  * a single character name.  So, we invoke Linux's getdents with a 26
     116                 :  * byte buffer.  What happens if the next actual directory entry's
     117                 :  * name is 40-18-1=21 bytes long?  It would have fit in the user's
     118                 :  * buffer, but the linux_dirent buffer required for that entry is
     119                 :  * 10+21+2 bytes in length, or 33 bytes.  We supplied linux with a 26
     120                 :  * byte buffer, so it should respond with EINVAL since the buffer
     121                 :  * space is too small.
     122                 :  *
     123                 :  * This argues against a simple scheme that avoids buffering.
     124                 :  *
     125                 :  * We could, when we encounter EINVAL, increase the buffer size used
     126                 :  * with the host OS.  How do we expand and could that might result in
     127                 :  * too much being read in?
     128                 :  */
     129                 : 
     130               4 : int NaClHostDirCtor(struct NaClHostDir  *d,
     131                 :                     int                 dir_desc) {
     132               4 :   if (!NaClMutexCtor(&d->mu)) {
     133               0 :     return -NACL_ABI_ENOMEM;
     134                 :   }
     135               4 :   d->fd = dir_desc;
     136               4 :   d->cur_byte = 0;
     137               4 :   d->nbytes = 0;
     138               4 :   NaClLog(3, "NaClHostDirCtor: success.\n");
     139               4 :   return 0;
     140                 : }
     141                 : 
     142               4 : int NaClHostDirOpen(struct NaClHostDir  *d,
     143                 :                     char                *path) {
     144                 :   int         fd;
     145                 :   struct stat stbuf;
     146                 :   int         rv;
     147                 : 
     148               4 :   NaClLog(3, "NaClHostDirOpen(0x%08"NACL_PRIxPTR", %s)\n", (uintptr_t) d, path);
     149               4 :   if (NULL == d) {
     150               0 :     NaClLog(LOG_FATAL, "NaClHostDirOpen: 'this' is NULL\n");
     151                 :   }
     152                 : 
     153               4 :   NaClLog(3, "NaClHostDirOpen: invoking open(%s)\n", path);
     154               4 :   fd = open(path, O_RDONLY);
     155               4 :   NaClLog(3, "NaClHostDirOpen: got DIR* %d\n", fd);
     156               4 :   if (-1 == fd) {
     157               0 :     NaClLog(LOG_ERROR,
     158               0 :             "NaClHostDirOpen: open returned -1, errno %d\n", errno);
     159               0 :     return -NaClXlateErrno(errno);
     160                 :   }
     161                 :   /* check that it is really a directory */
     162               4 :   if (-1 == fstat(fd, &stbuf)) {
     163               0 :     NaClLog(LOG_ERROR,
     164               0 :             "NaClHostDirOpen: fstat failed?!?  errno %d\n", errno);
     165               0 :     (void) close(fd);
     166               0 :     return -NaClXlateErrno(errno);
     167                 :   }
     168               4 :   if (!S_ISDIR(stbuf.st_mode)) {
     169               0 :     (void) close(fd);
     170               0 :     return -NACL_ABI_ENOTDIR;
     171                 :   }
     172               4 :   rv = NaClHostDirCtor(d, fd);
     173               4 :   return rv;
     174                 : }
     175                 : 
     176                 : /*
     177                 :  * Copy and translate a single linux_dirent to nacl_abi_dirent.
     178                 :  * Returns number of bytes consumed (includes alignment adjustment for
     179                 :  * next entry).
     180                 :  *
     181                 :  * TODO(bsy): add filesystem info argument to specify which
     182                 :  * directories are "root" inodes, to rewrite the inode number of '..'
     183                 :  * as appropriate.
     184                 :  */
     185              92 : static ssize_t NaClCopyDirent(struct NaClHostDir *d,
     186                 :                               void               *buf,
     187                 :                               size_t             len) {
     188              92 :   struct linux_dirent             *ldp = (struct linux_dirent *) (
     189              92 :       d->dirent_buf
     190              92 :       + d->cur_byte);
     191                 :   struct nacl_abi_dirent volatile *nadp;
     192                 :   size_t                          adjusted_size;
     193                 : 
     194                 :   /* make sure the buffer is aligned */
     195              92 :   CHECK(0 == ((sizeof(nacl_abi_ino_t) - 1) & (uintptr_t) buf));
     196                 : 
     197              92 :   if (d->cur_byte == d->nbytes) {
     198               9 :     return 0;  /* none available */
     199                 :   }
     200              83 :   CHECK(d->cur_byte < d->nbytes);
     201              83 :   CHECK(ldp->d_reclen <= d->nbytes - d->cur_byte);
     202                 :   /* no partial record transferred. */
     203                 : 
     204              83 :   nadp = (struct nacl_abi_dirent volatile *) buf;
     205                 : 
     206                 :   /*
     207                 :    * is there enough space? assume Linux is sane, so no ssize_t
     208                 :    * overflow in the adjusted_size computation.  (NAME_MAX is small.)
     209                 :    */
     210                 :   CHECK(NAME_MAX < 256);
     211              83 :   adjusted_size = offsetof(struct nacl_abi_dirent, nacl_abi_d_name)
     212              83 :       + strlen(ldp->d_name) + 1;  /* NUL termination */
     213                 :   /* pad for alignment for access to d_ino */
     214              83 :   adjusted_size = (adjusted_size + (sizeof(nacl_abi_ino_t) - 1))
     215                 :       & ~(sizeof(nacl_abi_ino_t) - 1);
     216              83 :   if (len < adjusted_size) {
     217              12 :     return -NACL_ABI_EINVAL;  /* result buffer is too small */
     218                 :   }
     219                 : 
     220                 : #if defined(NACL_MASK_INODES)
     221              71 :   nadp->nacl_abi_d_ino = NACL_FAKE_INODE_NUM;
     222                 : #else
     223                 :   nadp->nacl_abi_d_ino = ldp->d_ino;
     224                 : #endif
     225              71 :   nadp->nacl_abi_d_off = ldp->d_off;
     226              71 :   nadp->nacl_abi_d_reclen = adjusted_size;
     227              71 :   NaClLog(4, "NaClCopyDirent: %s\n", ldp->d_name);
     228              71 :   strcpy((char *) nadp->nacl_abi_d_name, ldp->d_name);
     229                 :   /* NB: some padding bytes may not get overwritten */
     230                 : 
     231              71 :   d->cur_byte += ldp->d_reclen;
     232                 : 
     233              71 :   NaClLog(4, "NaClCopyDirent: returning %"NACL_PRIuS"\n", adjusted_size);
     234              71 :   return (ssize_t) adjusted_size;
     235                 : }
     236                 : 
     237              21 : static ssize_t NaClStreamDirents(struct NaClHostDir *d,
     238                 :                                  void               *buf,
     239                 :                                  size_t             len) {
     240                 :   ssize_t retval;
     241              21 :   size_t  xferred = 0;
     242                 :   ssize_t entry_size;
     243                 : 
     244              21 :   NaClXMutexLock(&d->mu);
     245             116 :   while (len > 0) {
     246              92 :     NaClLog(4, "NaClStreamDirents: loop, xferred = %"NACL_PRIuS"\n", xferred);
     247              92 :     entry_size = NaClCopyDirent(d, buf, len);
     248              92 :     if (0 == entry_size) {
     249               9 :       CHECK(d->cur_byte == d->nbytes);
     250               9 :       retval = getdents(d->fd,
     251               9 :                         (struct dirent *) d->dirent_buf,
     252                 :                         sizeof d->dirent_buf);
     253               9 :       if (-1 == retval) {
     254               0 :         if (xferred > 0) {
     255                 :           /* next time through, we'll pick up the error again */
     256               0 :           goto cleanup;
     257                 :         } else {
     258               0 :           xferred = -NaClXlateErrno(errno);
     259               0 :           goto cleanup;
     260                 :         }
     261               9 :       } else if (0 == retval) {
     262               6 :         goto cleanup;
     263                 :       }
     264               3 :       d->cur_byte = 0;
     265               3 :       d->nbytes = retval;
     266              83 :     } else if (entry_size < 0) {
     267                 :       /*
     268                 :        * The only error return from NaClCopyDirent is NACL_ABI_EINVAL
     269                 :        * due to destinaton buffer too small for the current entry.  If
     270                 :        * we had copied some entries before, we were successful;
     271                 :        * otherwise report that the buffer is too small for the next
     272                 :        * directory entry.
     273                 :        */
     274              12 :       if (xferred > 0) {
     275               5 :         goto cleanup;
     276                 :       } else {
     277               7 :         xferred = entry_size;
     278               7 :         goto cleanup;
     279                 :       }
     280                 :     }
     281                 :     /* entry_size > 0, maybe copy another */
     282              74 :     buf = (void *) ((char *) buf + entry_size);
     283              74 :     CHECK(len >= (size_t) entry_size);
     284              74 :     len -= entry_size;
     285              74 :     xferred += entry_size;
     286                 :   }
     287                 :   /* perfect fit! */
     288                 :  cleanup:
     289              21 :   NaClXMutexUnlock(&d->mu);
     290              21 :   return xferred;
     291                 : }
     292                 : 
     293              21 : ssize_t NaClHostDirGetdents(struct NaClHostDir  *d,
     294                 :                             void                *buf,
     295                 :                             size_t              len) {
     296                 :   int retval;
     297                 : 
     298              21 :   if (NULL == d) {
     299               0 :     NaClLog(LOG_FATAL, "NaClHostDirGetdents: 'this' is NULL\n");
     300                 :   }
     301              21 :   NaClLog(3, "NaClHostDirGetdents(0x%08"NACL_PRIxPTR", %"NACL_PRIuS"):\n",
     302                 :           (uintptr_t) buf, len);
     303                 : 
     304              21 :   if (0 != ((__alignof__(struct nacl_abi_dirent) - 1) & (uintptr_t) buf)) {
     305               0 :     retval = -NACL_ABI_EINVAL;
     306               0 :     goto cleanup;
     307                 :   }
     308                 : 
     309              21 :   retval = NaClStreamDirents(d, buf, len);
     310                 :  cleanup:
     311              21 :   NaClLog(3, "NaClHostDirGetdents: returned %d\n", retval);
     312              21 :   return retval;
     313                 : }
     314                 : 
     315               1 : int NaClHostDirRewind(struct NaClHostDir *d) {
     316               1 :   if (NULL == d) {
     317               0 :     NaClLog(LOG_FATAL, "NaClHostDirRewind: 'this' is NULL\n");
     318                 :   }
     319               1 :   return -NaClXlateErrno(lseek64(d->fd, 0, SEEK_SET));
     320                 : }
     321                 : 
     322               3 : int NaClHostDirClose(struct NaClHostDir *d) {
     323                 :   int retval;
     324                 : 
     325               3 :   if (NULL == d) {
     326               0 :     NaClLog(LOG_FATAL, "NaClHostDirClose: 'this' is NULL\n");
     327                 :   }
     328               3 :   NaClLog(3, "NaClHostDirClose(%d)\n", d->fd);
     329               3 :   retval = close(d->fd);
     330               3 :   d->fd = -1;
     331               3 :   NaClMutexDtor(&d->mu);
     332               3 :   return (-1 == retval) ? -NaClXlateErrno(errno) : retval;
     333                 : }

Generated by: LCOV version 1.7