|
@@ -1813,27 +1813,105 @@ out:
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
-static int nfsd_do_readdir(struct file *file, filldir_t func,
|
|
|
|
- struct readdir_cd *cdp, loff_t *offsetp)
|
|
|
|
|
|
+/*
|
|
|
|
+ * We do this buffering because we must not call back into the file
|
|
|
|
+ * system's ->lookup() method from the filldir callback. That may well
|
|
|
|
+ * deadlock a number of file systems.
|
|
|
|
+ *
|
|
|
|
+ * This is based heavily on the implementation of same in XFS.
|
|
|
|
+ */
|
|
|
|
+struct buffered_dirent {
|
|
|
|
+ u64 ino;
|
|
|
|
+ loff_t offset;
|
|
|
|
+ int namlen;
|
|
|
|
+ unsigned int d_type;
|
|
|
|
+ char name[];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct readdir_data {
|
|
|
|
+ char *dirent;
|
|
|
|
+ size_t used;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int nfsd_buffered_filldir(void *__buf, const char *name, int namlen,
|
|
|
|
+ loff_t offset, u64 ino, unsigned int d_type)
|
|
|
|
+{
|
|
|
|
+ struct readdir_data *buf = __buf;
|
|
|
|
+ struct buffered_dirent *de = (void *)(buf->dirent + buf->used);
|
|
|
|
+ unsigned int reclen;
|
|
|
|
+
|
|
|
|
+ reclen = ALIGN(sizeof(struct buffered_dirent) + namlen, sizeof(u64));
|
|
|
|
+ if (buf->used + reclen > PAGE_SIZE)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ de->namlen = namlen;
|
|
|
|
+ de->offset = offset;
|
|
|
|
+ de->ino = ino;
|
|
|
|
+ de->d_type = d_type;
|
|
|
|
+ memcpy(de->name, name, namlen);
|
|
|
|
+ buf->used += reclen;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int nfsd_buffered_readdir(struct file *file, filldir_t func,
|
|
|
|
+ struct readdir_cd *cdp, loff_t *offsetp)
|
|
{
|
|
{
|
|
|
|
+ struct readdir_data buf;
|
|
|
|
+ struct buffered_dirent *de;
|
|
int host_err;
|
|
int host_err;
|
|
|
|
+ int size;
|
|
|
|
+ loff_t offset;
|
|
|
|
|
|
- /*
|
|
|
|
- * Read the directory entries. This silly loop is necessary because
|
|
|
|
- * readdir() is not guaranteed to fill up the entire buffer, but
|
|
|
|
- * may choose to do less.
|
|
|
|
- */
|
|
|
|
- do {
|
|
|
|
- cdp->err = nfserr_eof; /* will be cleared on successful read */
|
|
|
|
- host_err = vfs_readdir(file, func, cdp);
|
|
|
|
- } while (host_err >=0 && cdp->err == nfs_ok);
|
|
|
|
|
|
+ buf.dirent = (void *)__get_free_page(GFP_KERNEL);
|
|
|
|
+ if (!buf.dirent)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ offset = *offsetp;
|
|
|
|
+ cdp->err = nfserr_eof; /* will be cleared on successful read */
|
|
|
|
|
|
- *offsetp = vfs_llseek(file, 0, 1);
|
|
|
|
|
|
+ while (1) {
|
|
|
|
+ unsigned int reclen;
|
|
|
|
+
|
|
|
|
+ buf.used = 0;
|
|
|
|
+
|
|
|
|
+ host_err = vfs_readdir(file, nfsd_buffered_filldir, &buf);
|
|
|
|
+ if (host_err)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ size = buf.used;
|
|
|
|
+
|
|
|
|
+ if (!size)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ de = (struct buffered_dirent *)buf.dirent;
|
|
|
|
+ while (size > 0) {
|
|
|
|
+ offset = de->offset;
|
|
|
|
+
|
|
|
|
+ if (func(cdp, de->name, de->namlen, de->offset,
|
|
|
|
+ de->ino, de->d_type))
|
|
|
|
+ goto done;
|
|
|
|
+
|
|
|
|
+ if (cdp->err != nfs_ok)
|
|
|
|
+ goto done;
|
|
|
|
+
|
|
|
|
+ reclen = ALIGN(sizeof(*de) + de->namlen,
|
|
|
|
+ sizeof(u64));
|
|
|
|
+ size -= reclen;
|
|
|
|
+ de = (struct buffered_dirent *)((char *)de + reclen);
|
|
|
|
+ }
|
|
|
|
+ offset = vfs_llseek(file, 0, 1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ done:
|
|
|
|
+ free_page((unsigned long)(buf.dirent));
|
|
|
|
|
|
if (host_err)
|
|
if (host_err)
|
|
return nfserrno(host_err);
|
|
return nfserrno(host_err);
|
|
- else
|
|
|
|
- return cdp->err;
|
|
|
|
|
|
+
|
|
|
|
+ *offsetp = offset;
|
|
|
|
+ return cdp->err;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1858,7 +1936,7 @@ nfsd_readdir(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t *offsetp,
|
|
goto out_close;
|
|
goto out_close;
|
|
}
|
|
}
|
|
|
|
|
|
- err = nfsd_do_readdir(file, func, cdp, offsetp);
|
|
|
|
|
|
+ err = nfsd_buffered_readdir(file, func, cdp, offsetp);
|
|
|
|
|
|
if (err == nfserr_eof || err == nfserr_toosmall)
|
|
if (err == nfserr_eof || err == nfserr_toosmall)
|
|
err = nfs_ok; /* can still be found in ->err */
|
|
err = nfs_ok; /* can still be found in ->err */
|