libfuse: implement readdirplus for high-level API

Reuse the old "readdir" callback, but add a flags argument, that has
FUSE_READDIR_PLUS in case this is a "plus" version.  Filesystems can safely
ignore this flag, but if they want they can add optimizations based on it:
i.e. only retrieve the full attributes in PLUS mode.

The filler function is also given a flags argument and the filesystem can
set FUSE_FILL_DIR_PLUS if all the attributes in "stat" are valid.
This commit is contained in:
Eric Wong
2014-03-05 14:45:44 +01:00
committed by Miklos Szeredi
parent 0096c126aa
commit 6bf2e6f07c
11 changed files with 348 additions and 78 deletions
+11
View File
@@ -1,3 +1,14 @@
2014-03-05 Miklos Szeredi <miklos@szeredi.hu>
* libfuse: implement readdirplus for high-level API. Reuse the
old "readdir" callback, but add a flags argument, that has
FUSE_READDIR_PLUS in case this is a "plus" version. Filesystems
can safely ignore this flag, but if they want they can add
optimizations based on it: i.e. only retrieve the full attributes
in PLUS mode. The filler function is also given a flags argument
and the filesystem can set FUSE_FILL_DIR_PLUS if all the
attributes in "stat" are valid. Original patch by Eric Wong
2014-02-21 Miklos Szeredi <miklos@szeredi.hu>
* libfuse: added fuse_lo-plus.c to the examples
+1 -1
View File
@@ -54,7 +54,7 @@ if test "$enable_mtab" = "no"; then
fi
AC_CHECK_FUNCS([fork setxattr fdatasync splice vmsplice utimensat pipe2])
AC_CHECK_FUNCS([posix_fallocate])
AC_CHECK_FUNCS([posix_fallocate fstatat])
AC_CHECK_MEMBERS([struct stat.st_atim])
AC_CHECK_MEMBERS([struct stat.st_atimespec])
+6 -4
View File
@@ -169,17 +169,19 @@ static int fioc_truncate(const char *path, off_t size)
}
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
(void) fi;
(void) offset;
(void) flags;
if (fioc_file_type(path) != FIOC_ROOT)
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, FIOC_NAME, NULL, 0);
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
filler(buf, FIOC_NAME, NULL, 0, 0);
return 0;
}
+4 -2
View File
@@ -87,20 +87,22 @@ static int fsel_getattr(const char *path, struct stat *stbuf)
}
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
char name[2] = { };
int i;
(void) offset;
(void) fi;
(void) flags;
if (strcmp(path, "/") != 0)
return -ENOENT;
for (i = 0; i < FSEL_FILES; i++) {
name[0] = fsel_hex_map[i];
filler(buf, name, NULL, 0);
filler(buf, name, NULL, 0, 0);
}
return 0;
+4 -2
View File
@@ -81,13 +81,15 @@ static int xmp_readlink(const char *path, char *buf, size_t size)
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
DIR *dp;
struct dirent *de;
(void) offset;
(void) fi;
(void) flags;
dp = opendir(path);
if (dp == NULL)
@@ -98,7 +100,7 @@ static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
memset(&st, 0, sizeof(st));
st.st_ino = de->d_ino;
st.st_mode = de->d_type << 12;
if (filler(buf, de->d_name, &st, 0))
if (filler(buf, de->d_name, &st, 0, 0))
break;
}
+18 -5
View File
@@ -128,7 +128,8 @@ static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
}
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
struct xmp_dirp *d = get_dirp(fi);
@@ -141,18 +142,30 @@ static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
while (1) {
struct stat st;
off_t nextoff;
enum fuse_fill_dir_flags fill_flags = 0;
if (!d->entry) {
d->entry = readdir(d->dp);
if (!d->entry)
break;
}
#ifdef HAVE_FSTATAT
if (flags & FUSE_READDIR_PLUS) {
int res;
memset(&st, 0, sizeof(st));
st.st_ino = d->entry->d_ino;
st.st_mode = d->entry->d_type << 12;
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
AT_SYMLINK_NOFOLLOW);
if (res != -1)
fill_flags |= FUSE_FILL_DIR_PLUS;
}
#endif
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
memset(&st, 0, sizeof(st));
st.st_ino = d->entry->d_ino;
st.st_mode = d->entry->d_type << 12;
}
nextoff = telldir(d->dp);
if (filler(buf, d->entry->d_name, &st, nextoff))
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
break;
d->entry = NULL;
+6 -4
View File
@@ -65,17 +65,19 @@ static int hello_getattr(const char *path, struct stat *stbuf)
}
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
(void) offset;
(void) fi;
(void) flags;
if (strcmp(path, "/") != 0)
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
filler(buf, hello_path + 1, NULL, 0, 0);
return 0;
}
+34 -3
View File
@@ -36,16 +36,46 @@ extern "C" {
/** Handle for a FUSE filesystem */
struct fuse;
/**
* Readdir flags, passed to ->readdir()
*/
enum fuse_readdir_flags {
/**
* "Plus" mode.
*
* The kernel wants to prefill the inode cache during readdir. The
* filesystem may honour this by filling in the attributes and setting
* FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also
* just ignore this flag completely.
*/
FUSE_READDIR_PLUS = (1 << 0),
};
enum fuse_fill_dir_flags {
/**
* "Plus" mode: all file attributes are valid
*
* The attributes are used by the kernel to prefill the inode cache
* during a readdir.
*
* It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set
* and vice versa.
*/
FUSE_FILL_DIR_PLUS = (1 << 1),
};
/** Function to add an entry in a readdir() operation
*
* @param buf the buffer passed to the readdir() operation
* @param name the file name of the directory entry
* @param stat file attributes, can be NULL
* @param off offset of the next entry or zero
* @param flags fill flags
* @return 1 if buffer is full, zero otherwise
*/
typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
const struct stat *stbuf, off_t off);
const struct stat *stbuf, off_t off,
enum fuse_fill_dir_flags flags);
/**
* The file system operations:
@@ -289,9 +319,10 @@ struct fuse_operations {
* '1'.
*
* Introduced in version 2.3
* The "flags" argument added in version 3.0
*/
int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
struct fuse_file_info *);
struct fuse_file_info *, enum fuse_readdir_flags);
/** Release directory
*
@@ -806,7 +837,7 @@ int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi);
int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
fuse_fill_dir_t filler, off_t off,
struct fuse_file_info *fi);
struct fuse_file_info *fi, enum fuse_readdir_flags flags);
int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
struct fuse_file_info *fi);
int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+255 -51
View File
@@ -188,11 +188,19 @@ struct node_lru {
struct timespec forget_time;
};
struct fuse_direntry {
struct stat stat;
char *name;
struct fuse_direntry *next;
};
struct fuse_dh {
pthread_mutex_t lock;
struct fuse *fuse;
fuse_req_t req;
char *contents;
struct fuse_direntry *first;
struct fuse_direntry **last;
int allocated;
unsigned len;
unsigned size;
@@ -1887,16 +1895,19 @@ int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
fuse_fill_dir_t filler, off_t off,
struct fuse_file_info *fi)
struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
fuse_get_context()->private_data = fs->user_data;
if (fs->op.readdir) {
if (fs->debug)
fprintf(stderr, "readdir[%llu] from %llu\n",
if (fs->debug) {
fprintf(stderr, "readdir%s[%llu] from %llu\n",
(flags & FUSE_READDIR_PLUS) ? "plus" : "",
(unsigned long long) fi->fh,
(unsigned long long) off);
}
return fs->op.readdir(path, buf, filler, off, fi);
return fs->op.readdir(path, buf, filler, off, fi, flags);
} else {
return -ENOSYS;
}
@@ -2329,6 +2340,28 @@ static void update_stat(struct node *node, const struct stat *stbuf)
curr_time(&node->stat_updated);
}
static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
struct fuse_entry_param *e)
{
struct node *node;
node = find_node(f, nodeid, name);
if (node == NULL)
return -ENOMEM;
e->ino = node->nodeid;
e->generation = node->generation;
e->entry_timeout = f->conf.entry_timeout;
e->attr_timeout = f->conf.attr_timeout;
if (f->conf.auto_cache) {
pthread_mutex_lock(&f->lock);
update_stat(node, &e->attr);
pthread_mutex_unlock(&f->lock);
}
set_stat(f, e->ino, &e->attr);
return 0;
}
static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
const char *name, const char *path,
struct fuse_entry_param *e, struct fuse_file_info *fi)
@@ -2341,25 +2374,10 @@ static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
else
res = fuse_fs_getattr(f->fs, path, &e->attr);
if (res == 0) {
struct node *node;
node = find_node(f, nodeid, name);
if (node == NULL)
res = -ENOMEM;
else {
e->ino = node->nodeid;
e->generation = node->generation;
e->entry_timeout = f->conf.entry_timeout;
e->attr_timeout = f->conf.attr_timeout;
if (f->conf.auto_cache) {
pthread_mutex_lock(&f->lock);
update_stat(node, &e->attr);
pthread_mutex_unlock(&f->lock);
}
set_stat(f, e->ino, &e->attr);
if (f->conf.debug)
fprintf(stderr, " NODEID: %llu\n",
(unsigned long long) e->ino);
res = do_lookup(f, nodeid, name, e);
if (res == 0 && f->conf.debug) {
fprintf(stderr, " NODEID: %llu\n",
(unsigned long long) e->ino);
}
}
return res;
@@ -3199,6 +3217,7 @@ static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
memset(dh, 0, sizeof(struct fuse_dh));
dh->fuse = f;
dh->contents = NULL;
dh->first = NULL;
dh->len = 0;
dh->filled = 0;
dh->nodeid = ino;
@@ -3257,12 +3276,56 @@ static int extend_contents(struct fuse_dh *dh, unsigned minsize)
return 0;
}
static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
struct stat *st)
{
struct fuse_direntry *de;
de = malloc(sizeof(struct fuse_direntry));
if (!de) {
dh->error = -ENOMEM;
return -1;
}
de->name = strdup(name);
if (!de->name) {
dh->error = -ENOMEM;
free(de);
return -1;
}
de->stat = *st;
de->next = NULL;
*dh->last = de;
dh->last = &de->next;
return 0;
}
static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
const char *name)
{
struct node *node;
fuse_ino_t res = FUSE_UNKNOWN_INO;
pthread_mutex_lock(&f->lock);
node = lookup_node(f, parent, name);
if (node)
res = node->nodeid;
pthread_mutex_unlock(&f->lock);
return res;
}
static int fill_dir(void *dh_, const char *name, const struct stat *statp,
off_t off)
off_t off, enum fuse_fill_dir_flags flags)
{
struct fuse_dh *dh = (struct fuse_dh *) dh_;
struct stat stbuf;
size_t newlen;
if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
dh->error = -EIO;
return 1;
}
if (statp)
stbuf = *statp;
@@ -3274,16 +3337,19 @@ static int fill_dir(void *dh_, const char *name, const struct stat *statp,
if (!dh->fuse->conf.use_ino) {
stbuf.st_ino = FUSE_UNKNOWN_INO;
if (dh->fuse->conf.readdir_ino) {
struct node *node;
pthread_mutex_lock(&dh->fuse->lock);
node = lookup_node(dh->fuse, dh->nodeid, name);
if (node)
stbuf.st_ino = (ino_t) node->nodeid;
pthread_mutex_unlock(&dh->fuse->lock);
stbuf.st_ino = (ino_t)
lookup_nodeid(dh->fuse, dh->nodeid, name);
}
}
if (off) {
size_t newlen;
if (dh->first) {
dh->error = -EIO;
return 1;
}
if (extend_contents(dh, dh->needlen) == -1)
return 1;
@@ -3294,22 +3360,95 @@ static int fill_dir(void *dh_, const char *name, const struct stat *statp,
&stbuf, off);
if (newlen > dh->needlen)
return 1;
dh->len = newlen;
} else {
newlen = dh->len +
fuse_add_direntry(dh->req, NULL, 0, name, NULL, 0);
if (extend_contents(dh, newlen) == -1)
if (!dh->filled) {
dh->error = -EIO;
return 1;
}
if (fuse_add_direntry_to_dh(dh, name, &stbuf) == -1)
return 1;
}
return 0;
}
static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
off_t off, enum fuse_fill_dir_flags flags)
{
struct fuse_dh *dh = (struct fuse_dh *) dh_;
struct fuse_entry_param e = {
/* ino=0 tells the kernel to ignore readdirplus stat info */
.ino = 0,
};
struct fuse *f = dh->fuse;
int res;
if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
dh->error = -EIO;
return 1;
}
if (off && statp && (flags & FUSE_FILL_DIR_PLUS)) {
e.attr = *statp;
res = do_lookup(f, dh->nodeid, name, &e);
if (res) {
dh->error = res;
return 1;
}
} else {
e.attr.st_ino = FUSE_UNKNOWN_INO;
if (!f->conf.use_ino && f->conf.readdir_ino) {
e.attr.st_ino = (ino_t)
lookup_nodeid(f, dh->nodeid, name);
}
}
if (off) {
size_t newlen;
if (dh->first) {
dh->error = -EIO;
return 1;
}
if (extend_contents(dh, dh->needlen) == -1)
return 1;
fuse_add_direntry(dh->req, dh->contents + dh->len,
dh->size - dh->len, name, &stbuf, newlen);
dh->filled = 0;
newlen = dh->len +
fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
dh->needlen - dh->len, name,
&e, off);
if (newlen > dh->needlen)
return 1;
dh->len = newlen;
} else {
if (!dh->filled) {
dh->error = -EIO;
return 1;
}
if (fuse_add_direntry_to_dh(dh, name, &e.attr) == -1)
return 1;
}
dh->len = newlen;
return 0;
}
static void free_direntries(struct fuse_direntry *de)
{
while (de) {
struct fuse_direntry *next = de->next;
free(de->name);
free(de);
de = next;
}
}
static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
size_t size, off_t off, struct fuse_dh *dh,
struct fuse_file_info *fi)
struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
char *path;
int err;
@@ -3320,14 +3459,21 @@ static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
err = get_path(f, ino, &path);
if (!err) {
struct fuse_intr_data d;
fuse_fill_dir_t filler = fill_dir;
if (flags & FUSE_READDIR_PLUS)
filler = fill_dir_plus;
free_direntries(dh->first);
dh->first = NULL;
dh->last = &dh->first;
dh->len = 0;
dh->error = 0;
dh->needlen = size;
dh->filled = 1;
dh->req = req;
fuse_prepare_interrupt(f, req, &d);
err = fuse_fs_readdir(f->fs, path, dh, fill_dir, off, fi);
err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
fuse_finish_interrupt(f, req, &d);
dh->req = NULL;
if (!err)
@@ -3339,12 +3485,58 @@ static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
return err;
}
static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *llfi)
static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
off_t off, enum fuse_readdir_flags flags)
{
off_t pos;
struct fuse_direntry *de = dh->first;
dh->len = 0;
if (extend_contents(dh, dh->needlen) == -1)
return dh->error;
for (pos = 0; pos < off; pos++) {
if (!de)
break;
de = de->next;
}
while (de) {
char *p = dh->contents + dh->len;
unsigned rem = dh->needlen - dh->len;
unsigned thislen;
unsigned newlen;
pos++;
if (flags & FUSE_READDIR_PLUS) {
struct fuse_entry_param e = {
.ino = 0,
.attr = de->stat,
};
thislen = fuse_add_direntry_plus(req, p, rem,
de->name, &e, pos);
} else {
thislen = fuse_add_direntry(req, p, rem,
de->name, &de->stat, pos);
}
newlen = dh->len + thislen;
if (newlen > dh->needlen)
break;
dh->len = newlen;
de = de->next;
}
return 0;
}
static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *llfi,
enum fuse_readdir_flags flags)
{
struct fuse *f = req_fuse_prepare(req);
struct fuse_file_info fi;
struct fuse_dh *dh = get_dirhandle(llfi, &fi);
int err;
pthread_mutex_lock(&dh->lock);
/* According to SUS, directory contents need to be refreshed on
@@ -3353,27 +3545,37 @@ static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
dh->filled = 0;
if (!dh->filled) {
int err = readdir_fill(f, req, ino, size, off, dh, &fi);
err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
if (err) {
reply_err(req, err);
goto out;
}
}
if (dh->filled) {
if (off < dh->len) {
if (off + size > dh->len)
size = dh->len - off;
} else
size = 0;
} else {
size = dh->len;
off = 0;
dh->needlen = size;
err = readdir_fill_from_list(req, dh, off, flags);
if (err) {
reply_err(req, err);
goto out;
}
}
fuse_reply_buf(req, dh->contents + off, size);
fuse_reply_buf(req, dh->contents, dh->len);
out:
pthread_mutex_unlock(&dh->lock);
}
static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *llfi)
{
fuse_readdir_common(req, ino, size, off, llfi, 0);
}
static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *llfi)
{
fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
}
static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *llfi)
{
@@ -3393,6 +3595,7 @@ static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
pthread_mutex_lock(&dh->lock);
pthread_mutex_unlock(&dh->lock);
pthread_mutex_destroy(&dh->lock);
free_direntries(dh->first);
free(dh->contents);
free(dh);
reply_err(req, 0);
@@ -4023,6 +4226,7 @@ static struct fuse_lowlevel_ops fuse_path_ops = {
.fsync = fuse_lib_fsync,
.opendir = fuse_lib_opendir,
.readdir = fuse_lib_readdir,
.readdirplus = fuse_lib_readdirplus,
.releasedir = fuse_lib_releasedir,
.fsyncdir = fuse_lib_fsyncdir,
.statfs = fuse_lib_statfs,
+6 -4
View File
@@ -172,20 +172,22 @@ static int iconv_opendir(const char *path, struct fuse_file_info *fi)
}
static int iconv_dir_fill(void *buf, const char *name,
const struct stat *stbuf, off_t off)
const struct stat *stbuf, off_t off,
enum fuse_fill_dir_flags flags)
{
struct iconv_dh *dh = buf;
char *newname;
int res = 0;
if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
res = dh->prev_filler(dh->prev_buf, newname, stbuf, off);
res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
free(newname);
}
return res;
}
static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
struct iconv *ic = iconv_get();
char *newpath;
@@ -196,7 +198,7 @@ static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
dh.prev_buf = buf;
dh.prev_filler = filler;
err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
offset, fi);
offset, fi, flags);
free(newpath);
}
return err;
+3 -2
View File
@@ -181,14 +181,15 @@ static int subdir_opendir(const char *path, struct fuse_file_info *fi)
static int subdir_readdir(const char *path, void *buf,
fuse_fill_dir_t filler, off_t offset,
struct fuse_file_info *fi)
struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
struct subdir *d = subdir_get();
char *newpath;
int err = subdir_addpath(d, path, &newpath);
if (!err) {
err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
fi);
fi, flags);
free(newpath);
}
return err;