diff options
Diffstat (limited to 'fs/nfs/dir.c')
-rw-r--r-- | fs/nfs/dir.c | 311 |
1 files changed, 194 insertions, 117 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index ed8b1fe0e..d41505862 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -78,13 +78,13 @@ struct inode_operations nfs_dir_inode_operations = { nfs_rename, /* rename */ NULL, /* readlink */ NULL, /* follow_link */ + NULL, /* bmap */ NULL, /* readpage */ NULL, /* writepage */ - NULL, /* bmap */ + NULL, /* flushpage */ NULL, /* truncate */ NULL, /* permission */ NULL, /* smap */ - NULL, /* updatepage */ nfs_revalidate, /* revalidate */ }; @@ -118,6 +118,61 @@ struct nfs_cookie_table { }; static kmem_cache_t *nfs_cookie_cachep; +/* This whole scheme relies on the fact that dirent cookies + * are monotonically increasing. + * + * Another invariant is that once we have a valid non-zero + * EOF marker cached, we also have the complete set of cookie + * table entries. + * + * We return the page offset assosciated with the page where + * cookie must be if it exists at all, however if we can not + * figure that out conclusively, we return < 0. + */ +static long __nfs_readdir_offset(struct inode *inode, __u32 cookie) +{ + struct nfs_cookie_table *p; + unsigned long ret = 0; + + for(p = NFS_COOKIES(inode); p != NULL; p = p->next) { + int i; + + for (i = 0; i < COOKIES_PER_CHUNK; i++) { + __u32 this_cookie = p->cookies[i]; + + /* End of known cookies, EOF is our only hope. */ + if (!this_cookie) + goto check_eof; + + /* Next cookie is larger, must be in previous page. */ + if (this_cookie > cookie) + return ret; + + ret += 1; + + /* Exact cookie match, it must be in this page :-) */ + if (this_cookie == cookie) + return ret; + } + } +check_eof: + if (NFS_DIREOF(inode) != 0) + return ret; + + return -1L; +} + +static __inline__ long nfs_readdir_offset(struct inode *inode, __u32 cookie) +{ + /* Cookie zero is always at page offset zero. Optimize the + * other common case since most directories fit entirely + * in one page. + */ + if (!cookie || (!NFS_COOKIES(inode) && NFS_DIREOF(inode))) + return 0; + return __nfs_readdir_offset(inode, cookie); +} + /* Since a cookie of zero is declared special by the NFS * protocol, we easily can tell if a cookie in an existing * table chunk is valid or not. @@ -148,38 +203,7 @@ static __inline__ __u32 *find_cookie(struct inode *inode, unsigned long off) return ret; } -/* Now we cache directories properly, by stuffing the dirent - * data directly in the page cache. - * - * Inode invalidation due to refresh etc. takes care of - * _everything_, no sloppy entry flushing logic, no extraneous - * copying, network direct to page cache, the way it was meant - * to be. - * - * NOTE: Dirent information verification is done always by the - * page-in of the RPC reply, nowhere else, this simplies - * things substantially. - */ #define NFS_NAMELEN_ALIGN(__len) ((((__len)+3)>>2)<<2) -static u32 find_midpoint(__u32 *p, u32 doff) -{ - u32 walk = doff & PAGE_MASK; - - while(*p++ != 0) { - __u32 skip; - - p++; /* skip fileid */ - - /* Skip len, name, and cookie. */ - skip = NFS_NAMELEN_ALIGN(*p++); - p += (skip >> 2) + 1; - walk += skip + (4 * sizeof(__u32)); - if (walk >= doff) - break; - } - return walk; -} - static int create_cookie(__u32 cookie, unsigned long off, struct inode *inode) { struct nfs_cookie_table **cpp; @@ -211,48 +235,74 @@ static int create_cookie(__u32 cookie, unsigned long off, struct inode *inode) return 0; } -static struct page *try_to_get_dirent_page(struct file *, unsigned long, int); +static struct page *try_to_get_dirent_page(struct file *, __u32, int); /* Recover from a revalidation flush. The case here is that * the inode for the directory got invalidated somehow, and * all of our cached information is lost. In order to get * a correct cookie for the current readdir request from the * user, we must (re-)fetch older readdir page cache entries. + * + * Returns < 0 if some error occurrs, else it is the page offset + * to fetch. */ -static int refetch_to_readdir_off(struct file *file, struct inode *inode, u32 off) +static long refetch_to_readdir_cookie(struct file *file, struct inode *inode) { - u32 cur_off, goal_off = off & PAGE_MASK; + struct page *page; + u32 goal_cookie = file->f_pos; + long cur_off, ret = -1L; again: cur_off = 0; - while (cur_off < goal_off) { - struct page *page; - - page = find_page(inode, cur_off); + for (;;) { + page = find_get_page(inode, cur_off); if (page) { - if (PageLocked(page)) - __wait_on_page(page); - if (!PageUptodate(page)) - return -1; + if (!Page_Uptodate(page)) + goto out_error; } else { - page = try_to_get_dirent_page(file, cur_off, 0); + __u32 *cp = find_cookie(inode, cur_off); + + if (!cp) + goto out_error; + + page = try_to_get_dirent_page(file, *cp, 0); if (!page) { if (!cur_off) - return -1; + goto out_error; /* Someone touched the dir on us. */ goto again; } - page_cache_release(page); } + page_cache_release(page); - cur_off += PAGE_SIZE; + if ((ret = nfs_readdir_offset(inode, goal_cookie)) >= 0) + goto out; + + cur_off += 1; } +out: + return ret; - return 0; +out_error: + if (page) + page_cache_release(page); + goto out; } -static struct page *try_to_get_dirent_page(struct file *file, unsigned long offset, int refetch_ok) +/* Now we cache directories properly, by stuffing the dirent + * data directly in the page cache. + * + * Inode invalidation due to refresh etc. takes care of + * _everything_, no sloppy entry flushing logic, no extraneous + * copying, network direct to page cache, the way it was meant + * to be. + * + * NOTE: Dirent information verification is done always by the + * page-in of the RPC reply, nowhere else, this simplies + * things substantially. + */ +static struct page *try_to_get_dirent_page(struct file *file, __u32 cookie, int refetch_ok) { struct nfs_readdirargs rd_args; struct nfs_readdirres rd_res; @@ -260,6 +310,7 @@ static struct page *try_to_get_dirent_page(struct file *file, unsigned long offs struct inode *inode = dentry->d_inode; struct page *page, **hash; unsigned long page_cache; + long offset; __u32 *cookiep; page = NULL; @@ -267,27 +318,34 @@ static struct page *try_to_get_dirent_page(struct file *file, unsigned long offs if (!page_cache) goto out; - while ((cookiep = find_cookie(inode, offset)) == NULL) { + if ((offset = nfs_readdir_offset(inode, cookie)) < 0) { if (!refetch_ok || - refetch_to_readdir_off(file, inode, file->f_pos)) + (offset = refetch_to_readdir_cookie(file, inode)) < 0) { + page_cache_free(page_cache); goto out; + } + } + + cookiep = find_cookie(inode, offset); + if (!cookiep) { + /* Gross fatal error. */ + page_cache_free(page_cache); + goto out; } hash = page_hash(inode, offset); - page = __find_page(inode, offset, *hash); +repeat: + page = __find_lock_page(inode, offset, hash); if (page) { page_cache_free(page_cache); - goto out; + goto unlock_out; } page = page_cache_entry(page_cache); - atomic_inc(&page->count); - page->flags = ((page->flags & - ~((1 << PG_uptodate) | (1 << PG_error))) | - ((1 << PG_referenced) | (1 << PG_locked))); - page->offset = offset; - add_page_to_inode_queue(inode, page); - __add_page_to_hash_queue(page, hash); + if (add_to_page_cache_unique(page, inode, offset, hash)) { + page_cache_release(page); + goto repeat; + } rd_args.fh = NFS_FH(dentry); rd_res.buffer = (char *)page_cache; @@ -303,48 +361,50 @@ static struct page *try_to_get_dirent_page(struct file *file, unsigned long offs } while(rd_res.bufsiz > 0); if (rd_res.bufsiz < 0) - NFS_DIREOF(inode) = - (offset << PAGE_CACHE_SHIFT) + -(rd_res.bufsiz); + NFS_DIREOF(inode) = rd_res.cookie; else if (create_cookie(rd_res.cookie, offset, inode)) goto error; - set_bit(PG_uptodate, &page->flags); + SetPageUptodate(page); unlock_out: - clear_bit(PG_locked, &page->flags); - wake_up(&page->wait); + UnlockPage(page); out: return page; error: - set_bit(PG_error, &page->flags); + SetPageError(page); goto unlock_out; } -static __inline__ u32 nfs_do_filldir(__u32 *p, u32 doff, +/* Seek up to dirent assosciated with the passed in cookie, + * then fill in dirents found. Return the last cookie + * actually given to the user, to update the file position. + */ +static __inline__ u32 nfs_do_filldir(__u32 *p, u32 cookie, void *dirent, filldir_t filldir) { u32 end; - if (doff & ~PAGE_CACHE_MASK) { - doff = find_midpoint(p, doff); - p += (doff & ~PAGE_CACHE_MASK) >> 2; - } while((end = *p++) != 0) { - __u32 fileid = *p++; - __u32 len = *p++; - __u32 skip = NFS_NAMELEN_ALIGN(len); - char *name = (char *) p; - - /* Skip the cookie. */ - p = ((__u32 *) (name + skip)) + 1; - if (filldir(dirent, name, len, doff, fileid) < 0) - goto out; - doff += (skip + (4 * sizeof(__u32))); + __u32 fileid, len, skip, this_cookie; + char *name; + + fileid = *p++; + len = *p++; + name = (char *) p; + skip = NFS_NAMELEN_ALIGN(len); + p += (skip >> 2); + this_cookie = *p++; + + if (this_cookie < cookie) + continue; + + cookie = this_cookie; + if (filldir(dirent, name, len, cookie, fileid) < 0) + break; } - if (!*p) - doff = PAGE_CACHE_ALIGN(doff); -out: - return doff; + + return cookie; } /* The file offset position is represented in pure bytes, to @@ -359,7 +419,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir) struct dentry *dentry = filp->f_dentry; struct inode *inode = dentry->d_inode; struct page *page, **hash; - unsigned long offset; + long offset; int res; res = nfs_revalidate_inode(NFS_DSERVER(dentry), dentry); @@ -369,14 +429,14 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir) if (NFS_DIREOF(inode) && filp->f_pos >= NFS_DIREOF(inode)) return 0; - offset = filp->f_pos >> PAGE_CACHE_SHIFT; + if ((offset = nfs_readdir_offset(inode, filp->f_pos)) < 0) + goto no_dirent_page; + hash = page_hash(inode, offset); - page = __find_page(inode, offset, *hash); + page = __find_get_page(inode, offset, hash); if (!page) goto no_dirent_page; - if (PageLocked(page)) - goto dirent_locked_wait; - if (!PageUptodate(page)) + if (!Page_Uptodate(page)) goto dirent_read_error; success: filp->f_pos = nfs_do_filldir((__u32 *) page_address(page), @@ -385,13 +445,11 @@ success: return 0; no_dirent_page: - page = try_to_get_dirent_page(filp, offset, 1); + page = try_to_get_dirent_page(filp, filp->f_pos, 1); if (!page) goto no_page; -dirent_locked_wait: - wait_on_page(page); - if (PageUptodate(page)) + if (Page_Uptodate(page)) goto success; dirent_read_error: page_cache_release(page); @@ -399,20 +457,39 @@ no_page: return -EIO; } -/* Invalidate directory cookie caches and EOF marker - * for an inode. +/* Flush directory cookie and EOF caches for an inode. + * So we don't thrash allocating/freeing cookie tables, + * we keep the cookies around until the inode is + * deleted/reused. + */ +__inline__ void nfs_flush_dircache(struct inode *inode) +{ + struct nfs_cookie_table *p = NFS_COOKIES(inode); + + while (p != NULL) { + int i; + + for(i = 0; i < COOKIES_PER_CHUNK; i++) + p->cookies[i] = 0; + + p = p->next; + } + NFS_DIREOF(inode) = 0; +} + +/* Free up directory cache state, this happens when + * nfs_delete_inode is called on an NFS directory. */ -__inline__ void nfs_invalidate_dircache(struct inode *inode) +void nfs_free_dircache(struct inode *inode) { struct nfs_cookie_table *p = NFS_COOKIES(inode); - if (p != NULL) { - NFS_COOKIES(inode) = NULL; - do { struct nfs_cookie_table *next = p->next; - kmem_cache_free(nfs_cookie_cachep, p); - p = next; - } while (p != NULL); + while (p != NULL) { + struct nfs_cookie_table *next = p->next; + kmem_cache_free(nfs_cookie_cachep, p); + p = next; } + NFS_COOKIES(inode) = NULL; NFS_DIREOF(inode) = 0; } @@ -538,11 +615,11 @@ out_bad: /* Purge readdir caches. */ if (dentry->d_parent->d_inode) { invalidate_inode_pages(dentry->d_parent->d_inode); - nfs_invalidate_dircache(dentry->d_parent->d_inode); + nfs_flush_dircache(dentry->d_parent->d_inode); } if (inode && S_ISDIR(inode->i_mode)) { invalidate_inode_pages(inode); - nfs_invalidate_dircache(inode); + nfs_flush_dircache(inode); } return 0; } @@ -739,7 +816,7 @@ static int nfs_create(struct inode *dir, struct dentry *dentry, int mode) * Invalidate the dir cache before the operation to avoid a race. */ invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name, &sattr, &fhandle, &fattr); if (!error) @@ -769,7 +846,7 @@ static int nfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rde sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1; invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name, &sattr, &fhandle, &fattr); if (!error) @@ -804,7 +881,7 @@ static int nfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) */ d_drop(dentry); invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_mkdir(NFS_DSERVER(dentry), NFS_FH(dentry->d_parent), dentry->d_name.name, &sattr, &fhandle, &fattr); return error; @@ -825,7 +902,7 @@ dentry->d_inode->i_count, dentry->d_inode->i_nlink); #endif invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name); @@ -953,7 +1030,7 @@ dentry->d_parent->d_name.name, dentry->d_name.name); } while(sdentry->d_inode != NULL); /* need negative lookup */ invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_rename(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name, NFS_FH(dentry->d_parent), silly); @@ -1023,7 +1100,7 @@ inode->i_count, inode->i_nlink); d_delete(dentry); } invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name); /* @@ -1090,7 +1167,7 @@ dentry->d_parent->d_name.name, dentry->d_name.name); */ d_drop(dentry); invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_symlink(NFS_SERVER(dir), NFS_FH(dentry->d_parent), dentry->d_name.name, symname, &sattr); if (!error) { @@ -1121,7 +1198,7 @@ nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) */ d_drop(dentry); invalidate_inode_pages(dir); - nfs_invalidate_dircache(dir); + nfs_flush_dircache(dir); error = nfs_proc_link(NFS_DSERVER(old_dentry), NFS_FH(old_dentry), NFS_FH(dentry->d_parent), dentry->d_name.name); if (!error) { @@ -1267,9 +1344,9 @@ new_inode->i_count, new_inode->i_nlink); } invalidate_inode_pages(new_dir); - nfs_invalidate_dircache(new_dir); + nfs_flush_dircache(new_dir); invalidate_inode_pages(old_dir); - nfs_invalidate_dircache(old_dir); + nfs_flush_dircache(old_dir); error = nfs_proc_rename(NFS_DSERVER(old_dentry), NFS_FH(old_dentry->d_parent), old_dentry->d_name.name, NFS_FH(new_dentry->d_parent), new_dentry->d_name.name); |