diff options
author | Anand V. Avati <avati@redhat.com> | 2012-08-19 08:53:23 -0400 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2013-01-24 10:21:25 -0500 |
commit | 0b05b18381eea98c9c9ada95629bf659a88c9374 (patch) | |
tree | a6389eaffda03a2e28cb05be242e03ef839fcb91 /fs/fuse/dir.c | |
parent | ff7532ca2c631e7e96dcd305a967b610259dc0ea (diff) |
fuse: implement NFS-like readdirplus support
This patch implements readdirplus support in FUSE, similar to NFS.
The payload returned in the readdirplus call contains
'fuse_entry_out' structure thereby providing all the necessary inputs
for 'faking' a lookup() operation on the spot.
If the dentry and inode already existed (for e.g. in a re-run of ls -l)
then just the inode attributes timeout and dentry timeout are refreshed.
With a simple client->network->server implementation of a FUSE based
filesystem, the following performance observations were made:
Test: Performing a filesystem crawl over 20,000 files with
sh# time ls -lR /mnt
Without readdirplus:
Run 1: 18.1s
Run 2: 16.0s
Run 3: 16.2s
With readdirplus:
Run 1: 4.1s
Run 2: 3.8s
Run 3: 3.8s
The performance improvement is significant as it avoided 20,000 upcalls
calls (lookup). Cache consistency is no worse than what already is.
Signed-off-by: Anand V. Avati <avati@redhat.com>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Diffstat (limited to 'fs/fuse/dir.c')
-rw-r--r-- | fs/fuse/dir.c | 160 |
1 files changed, 156 insertions, 4 deletions
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index b7c09f9eb40c..dcc1e522c7d4 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c | |||
@@ -1155,6 +1155,143 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file, | |||
1155 | return 0; | 1155 | return 0; |
1156 | } | 1156 | } |
1157 | 1157 | ||
1158 | static int fuse_direntplus_link(struct file *file, | ||
1159 | struct fuse_direntplus *direntplus, | ||
1160 | u64 attr_version) | ||
1161 | { | ||
1162 | int err; | ||
1163 | struct fuse_entry_out *o = &direntplus->entry_out; | ||
1164 | struct fuse_dirent *dirent = &direntplus->dirent; | ||
1165 | struct dentry *parent = file->f_path.dentry; | ||
1166 | struct qstr name = QSTR_INIT(dirent->name, dirent->namelen); | ||
1167 | struct dentry *dentry; | ||
1168 | struct dentry *alias; | ||
1169 | struct inode *dir = parent->d_inode; | ||
1170 | struct fuse_conn *fc; | ||
1171 | struct inode *inode; | ||
1172 | |||
1173 | if (!o->nodeid) { | ||
1174 | /* | ||
1175 | * Unlike in the case of fuse_lookup, zero nodeid does not mean | ||
1176 | * ENOENT. Instead, it only means the userspace filesystem did | ||
1177 | * not want to return attributes/handle for this entry. | ||
1178 | * | ||
1179 | * So do nothing. | ||
1180 | */ | ||
1181 | return 0; | ||
1182 | } | ||
1183 | |||
1184 | if (name.name[0] == '.') { | ||
1185 | /* | ||
1186 | * We could potentially refresh the attributes of the directory | ||
1187 | * and its parent? | ||
1188 | */ | ||
1189 | if (name.len == 1) | ||
1190 | return 0; | ||
1191 | if (name.name[1] == '.' && name.len == 2) | ||
1192 | return 0; | ||
1193 | } | ||
1194 | fc = get_fuse_conn(dir); | ||
1195 | |||
1196 | name.hash = full_name_hash(name.name, name.len); | ||
1197 | dentry = d_lookup(parent, &name); | ||
1198 | if (dentry && dentry->d_inode) { | ||
1199 | inode = dentry->d_inode; | ||
1200 | if (get_node_id(inode) == o->nodeid) { | ||
1201 | struct fuse_inode *fi; | ||
1202 | fi = get_fuse_inode(inode); | ||
1203 | spin_lock(&fc->lock); | ||
1204 | fi->nlookup++; | ||
1205 | spin_unlock(&fc->lock); | ||
1206 | |||
1207 | /* | ||
1208 | * The other branch to 'found' comes via fuse_iget() | ||
1209 | * which bumps nlookup inside | ||
1210 | */ | ||
1211 | goto found; | ||
1212 | } | ||
1213 | err = d_invalidate(dentry); | ||
1214 | if (err) | ||
1215 | goto out; | ||
1216 | dput(dentry); | ||
1217 | dentry = NULL; | ||
1218 | } | ||
1219 | |||
1220 | dentry = d_alloc(parent, &name); | ||
1221 | err = -ENOMEM; | ||
1222 | if (!dentry) | ||
1223 | goto out; | ||
1224 | |||
1225 | inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, | ||
1226 | &o->attr, entry_attr_timeout(o), attr_version); | ||
1227 | if (!inode) | ||
1228 | goto out; | ||
1229 | |||
1230 | alias = d_materialise_unique(dentry, inode); | ||
1231 | err = PTR_ERR(alias); | ||
1232 | if (IS_ERR(alias)) | ||
1233 | goto out; | ||
1234 | if (alias) { | ||
1235 | dput(dentry); | ||
1236 | dentry = alias; | ||
1237 | } | ||
1238 | |||
1239 | found: | ||
1240 | fuse_change_attributes(inode, &o->attr, entry_attr_timeout(o), | ||
1241 | attr_version); | ||
1242 | |||
1243 | fuse_change_entry_timeout(dentry, o); | ||
1244 | |||
1245 | err = 0; | ||
1246 | out: | ||
1247 | if (dentry) | ||
1248 | dput(dentry); | ||
1249 | return err; | ||
1250 | } | ||
1251 | |||
1252 | static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file, | ||
1253 | void *dstbuf, filldir_t filldir, u64 attr_version) | ||
1254 | { | ||
1255 | struct fuse_direntplus *direntplus; | ||
1256 | struct fuse_dirent *dirent; | ||
1257 | size_t reclen; | ||
1258 | int over = 0; | ||
1259 | int ret; | ||
1260 | |||
1261 | while (nbytes >= FUSE_NAME_OFFSET_DIRENTPLUS) { | ||
1262 | direntplus = (struct fuse_direntplus *) buf; | ||
1263 | dirent = &direntplus->dirent; | ||
1264 | reclen = FUSE_DIRENTPLUS_SIZE(direntplus); | ||
1265 | |||
1266 | if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) | ||
1267 | return -EIO; | ||
1268 | if (reclen > nbytes) | ||
1269 | break; | ||
1270 | |||
1271 | if (!over) { | ||
1272 | /* We fill entries into dstbuf only as much as | ||
1273 | it can hold. But we still continue iterating | ||
1274 | over remaining entries to link them. If not, | ||
1275 | we need to send a FORGET for each of those | ||
1276 | which we did not link. | ||
1277 | */ | ||
1278 | over = filldir(dstbuf, dirent->name, dirent->namelen, | ||
1279 | file->f_pos, dirent->ino, | ||
1280 | dirent->type); | ||
1281 | file->f_pos = dirent->off; | ||
1282 | } | ||
1283 | |||
1284 | buf += reclen; | ||
1285 | nbytes -= reclen; | ||
1286 | |||
1287 | ret = fuse_direntplus_link(file, direntplus, attr_version); | ||
1288 | if (ret) | ||
1289 | fuse_force_forget(file, direntplus->entry_out.nodeid); | ||
1290 | } | ||
1291 | |||
1292 | return 0; | ||
1293 | } | ||
1294 | |||
1158 | static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) | 1295 | static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) |
1159 | { | 1296 | { |
1160 | int err; | 1297 | int err; |
@@ -1163,6 +1300,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) | |||
1163 | struct inode *inode = file->f_path.dentry->d_inode; | 1300 | struct inode *inode = file->f_path.dentry->d_inode; |
1164 | struct fuse_conn *fc = get_fuse_conn(inode); | 1301 | struct fuse_conn *fc = get_fuse_conn(inode); |
1165 | struct fuse_req *req; | 1302 | struct fuse_req *req; |
1303 | u64 attr_version = 0; | ||
1166 | 1304 | ||
1167 | if (is_bad_inode(inode)) | 1305 | if (is_bad_inode(inode)) |
1168 | return -EIO; | 1306 | return -EIO; |
@@ -1179,14 +1317,28 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) | |||
1179 | req->out.argpages = 1; | 1317 | req->out.argpages = 1; |
1180 | req->num_pages = 1; | 1318 | req->num_pages = 1; |
1181 | req->pages[0] = page; | 1319 | req->pages[0] = page; |
1182 | fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, FUSE_READDIR); | 1320 | if (fc->do_readdirplus) { |
1321 | attr_version = fuse_get_attr_version(fc); | ||
1322 | fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, | ||
1323 | FUSE_READDIRPLUS); | ||
1324 | } else { | ||
1325 | fuse_read_fill(req, file, file->f_pos, PAGE_SIZE, | ||
1326 | FUSE_READDIR); | ||
1327 | } | ||
1183 | fuse_request_send(fc, req); | 1328 | fuse_request_send(fc, req); |
1184 | nbytes = req->out.args[0].size; | 1329 | nbytes = req->out.args[0].size; |
1185 | err = req->out.h.error; | 1330 | err = req->out.h.error; |
1186 | fuse_put_request(fc, req); | 1331 | fuse_put_request(fc, req); |
1187 | if (!err) | 1332 | if (!err) { |
1188 | err = parse_dirfile(page_address(page), nbytes, file, dstbuf, | 1333 | if (fc->do_readdirplus) { |
1189 | filldir); | 1334 | err = parse_dirplusfile(page_address(page), nbytes, |
1335 | file, dstbuf, filldir, | ||
1336 | attr_version); | ||
1337 | } else { | ||
1338 | err = parse_dirfile(page_address(page), nbytes, file, | ||
1339 | dstbuf, filldir); | ||
1340 | } | ||
1341 | } | ||
1190 | 1342 | ||
1191 | __free_page(page); | 1343 | __free_page(page); |
1192 | fuse_invalidate_attr(inode); /* atime changed */ | 1344 | fuse_invalidate_attr(inode); /* atime changed */ |