Merge pull request #82 from plieven/readahead
authorRonnie Sahlberg <ronniesahlberg@gmail.com>
Sun, 29 Jun 2014 21:41:28 +0000 (14:41 -0700)
committerRonnie Sahlberg <ronniesahlberg@gmail.com>
Sun, 29 Jun 2014 21:41:28 +0000 (14:41 -0700)
add readahead support

1  2 
include/nfsc/libnfs.h
lib/init.c
lib/libnfs.c

diff --combined include/nfsc/libnfs.h
index b6cb806dac48b7b1a8967533f5d8fd2644158888,f2d8cb0537b3ae6658f258a4b2f397729660a44e..dfa2185a06b57854be7f3aea6a9df5291065fbde
@@@ -36,6 -36,9 +36,9 @@@
  extern "C" {
  #endif
  
+ #define LIBNFS_FEATURE_READAHEAD
+ #define NFS_BLKSIZE 4096
  struct nfs_context;
  struct rpc_context;
  
@@@ -184,6 -187,7 +187,7 @@@ EXTERN uint64_t nfs_get_writemax(struc
  EXTERN void nfs_set_tcp_syncnt(struct nfs_context *nfs, int v);
  EXTERN void nfs_set_uid(struct nfs_context *nfs, int uid);
  EXTERN void nfs_set_gid(struct nfs_context *nfs, int gid);
+ EXTERN void nfs_set_readahead(struct nfs_context *nfs, uint32_t v);
  
  /*
   * MOUNT THE EXPORT
@@@ -781,7 -785,6 +785,7 @@@ struct nfsdirent  
         struct timeval ctime;
         uint32_t uid;
         uint32_t gid;
 +       uint32_t nlink;
  };
  /*
   * nfs_readdir() never blocks, so no special sync/async versions are available
diff --combined lib/init.c
index db588dfb626d206bd46e7e56e3be3fcfc596192b,99ec1596e5927be44eaf358b17abf0d2176644fc..d41c4f8186acdc02261a473ae53c9b8c0288918d
@@@ -1,10 -1,7 +1,10 @@@
  /*
     Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
  
 -
 +   This program is free software; you can redistribute it and/or modify
 +   it under the terms of the GNU Lesser General Public License as published by
 +   the Free Software Foundation; either version 2.1 of the License, or
 +   (at your option) any later version.
  
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
@@@ -94,6 -91,12 +94,12 @@@ struct rpc_context *rpc_init_context(vo
        return rpc;
  }
  
+ void rpc_set_readahead(struct rpc_context *rpc, uint32_t v)
+ {
+       assert(rpc->magic == RPC_CONTEXT_MAGIC);
+       rpc->readahead = v;
+ }
  
  struct rpc_context *rpc_init_udp_context(void)
  {
@@@ -238,7 -241,7 +244,7 @@@ void rpc_destroy_context(struct rpc_con
  
        while((pdu = rpc->outqueue.head) != NULL) {
                pdu->cb(rpc, RPC_STATUS_CANCEL, NULL, pdu->private_data);
 -              rpc->outqueue.head = pdu->next;
 +              LIBNFS_LIST_REMOVE(&rpc->outqueue.head, pdu);
                rpc_free_pdu(rpc, pdu);
        }
  
  
                while((pdu = q->head) != NULL) {
                        pdu->cb(rpc, RPC_STATUS_CANCEL, NULL, pdu->private_data);
 -                      rpc->outqueue.head = pdu->next;
 +                      LIBNFS_LIST_REMOVE(&q->head, pdu);
                        rpc_free_pdu(rpc, pdu);
                }
        }
diff --combined lib/libnfs.c
index 30719ab562809cb59172cf5cd3b6b2434d0bffa3,d54a542282e2e3ad2da11743e8e8d990e72d5e2d..41b8a2b35f01ea0b9a15cd0a575b7009476fa09d
@@@ -69,7 -69,6 +69,7 @@@
  #include <sys/stat.h>
  #include <fcntl.h>
  #include "libnfs-zdr.h"
 +#include "slist.h"
  #include "libnfs.h"
  #include "libnfs-raw.h"
  #include "libnfs-raw-mount.h"
  #include "libnfs-raw-portmap.h"
  #include "libnfs-private.h"
  
 +#define MAX_DIR_CACHE 1024
 +
  struct nfsdir {
 +       struct nfs_fh3 fh;
 +       fattr3 attr;
 +       struct nfsdir *next;
 +
         struct nfsdirent *entries;
         struct nfsdirent *current;
  };
  
+ struct nfs_readahead {
+        uint64_t fh_offset;
+        uint64_t last_offset;
+        uint64_t buf_offset;
+        uint64_t buf_count;
+        time_t buf_ts;
+        void *buf;
+        uint32_t cur_ra;
+ };
  struct nfsfh {
         struct nfs_fh3 fh;
         int is_sync;
         int is_append;
         uint64_t offset;
+        struct nfs_readahead ra;
  };
  
  struct nfs_context {
         uint64_t readmax;
         uint64_t writemax;
         char *cwd;
 +       struct nfsdir *dircache;
  };
  
  void nfs_free_nfsdir(struct nfsdir *nfsdir)
                free(nfsdir->entries);
                nfsdir->entries = dirent;
        }
 +      free(nfsdir->fh.data.data_val);
        free(nfsdir);
  }
  
 +static void nfs_dircache_add(struct nfs_context *nfs, struct nfsdir *nfsdir)
 +{
 +      int i;
 +      LIBNFS_LIST_ADD(&nfs->dircache, nfsdir);
 +
 +      for (nfsdir = nfs->dircache, i = 0; nfsdir; nfsdir = nfsdir->next, i++) {
 +              if (i > MAX_DIR_CACHE) {
 +                      LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
 +                      nfs_free_nfsdir(nfsdir);
 +                      break;
 +              }
 +      }
 +}
 +
 +static struct nfsdir *nfs_dircache_find(struct nfs_context *nfs, struct nfs_fh3 *fh)
 +{
 +      struct nfsdir *nfsdir;
 +
 +      for (nfsdir = nfs->dircache; nfsdir; nfsdir = nfsdir->next) {
 +              if (nfsdir->fh.data.data_len == fh->data.data_len &&
 +                  !memcmp(nfsdir->fh.data.data_val, fh->data.data_val, fh->data.data_len)) {
 +                      LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
 +                      return nfsdir;
 +              }
 +      }
 +
 +      return NULL;
 +}
 +
  struct nfs_cb_data;
 -typedef int (*continue_func)(struct nfs_context *nfs, struct nfs_cb_data *data);
 +typedef int (*continue_func)(struct nfs_context *nfs, fattr3 *attr,
 +                           struct nfs_cb_data *data);
  
  struct nfs_cb_data {
         struct nfs_context *nfs;
         int cancel;
         int oom;
         int num_calls;
-        uint64_t start_offset, max_offset, count;
+        uint64_t offset, count, max_offset, org_offset, org_count;
         char *buffer;
-        size_t request_size;
         char *usrbuf;
  };
  
@@@ -186,7 -157,7 +196,7 @@@ struct nfs_mcb_data 
         int update_pos;
  };
  
 -static int nfs_lookup_path_async_internal(struct nfs_context *nfs, struct nfs_cb_data *data, struct nfs_fh3 *fh);
 +static int nfs_lookup_path_async_internal(struct nfs_context *nfs, fattr3 *attr, struct nfs_cb_data *data, struct nfs_fh3 *fh);
  
  void nfs_set_auth(struct nfs_context *nfs, struct AUTH *auth)
  {
@@@ -220,12 -191,14 +230,14 @@@ char *nfs_get_error(struct nfs_context 
  
  static int nfs_set_context_args(struct nfs_context *nfs, char *arg, char *val)
  {
-       if (!strncmp(arg, "tcp-syncnt", 10)) {
+       if (!strcmp(arg, "tcp-syncnt")) {
                rpc_set_tcp_syncnt(nfs_get_rpc_context(nfs), atoi(val));
-       } else if (!strncmp(arg, "uid", 3)) {
+       } else if (!strcmp(arg, "uid")) {
                rpc_set_uid(nfs_get_rpc_context(nfs), atoi(val));
-       } else if (!strncmp(arg, "gid", 3)) {
+       } else if (!strcmp(arg, "gid")) {
                rpc_set_gid(nfs_get_rpc_context(nfs), atoi(val));
+       } else if (!strcmp(arg, "readahaed")) {
+               rpc_set_readahead(nfs_get_rpc_context(nfs), atoi(val));
        }
        return 0;
  }
@@@ -417,12 -390,6 +429,12 @@@ void nfs_destroy_context(struct nfs_con
                nfs->rootfh.data.data_val = NULL;
        }
  
 +      while (nfs->dircache) {
 +              struct nfsdir *nfsdir = nfs->dircache;
 +              LIBNFS_LIST_REMOVE(&nfs->dircache, nfsdir);
 +              nfs_free_nfsdir(nfsdir);
 +      }
 +
        free(nfs);
  }
  
@@@ -685,6 -652,9 +697,6 @@@ static void free_nfs_cb_data(struct nfs
  }
  
  
 -
 -
 -
  static void nfs_mount_10_cb(struct rpc_context *rpc, int status, void *command_data, void *private_data)
  {
        struct nfs_cb_data *data = private_data;
@@@ -890,7 -860,6 +902,7 @@@ static void nfs_lookup_path_1_cb(struc
        struct nfs_cb_data *data = private_data;
        struct nfs_context *nfs = data->nfs;
        LOOKUP3res *res;
 +      fattr3 *attr;
  
        assert(rpc->magic == RPC_CONTEXT_MAGIC);
  
                return;
        }
  
 -      if (nfs_lookup_path_async_internal(nfs, data, &res->LOOKUP3res_u.resok.object) != 0) {
 -              rpc_set_error(nfs->rpc, "Failed to create lookup pdu");
 -              data->cb(-ENOMEM, nfs, rpc_get_error(nfs->rpc), data->private_data);
 -              free_nfs_cb_data(data);
 -              return;
 -      }
 +      attr = res->LOOKUP3res_u.resok.obj_attributes.attributes_follow ?
 +        &res->LOOKUP3res_u.resok.obj_attributes.post_op_attr_u.attributes :
 +        NULL;
 +
 +      /* This function will always invoke the callback and cleanup
 +       * for failures. So no need to check the return value.
 +       */
 +      nfs_lookup_path_async_internal(nfs, attr, data, &res->LOOKUP3res_u.resok.object);
  }
  
 -static int nfs_lookup_path_async_internal(struct nfs_context *nfs, struct nfs_cb_data *data, struct nfs_fh3 *fh)
 +static int nfs_lookup_path_async_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data, struct nfs_fh3 *fh)
  {
        char *path, *slash;
        LOOKUP3args args;
                if (slash != NULL) {
                        *slash = '/';
                }
 -              data->continue_cb(nfs, data);
 +              data->continue_cb(nfs, attr, data);
                return 0;
        }
  
 -
        memset(&args, 0, sizeof(LOOKUP3args));
        args.what.dir = *fh;
        args.what.name = path;
@@@ -1082,45 -1050,9 +1094,45 @@@ static int nfs_normalize_path(struct nf
        return 0;
  }
  
 +static void nfs_lookup_path_getattr_cb(struct rpc_context *rpc, int status, void *command_data, void *private_data)
 +{
 +      struct nfs_cb_data *data = private_data;
 +      struct nfs_context *nfs = data->nfs;
 +      GETATTR3res *res;
 +      fattr3 *attr;
 +
 +      assert(rpc->magic == RPC_CONTEXT_MAGIC);
 +
 +      if (status == RPC_STATUS_ERROR) {
 +              data->cb(-EFAULT, nfs, command_data, data->private_data);
 +              free_nfs_cb_data(data);
 +              return;
 +      }
 +      if (status == RPC_STATUS_CANCEL) {
 +              data->cb(-EINTR, nfs, "Command was cancelled", data->private_data);
 +              free_nfs_cb_data(data);
 +              return;
 +      }
 +
 +      res = command_data;
 +      if (res->status != NFS3_OK) {
 +              rpc_set_error(nfs->rpc, "NFS: GETATTR of %s failed with %s(%d)", data->saved_path, nfsstat3_to_str(res->status), nfsstat3_to_errno(res->status));
 +              data->cb(nfsstat3_to_errno(res->status), nfs, rpc_get_error(nfs->rpc), data->private_data);
 +              free_nfs_cb_data(data);
 +              return;
 +      }
 +
 +      attr = &res->GETATTR3res_u.resok.obj_attributes;
 +      /* This function will always invoke the callback and cleanup
 +       * for failures. So no need to check the return value.
 +       */
 +      nfs_lookup_path_async_internal(nfs, attr, data, &nfs->rootfh);
 +}
 +
  static int nfs_lookuppath_async(struct nfs_context *nfs, const char *path, nfs_cb cb, void *private_data, continue_func continue_cb, void *continue_data, void (*free_continue_data)(void *), int continue_int)
  {
        struct nfs_cb_data *data;
 +      struct GETATTR3args args;
  
        if (path[0] == '\0') {
                path = ".";
        }
  
        data->path = data->saved_path;
 -
 -      if (nfs_lookup_path_async_internal(nfs, data, &nfs->rootfh) != 0) {
 -              /* return 0 here since the callback will be invoked if there is a failure */
 +      if (data->path[0]) {
 +              /* This function will always invoke the callback and cleanup
 +               * for failures. So no need to check the return value.
 +               */
 +              nfs_lookup_path_async_internal(nfs, NULL, data, &nfs->rootfh);
                return 0;
        }
 +
 +      /* We have a request for "", so just perform a GETATTR3 so we can
 +       * return the attributes to the caller.
 +       */
 +      memset(&args, 0, sizeof(GETATTR3args));
 +      args.object = nfs->rootfh;
 +      if (rpc_nfs3_getattr_async(nfs->rpc, nfs_lookup_path_getattr_cb, &args, data) != 0) {
 +              free_nfs_cb_data(data);
 +              return -1;
 +      }
        return 0;
  }
  
  
 -
 -
 -
  /*
   * Async stat()
   */
@@@ -1237,8 -1160,8 +1249,8 @@@ static void nfs_stat_1_cb(struct rpc_co
          st.st_rdev    = 0;
          st.st_size    = res->GETATTR3res_u.resok.obj_attributes.size;
  #ifndef WIN32
-         st.st_blksize = 4096;
-         st.st_blocks  = res->GETATTR3res_u.resok.obj_attributes.size / 4096;
+         st.st_blksize = NFS_BLKSIZE;
+         st.st_blocks  = res->GETATTR3res_u.resok.obj_attributes.size / NFS_BLKSIZE;
  #endif//WIN32
          st.st_atime   = res->GETATTR3res_u.resok.obj_attributes.atime.seconds;
          st.st_mtime   = res->GETATTR3res_u.resok.obj_attributes.mtime.seconds;
        free_nfs_cb_data(data);
  }
  
 -static int nfs_stat_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_stat_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct GETATTR3args args;
  
@@@ -1328,7 -1251,7 +1340,7 @@@ static void nfs_stat64_1_cb(struct rpc_
        free_nfs_cb_data(data);
  }
  
 -static int nfs_stat64_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_stat64_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct GETATTR3args args;
  
@@@ -1508,7 -1431,7 +1520,7 @@@ static void nfs_open_cb(struct rpc_cont
        free_nfs_cb_data(data);
  }
  
 -static int nfs_open_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_open_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        int nfsmode = 0;
        ACCESS3args args;
@@@ -1552,7 -1475,7 +1564,7 @@@ int nfs_open_async(struct nfs_context *
  /*
   * Async chdir()
   */
 -static int nfs_chdir_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_chdir_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        /* steal saved_path */
        free(nfs->cwd);
@@@ -1621,9 -1544,9 +1633,9 @@@ static void nfs_pread_mcb(struct rpc_co
                        /* if we have more than one call or we have received a short read we need a reassembly buffer */
                        if (data->num_calls || (count < mdata->count && !res->READ3res_u.resok.eof)) {
                                if (data->buffer == NULL) {
-                                       data->buffer =  malloc(data->request_size);
+                                       data->buffer =  malloc(data->count);
                                        if (data->buffer == NULL) {
-                                               rpc_set_error(nfs->rpc, "Out-Of-Memory: Failed to allocate reassembly buffer for %d bytes", (int)data->request_size);
+                                               rpc_set_error(nfs->rpc, "Out-Of-Memory: Failed to allocate reassembly buffer for %d bytes", (int)data->count);
                                                data->oom = 1;
                                        }
                                }
                                if (count <= mdata->count) {
                                        /* copy data into reassembly buffer if we have one */
                                        if (data->buffer != NULL) {
-                                               memcpy(&data->buffer[mdata->offset - data->start_offset], res->READ3res_u.resok.data.data_val, count);
+                                               memcpy(&data->buffer[mdata->offset - data->offset], res->READ3res_u.resok.data.data_val, count);
                                        }
                                        if (data->max_offset < mdata->offset + count) {
                                                data->max_offset = mdata->offset + count;
        }
  
        if (data->buffer) {
-               data->cb(data->max_offset - data->start_offset, nfs, data->buffer, data->private_data);
+               if (data->max_offset > data->org_offset + data->org_count) {
+                       data->max_offset = data->org_offset + data->org_count;
+               }
+               data->cb(data->max_offset - data->org_offset, nfs, data->buffer + (data->org_offset - data->offset), data->private_data);
        } else {
                data->cb(res->READ3res_u.resok.count, nfs, res->READ3res_u.resok.data.data_val, data->private_data);
        }
  
+       data->nfsfh->ra.fh_offset = data->max_offset;
+       if (data->nfsfh->ra.cur_ra) {
+               free(data->nfsfh->ra.buf);
+               data->nfsfh->ra.buf = data->buffer;
+               data->nfsfh->ra.buf_offset = data->offset;
+               data->nfsfh->ra.buf_count = data->count;
+               data->nfsfh->ra.buf_ts = time(NULL);
+               data->buffer = NULL;
+       }
        free_nfs_cb_data(data);
  }
  
+ static void nfs_ra_invalidate(struct nfsfh *nfsfh) {
+       free(nfsfh->ra.buf);
+       nfsfh->ra.buf = NULL;
+       nfsfh->ra.buf_offset = 0;
+       nfsfh->ra.buf_count = 0;
+       nfsfh->ra.buf_ts = time(NULL);
+       nfsfh->ra.cur_ra = NFS_BLKSIZE;
+ }
  static int nfs_pread_async_internal(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t offset, uint64_t count, nfs_cb cb, void *private_data, int update_pos)
  {
        struct nfs_cb_data *data;
        data->cb           = cb;
        data->private_data = private_data;
        data->nfsfh        = nfsfh;
-       data->request_size = count;
+       data->org_offset   = offset;
+       data->org_count    = count;
  
        assert(data->num_calls == 0);
  
+       if (nfs->rpc->readahead && time(NULL) - nfsfh->ra.buf_ts > NFS_RA_TIMEOUT) {
+               /* readahead cache timeout */
+               nfs_ra_invalidate(nfsfh);
+       }
+       if (nfs->rpc->readahead) {
+               if (offset >= nfsfh->ra.last_offset &&
+                       offset - NFS_BLKSIZE <= nfsfh->ra.fh_offset + nfsfh->ra.cur_ra) {
+                       if (nfs->rpc->readahead > nfsfh->ra.cur_ra) {
+                               nfsfh->ra.cur_ra <<= 1;
+                       }
+               } else {
+                       nfsfh->ra.cur_ra = NFS_BLKSIZE;
+               }
+               nfsfh->ra.last_offset = offset;
+               if (nfsfh->ra.buf_offset <= offset &&
+                       nfsfh->ra.buf_offset + nfsfh->ra.buf_count >= offset + count) {
+                       /* serve request completely from cache */
+                       data->buffer = malloc(count);
+                       if (data->buffer == NULL) {
+                               free_nfs_cb_data(data);
+                               return -ENOMEM;
+                       }
+                       memcpy(data->buffer, nfsfh->ra.buf + (offset - nfsfh->ra.buf_offset), count);
+                       data->cb(count, nfs, data->buffer, data->private_data);
+                       nfsfh->ra.fh_offset = offset + count;
+                       free_nfs_cb_data(data);
+                       return 0;
+               }
+               /* align start offset to blocksize */
+               count += offset & (NFS_BLKSIZE - 1);
+               offset &= ~(NFS_BLKSIZE - 1);
+               /* align end offset to blocksize and add readahead */
+               count += nfsfh->ra.cur_ra - 1;
+               count &= ~(NFS_BLKSIZE - 1);
+               data->buffer = malloc(count);
+               if (data->buffer == NULL) {
+                       free_nfs_cb_data(data);
+                       return -ENOMEM;
+               }
+               data->offset = offset;
+               data->count = count;
+               if (nfsfh->ra.buf_count && nfsfh->ra.buf_offset <= offset &&
+                       nfsfh->ra.buf_offset + nfsfh->ra.buf_count >= offset) {
+                       /* serve request partially from cache */
+                       size_t overlap = (nfsfh->ra.buf_offset + nfsfh->ra.buf_count) - offset;
+                       if (overlap > count) count = overlap;
+                       memcpy(data->buffer, nfsfh->ra.buf + (offset - nfsfh->ra.buf_offset), overlap);
+                       offset += overlap;
+                       count -= overlap;
+               }
+       } else {
+               data->offset = offset;
+               data->count = count;
+       }
+       data->max_offset = offset;
        /* chop requests into chunks of at most READMAX bytes if necessary.
         * we send all reads in parallel so that performance is still good.
         */
-       data->max_offset = offset;
-       data->start_offset = offset;
        do {
                uint64_t readcount = count;
                struct nfs_mcb_data *mdata;
@@@ -1838,7 -1844,7 +1933,7 @@@ static void nfs_pwrite_mcb(struct rpc_c
                                        mdata->count -= count;
  
                                        nfs_fill_WRITE3args(&args, data->nfsfh, mdata->offset, mdata->count,
-                                                                               &data->usrbuf[mdata->offset - data->start_offset]);
+                                                                               &data->usrbuf[mdata->offset - data->offset]);
                                        if (rpc_nfs3_write_async(nfs->rpc, nfs_pwrite_mcb, &args, mdata) == 0) {
                                                data->num_calls++;
                                                return;
                return;
        }
  
-       data->cb(data->max_offset - data->start_offset, nfs, NULL, data->private_data);
+       data->cb(data->max_offset - data->offset, nfs, NULL, data->private_data);
  
        free_nfs_cb_data(data);
  }
@@@ -1907,7 -1913,7 +2002,7 @@@ static int nfs_pwrite_async_internal(st
         * we send all writes in parallel so that performance is still good.
         */
        data->max_offset = offset;
-       data->start_offset = offset;
+       data->offset = offset;
  
        do {
                uint64_t writecount = count;
                mdata->count  = writecount;
                mdata->update_pos = update_pos;
  
-               nfs_fill_WRITE3args(&args, nfsfh, offset, writecount, &buf[offset - data->start_offset]);
+               nfs_fill_WRITE3args(&args, nfsfh, offset, writecount, &buf[offset - data->offset]);
  
                if (rpc_nfs3_write_async(nfs->rpc, nfs_pwrite_mcb, &args, mdata) != 0) {
                        rpc_set_error(nfs->rpc, "RPC error: Failed to send WRITE call for %s", data->path);
@@@ -2000,6 -2006,7 +2095,7 @@@ static void nfs_write_append_cb(struct 
  
  int nfs_write_async(struct nfs_context *nfs, struct nfsfh *nfsfh, uint64_t count, char *buf, nfs_cb cb, void *private_data)
  {
+       nfs_ra_invalidate(nfsfh);
        if (nfsfh->is_append) {
                struct GETATTR3args args;
                struct nfs_cb_data *data;
@@@ -2043,6 -2050,7 +2139,7 @@@ int nfs_close_async(struct nfs_context 
                free(nfsfh->fh.data.data_val);
                nfsfh->fh.data.data_val = NULL;
        }
+       free(nfsfh->ra.buf);
        free(nfsfh);
  
        cb(0, nfs, NULL, private_data);
@@@ -2216,7 -2224,7 +2313,7 @@@ int nfs_ftruncate_async(struct nfs_cont
  /*
   * Async truncate()
   */
 -static int nfs_truncate_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_truncate_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        uint64_t offset = data->continue_int;
        struct nfsfh nfsfh;
@@@ -2287,7 -2295,7 +2384,7 @@@ static void nfs_mkdir_cb(struct rpc_con
        free_nfs_cb_data(data);
  }
  
 -static int nfs_mkdir_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_mkdir_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        char *str = data->continue_data;
        MKDIR3args args;
@@@ -2378,7 -2386,7 +2475,7 @@@ static void nfs_rmdir_cb(struct rpc_con
        free_nfs_cb_data(data);
  }
  
 -static int nfs_rmdir_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_rmdir_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        char *str = data->continue_data;
        RMDIR3args args;
@@@ -2523,7 -2531,7 +2620,7 @@@ static void nfs_creat_1_cb(struct rpc_c
        return;
  }
  
 -static int nfs_creat_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_creat_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        char *str = data->continue_data;
        CREATE3args args;
@@@ -2614,7 -2622,7 +2711,7 @@@ static void nfs_unlink_cb(struct rpc_co
        free_nfs_cb_data(data);
  }
  
 -static int nfs_unlink_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_unlink_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        char *str = data->continue_data;
        struct REMOVE3args args;
@@@ -2713,7 -2721,7 +2810,7 @@@ static void nfs_mknod_cb(struct rpc_con
        free_nfs_cb_data(data);
  }
  
 -static int nfs_mknod_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_mknod_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct mknod_cb_data *cb_data = data->continue_data;
        char *str = cb_data->path;
@@@ -2861,7 -2869,6 +2958,7 @@@ static void nfs_opendir3_cb(struct rpc_
                        nfsdirent->ctime.tv_usec = attributes->ctime.nseconds/1000;
                        nfsdirent->uid = attributes->uid;
                        nfsdirent->gid = attributes->gid;
 +                      nfsdirent->nlink = attributes->nlink;
                }
        }
  
@@@ -2968,9 -2975,6 +3065,9 @@@ static void nfs_opendir2_cb(struct rpc_
                return;
        }
  
 +      if (res->READDIR3res_u.resok.dir_attributes.attributes_follow)
 +              nfsdir->attr = res->READDIR3res_u.resok.dir_attributes.post_op_attr_u.attributes;
 +
        /* steal the dirhandle */
        nfsdir->current = nfsdir->entries;
  
@@@ -3102,7 -3106,6 +3199,7 @@@ static void nfs_opendir_cb(struct rpc_c
                        nfsdirent->ctime.tv_usec = entry->name_attributes.post_op_attr_u.attributes.ctime.nseconds/1000;
                        nfsdirent->uid = entry->name_attributes.post_op_attr_u.attributes.uid;
                        nfsdirent->gid = entry->name_attributes.post_op_attr_u.attributes.gid;
 +                      nfsdirent->nlink = entry->name_attributes.post_op_attr_u.attributes.nlink;
                }
  
                nfsdirent->next  = nfsdir->entries;
                return;
        }
  
 +      if (res->READDIRPLUS3res_u.resok.dir_attributes.attributes_follow)
 +              nfsdir->attr = res->READDIRPLUS3res_u.resok.dir_attributes.post_op_attr_u.attributes;
 +
        /* steal the dirhandle */
        data->continue_data = NULL;
        nfsdir->current = nfsdir->entries;
        free_nfs_cb_data(data);
  }
  
 -static int nfs_opendir_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_opendir_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        READDIRPLUS3args args;
 +      struct nfsdir *nfsdir = data->continue_data;;
 +      struct nfsdir *cached;
 +
 +      cached = nfs_dircache_find(nfs, &data->fh);
 +      if (cached) {
 +              if (attr && attr->mtime.seconds == cached->attr.mtime.seconds) {
 +                      cached->current = cached->entries;
 +                      data->cb(0, nfs, cached, data->private_data);
 +                      free_nfs_cb_data(data);
 +                      return 0;
 +              } else {
 +                      /* cache must be stale */
 +                      nfs_free_nfsdir(cached);
 +              }
 +      }
 +
 +      nfsdir->fh.data.data_len  = data->fh.data.data_len;
 +      nfsdir->fh.data.data_val = malloc(nfsdir->fh.data.data_len);
 +      if (nfsdir->fh.data.data_val == NULL) {
 +              rpc_set_error(nfs->rpc, "OOM when allocating fh for nfsdir");
 +              data->cb(-ENOMEM, nfs, rpc_get_error(nfs->rpc), data->private_data);
 +              free_nfs_cb_data(data);
 +              return -1;
 +      }
 +      memcpy(nfsdir->fh.data.data_val, data->fh.data.data_val, data->fh.data.data_len);
  
        args.dir = data->fh;
        args.cookie = 0;
@@@ -3220,9 -3195,9 +3317,9 @@@ struct nfsdirent *nfs_readdir(struct nf
  /*
   * closedir()
   */
 -void nfs_closedir(struct nfs_context *nfs _U_, struct nfsdir *nfsdir)
 +void nfs_closedir(struct nfs_context *nfs, struct nfsdir *nfsdir)
  {
 -      nfs_free_nfsdir(nfsdir);
 +      nfs_dircache_add(nfs, nfsdir);
  }
  
  
@@@ -3371,11 -3346,11 +3468,11 @@@ static void nfs_statvfs_1_cb(struct rpc
                return;
        }
  
-       svfs.f_bsize   = 4096;
-       svfs.f_frsize  = 4096;
-       svfs.f_blocks  = res->FSSTAT3res_u.resok.tbytes/4096;
-       svfs.f_bfree   = res->FSSTAT3res_u.resok.fbytes/4096;
-       svfs.f_bavail  = res->FSSTAT3res_u.resok.abytes/4096;
+       svfs.f_bsize   = NFS_BLKSIZE;
+       svfs.f_frsize  = NFS_BLKSIZE;
+       svfs.f_blocks  = res->FSSTAT3res_u.resok.tbytes/NFS_BLKSIZE;
+       svfs.f_bfree   = res->FSSTAT3res_u.resok.fbytes/NFS_BLKSIZE;
+       svfs.f_bavail  = res->FSSTAT3res_u.resok.abytes/NFS_BLKSIZE;
        svfs.f_files   = res->FSSTAT3res_u.resok.tfiles;
        svfs.f_ffree   = res->FSSTAT3res_u.resok.ffiles;
  #if !defined(ANDROID)
        free_nfs_cb_data(data);
  }
  
 -static int nfs_statvfs_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_statvfs_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        FSSTAT3args args;
  
@@@ -3451,7 -3426,7 +3548,7 @@@ static void nfs_readlink_1_cb(struct rp
        free_nfs_cb_data(data);
  }
  
 -static int nfs_readlink_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_readlink_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        READLINK3args args;
  
@@@ -3513,7 -3488,7 +3610,7 @@@ static void nfs_chmod_cb(struct rpc_con
        free_nfs_cb_data(data);
  }
  
 -static int nfs_chmod_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_chmod_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        SETATTR3args args;
  
@@@ -3568,7 -3543,7 +3665,7 @@@ int nfs_fchmod_async(struct nfs_contex
        }
        memcpy(data->fh.data.data_val, nfsfh->fh.data.data_val, data->fh.data.data_len);
  
 -      if (nfs_chmod_continue_internal(nfs, data) != 0) {
 +      if (nfs_chmod_continue_internal(nfs, NULL, data) != 0) {
                return -1;
        }
  
@@@ -3616,7 -3591,7 +3713,7 @@@ struct nfs_chown_data 
         gid_t gid;
  };
  
 -static int nfs_chown_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_chown_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        SETATTR3args args;
        struct nfs_chown_data *chown_data = data->continue_data;
@@@ -3702,7 -3677,7 +3799,7 @@@ int nfs_fchown_async(struct nfs_contex
        }
        memcpy(data->fh.data.data_val, nfsfh->fh.data.data_val, data->fh.data.data_len);
  
 -      if (nfs_chown_continue_internal(nfs, data) != 0) {
 +      if (nfs_chown_continue_internal(nfs, NULL, data) != 0) {
                return -1;
        }
  
@@@ -3747,7 -3722,7 +3844,7 @@@ static void nfs_utimes_cb(struct rpc_co
        free_nfs_cb_data(data);
  }
  
 -static int nfs_utimes_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_utimes_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        SETATTR3args args;
        struct timeval *utimes_data = data->continue_data;
@@@ -3885,7 -3860,7 +3982,7 @@@ static void nfs_access_cb(struct rpc_co
        free_nfs_cb_data(data);
  }
  
 -static int nfs_access_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_access_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        int nfsmode = 0;
        ACCESS3args args;
@@@ -3982,7 -3957,7 +4079,7 @@@ static void nfs_symlink_cb(struct rpc_c
        free_nfs_cb_data(data);
  }
  
 -static int nfs_symlink_continue_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_symlink_continue_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct nfs_symlink_data *symlink_data = data->continue_data;
        SYMLINK3args args;
@@@ -4118,7 -4093,7 +4215,7 @@@ static void nfs_rename_cb(struct rpc_co
        free_nfs_cb_data(data);
  }
  
 -static int nfs_rename_continue_2_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_rename_continue_2_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct nfs_rename_data *rename_data = data->continue_data;
        RENAME3args args;
  }
  
  
 -static int nfs_rename_continue_1_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_rename_continue_1_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct nfs_rename_data *rename_data = data->continue_data;
        char* newpath = strdup(rename_data->newpath);
@@@ -4288,7 -4263,7 +4385,7 @@@ static void nfs_link_cb(struct rpc_cont
        free_nfs_cb_data(data);
  }
  
 -static int nfs_link_continue_2_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_link_continue_2_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct nfs_link_data *link_data = data->continue_data;
        LINK3args args;
  }
  
  
 -static int nfs_link_continue_1_internal(struct nfs_context *nfs, struct nfs_cb_data *data)
 +static int nfs_link_continue_1_internal(struct nfs_context *nfs, fattr3 *attr _U_, struct nfs_cb_data *data)
  {
        struct nfs_link_data *link_data = data->continue_data;
  
@@@ -4413,6 -4388,10 +4510,10 @@@ void nfs_set_gid(struct nfs_context *nf
        rpc_set_gid(nfs->rpc, gid);
  }
  
+ void nfs_set_readahead(struct nfs_context *nfs, uint32_t v) {
+       rpc_set_readahead(nfs->rpc, v);
+ }
  void nfs_set_error(struct nfs_context *nfs, char *error_string, ...)
  {
          va_list ap;