| /* |
| * linux/fs/nfs/dir.c |
| * |
| * Copyright (C) 1992 Rick Sladkey |
| * |
| * nfs directory handling functions |
| */ |
| |
| #include <linux/sched.h> |
| #include <linux/errno.h> |
| #include <linux/stat.h> |
| #include <linux/nfs_fs.h> |
| #include <linux/fcntl.h> |
| #include <linux/string.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| |
| #include <asm/segment.h> /* for fs functions */ |
| |
| static int nfs_dir_read(struct inode *, struct file *filp, char *buf, |
| int count); |
| static int nfs_readdir(struct inode *, struct file *, struct dirent *, int); |
| static int nfs_lookup(struct inode *dir, const char *name, int len, |
| struct inode **result); |
| static int nfs_create(struct inode *dir, const char *name, int len, int mode, |
| struct inode **result); |
| static int nfs_mkdir(struct inode *dir, const char *name, int len, int mode); |
| static int nfs_rmdir(struct inode *dir, const char *name, int len); |
| static int nfs_unlink(struct inode *dir, const char *name, int len); |
| static int nfs_symlink(struct inode *inode, const char *name, int len, |
| const char *symname); |
| static int nfs_link(struct inode *oldinode, struct inode *dir, |
| const char *name, int len); |
| static int nfs_mknod(struct inode *dir, const char *name, int len, int mode, |
| int rdev); |
| static int nfs_rename(struct inode *old_dir, const char *old_name, |
| int old_len, struct inode *new_dir, const char *new_name, |
| int new_len); |
| |
| static struct file_operations nfs_dir_operations = { |
| NULL, /* lseek - default */ |
| nfs_dir_read, /* read - bad */ |
| NULL, /* write - bad */ |
| nfs_readdir, /* readdir */ |
| NULL, /* select - default */ |
| NULL, /* ioctl - default */ |
| NULL, /* mmap */ |
| NULL, /* no special open code */ |
| NULL, /* no special release code */ |
| NULL /* fsync */ |
| }; |
| |
| struct inode_operations nfs_dir_inode_operations = { |
| &nfs_dir_operations, /* default directory file-ops */ |
| nfs_create, /* create */ |
| nfs_lookup, /* lookup */ |
| nfs_link, /* link */ |
| nfs_unlink, /* unlink */ |
| nfs_symlink, /* symlink */ |
| nfs_mkdir, /* mkdir */ |
| nfs_rmdir, /* rmdir */ |
| nfs_mknod, /* mknod */ |
| nfs_rename, /* rename */ |
| NULL, /* readlink */ |
| NULL, /* follow_link */ |
| NULL, /* bmap */ |
| NULL, /* truncate */ |
| NULL /* permission */ |
| }; |
| |
| static int nfs_dir_read(struct inode *inode, struct file *filp, char *buf, |
| int count) |
| { |
| return -EISDIR; |
| } |
| |
| /* |
| * We need to do caching of directory entries to prevent an |
| * incredible amount of RPC traffic. Only the most recent open |
| * directory is cached. This seems sufficient for most purposes. |
| * Technically, we ought to flush the cache on close but this is |
| * not a problem in practice. |
| */ |
| |
| static int nfs_readdir(struct inode *inode, struct file *filp, |
| struct dirent *dirent, int count) |
| { |
| static int c_dev = 0; |
| static int c_ino; |
| static int c_size; |
| static struct nfs_entry *c_entry = NULL; |
| |
| int result; |
| int i; |
| struct nfs_entry *entry; |
| |
| if (!inode || !S_ISDIR(inode->i_mode)) { |
| printk("nfs_readdir: inode is NULL or not a directory\n"); |
| return -EBADF; |
| } |
| |
| /* initialize cache memory if it hasn't been used before */ |
| |
| if (c_entry == NULL) { |
| i = sizeof (struct nfs_entry)*NFS_READDIR_CACHE_SIZE; |
| c_entry = (struct nfs_entry *) kmalloc(i, GFP_KERNEL); |
| for (i = 0; i < NFS_READDIR_CACHE_SIZE; i++) { |
| c_entry[i].name = (char *) kmalloc(NFS_MAXNAMLEN + 1, |
| GFP_KERNEL); |
| } |
| } |
| entry = NULL; |
| |
| /* try to find it in the cache */ |
| |
| if (inode->i_dev == c_dev && inode->i_ino == c_ino) { |
| for (i = 0; i < c_size; i++) { |
| if (filp->f_pos == c_entry[i].cookie) { |
| if (i == c_size - 1) { |
| if (c_entry[i].eof) |
| return 0; |
| } |
| else |
| entry = c_entry + i + 1; |
| break; |
| } |
| } |
| } |
| |
| /* if we didn't find it in the cache, revert to an nfs call */ |
| |
| if (!entry) { |
| result = nfs_proc_readdir(NFS_SERVER(inode), NFS_FH(inode), |
| filp->f_pos, NFS_READDIR_CACHE_SIZE, c_entry); |
| if (result < 0) { |
| c_dev = 0; |
| return result; |
| } |
| if (result > 0) { |
| c_dev = inode->i_dev; |
| c_ino = inode->i_ino; |
| c_size = result; |
| entry = c_entry + 0; |
| } |
| } |
| |
| /* if we found it in the cache or from an nfs call, return results */ |
| |
| if (entry) { |
| i = strlen(entry->name); |
| memcpy_tofs(dirent->d_name, entry->name, i + 1); |
| put_fs_long(entry->fileid, &dirent->d_ino); |
| put_fs_word(i, &dirent->d_reclen); |
| filp->f_pos = entry->cookie; |
| return i; |
| } |
| return 0; |
| } |
| |
| /* |
| * Lookup caching is a big win for performance but this is just |
| * a trial to see how well it works on a small scale. |
| * For example, bash does a lookup on ".." 13 times for each path |
| * element when running pwd. Yes, hard to believe but true. |
| * Try pwd in a filesystem mounted with noac. |
| * |
| * It trades a little cpu time and memory for a lot of network bandwidth. |
| * Since the cache is not hashed yet, it is a good idea not to make it too |
| * large because every lookup looks through the entire cache even |
| * though most of them will fail. |
| */ |
| |
| static struct nfs_lookup_cache_entry { |
| int dev; |
| int inode; |
| char filename[NFS_MAXNAMLEN + 1]; |
| struct nfs_fh fhandle; |
| struct nfs_fattr fattr; |
| int expiration_date; |
| } nfs_lookup_cache[NFS_LOOKUP_CACHE_SIZE]; |
| |
| static struct nfs_lookup_cache_entry *nfs_lookup_cache_index(struct inode *dir, |
| const char *filename) |
| { |
| struct nfs_lookup_cache_entry *entry; |
| int i; |
| |
| for (i = 0; i < NFS_LOOKUP_CACHE_SIZE; i++) { |
| entry = nfs_lookup_cache + i; |
| if (entry->dev == dir->i_dev && entry->inode == dir->i_ino |
| && !strncmp(filename, entry->filename, NFS_MAXNAMLEN)) |
| return entry; |
| } |
| return NULL; |
| } |
| |
| static int nfs_lookup_cache_lookup(struct inode *dir, const char *filename, |
| struct nfs_fh *fhandle, |
| struct nfs_fattr *fattr) |
| { |
| static int nfs_lookup_cache_in_use = 0; |
| |
| struct nfs_lookup_cache_entry *entry; |
| |
| if (!nfs_lookup_cache_in_use) { |
| memset(nfs_lookup_cache, 0, sizeof(nfs_lookup_cache)); |
| nfs_lookup_cache_in_use = 1; |
| } |
| if ((entry = nfs_lookup_cache_index(dir, filename))) { |
| if (jiffies > entry->expiration_date) { |
| entry->dev = 0; |
| return 0; |
| } |
| *fhandle = entry->fhandle; |
| *fattr = entry->fattr; |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void nfs_lookup_cache_add(struct inode *dir, const char *filename, |
| struct nfs_fh *fhandle, |
| struct nfs_fattr *fattr) |
| { |
| static int nfs_lookup_cache_pos = 0; |
| struct nfs_lookup_cache_entry *entry; |
| |
| /* compensate for bug in SGI NFS server */ |
| if (fattr->size == -1 || fattr->uid == -1 || fattr->gid == -1 |
| || fattr->atime.seconds == -1 || fattr->mtime.seconds == -1) |
| return; |
| if (!(entry = nfs_lookup_cache_index(dir, filename))) { |
| entry = nfs_lookup_cache + nfs_lookup_cache_pos++; |
| if (nfs_lookup_cache_pos == NFS_LOOKUP_CACHE_SIZE) |
| nfs_lookup_cache_pos = 0; |
| } |
| entry->dev = dir->i_dev; |
| entry->inode = dir->i_ino; |
| strcpy(entry->filename, filename); |
| entry->fhandle = *fhandle; |
| entry->fattr = *fattr; |
| entry->expiration_date = jiffies + (S_ISDIR(fattr->mode) |
| ? NFS_SERVER(dir)->acdirmax : NFS_SERVER(dir)->acregmax); |
| } |
| |
| static void nfs_lookup_cache_remove(struct inode *dir, struct inode *inode, |
| const char *filename) |
| { |
| struct nfs_lookup_cache_entry *entry; |
| int dev; |
| int fileid; |
| int i; |
| |
| if (inode) { |
| dev = inode->i_dev; |
| fileid = inode->i_ino; |
| } |
| else if ((entry = nfs_lookup_cache_index(dir, filename))) { |
| dev = entry->dev; |
| fileid = entry->fattr.fileid; |
| } |
| else |
| return; |
| for (i = 0; i < NFS_LOOKUP_CACHE_SIZE; i++) { |
| entry = nfs_lookup_cache + i; |
| if (entry->dev == dev && entry->fattr.fileid == fileid) |
| entry->dev = 0; |
| } |
| } |
| |
| static void nfs_lookup_cache_refresh(struct inode *file, |
| struct nfs_fattr *fattr) |
| { |
| struct nfs_lookup_cache_entry *entry; |
| int dev = file->i_dev; |
| int fileid = file->i_ino; |
| int i; |
| |
| for (i = 0; i < NFS_LOOKUP_CACHE_SIZE; i++) { |
| entry = nfs_lookup_cache + i; |
| if (entry->dev == dev && entry->fattr.fileid == fileid) |
| entry->fattr = *fattr; |
| } |
| } |
| |
| static int nfs_lookup(struct inode *dir, const char *__name, int len, |
| struct inode **result) |
| { |
| struct nfs_fh fhandle; |
| struct nfs_fattr fattr; |
| char name[len > NFS_MAXNAMLEN? 1 : len+1]; |
| int error; |
| |
| *result = NULL; |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_lookup: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| memcpy(name,__name,len); |
| name[len] = '\0'; |
| if (len == 1 && name[0] == '.') { /* cheat for "." */ |
| *result = dir; |
| return 0; |
| } |
| if ((NFS_SERVER(dir)->flags & NFS_MOUNT_NOAC) |
| || !nfs_lookup_cache_lookup(dir, name, &fhandle, &fattr)) { |
| if ((error = nfs_proc_lookup(NFS_SERVER(dir), NFS_FH(dir), |
| name, &fhandle, &fattr))) { |
| iput(dir); |
| return error; |
| } |
| nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
| } |
| if (!(*result = nfs_fhget(dir->i_sb, &fhandle, &fattr))) { |
| iput(dir); |
| return -EACCES; |
| } |
| iput(dir); |
| return 0; |
| } |
| |
| static int nfs_create(struct inode *dir, const char *name, int len, int mode, |
| struct inode **result) |
| { |
| struct nfs_sattr sattr; |
| struct nfs_fattr fattr; |
| struct nfs_fh fhandle; |
| int error; |
| |
| *result = NULL; |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_create: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| sattr.mode = mode; |
| sattr.uid = sattr.gid = sattr.size = (unsigned) -1; |
| sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1; |
| if ((error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir), |
| name, &sattr, &fhandle, &fattr))) { |
| iput(dir); |
| return error; |
| } |
| if (!(*result = nfs_fhget(dir->i_sb, &fhandle, &fattr))) { |
| iput(dir); |
| return -EACCES; |
| } |
| nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
| iput(dir); |
| return 0; |
| } |
| |
| static int nfs_mknod(struct inode *dir, const char *name, int len, |
| int mode, int rdev) |
| { |
| struct nfs_sattr sattr; |
| struct nfs_fattr fattr; |
| struct nfs_fh fhandle; |
| int error; |
| |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_mknod: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| sattr.mode = mode; |
| sattr.uid = sattr.gid = (unsigned) -1; |
| if (S_ISCHR(mode) || S_ISBLK(mode)) |
| sattr.size = rdev; /* get out your barf bag */ |
| else |
| sattr.size = (unsigned) -1; |
| sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1; |
| error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir), |
| name, &sattr, &fhandle, &fattr); |
| if (!error) |
| nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_mkdir(struct inode *dir, const char *name, int len, int mode) |
| { |
| struct nfs_sattr sattr; |
| struct nfs_fattr fattr; |
| struct nfs_fh fhandle; |
| int error; |
| |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_mkdir: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| sattr.mode = mode; |
| sattr.uid = sattr.gid = sattr.size = (unsigned) -1; |
| sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1; |
| error = nfs_proc_mkdir(NFS_SERVER(dir), NFS_FH(dir), |
| name, &sattr, &fhandle, &fattr); |
| if (!error) |
| nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_rmdir(struct inode *dir, const char *name, int len) |
| { |
| int error; |
| |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_rmdir: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| error = nfs_proc_rmdir(NFS_SERVER(dir), NFS_FH(dir), name); |
| if (!error) |
| nfs_lookup_cache_remove(dir, NULL, name); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_unlink(struct inode *dir, const char *name, int len) |
| { |
| int error; |
| |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_unlink: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), name); |
| if (!error) |
| nfs_lookup_cache_remove(dir, NULL, name); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_symlink(struct inode *dir, const char *name, int len, |
| const char *symname) |
| { |
| struct nfs_sattr sattr; |
| int error; |
| |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_symlink: inode is NULL or not a directory\n"); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| if (strlen(symname) > NFS_MAXPATHLEN) { |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| sattr.mode = S_IFLNK | 0777; /* SunOS 4.1.2 crashes without this! */ |
| sattr.uid = sattr.gid = sattr.size = (unsigned) -1; |
| sattr.atime.seconds = sattr.mtime.seconds = (unsigned) -1; |
| error = nfs_proc_symlink(NFS_SERVER(dir), NFS_FH(dir), |
| name, symname, &sattr); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_link(struct inode *oldinode, struct inode *dir, |
| const char *name, int len) |
| { |
| int error; |
| |
| if (!oldinode) { |
| printk("nfs_link: old inode is NULL\n"); |
| iput(oldinode); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (!dir || !S_ISDIR(dir->i_mode)) { |
| printk("nfs_link: dir is NULL or not a directory\n"); |
| iput(oldinode); |
| iput(dir); |
| return -ENOENT; |
| } |
| if (len > NFS_MAXNAMLEN) { |
| iput(oldinode); |
| iput(dir); |
| return -ENAMETOOLONG; |
| } |
| error = nfs_proc_link(NFS_SERVER(oldinode), NFS_FH(oldinode), |
| NFS_FH(dir), name); |
| if (!error) |
| nfs_lookup_cache_remove(dir, oldinode, NULL); |
| iput(oldinode); |
| iput(dir); |
| return error; |
| } |
| |
| static int nfs_rename(struct inode *old_dir, const char *old_name, int old_len, |
| struct inode *new_dir, const char *new_name, int new_len) |
| { |
| int error; |
| |
| if (!old_dir || !S_ISDIR(old_dir->i_mode)) { |
| printk("nfs_rename: old inode is NULL or not a directory\n"); |
| iput(old_dir); |
| iput(new_dir); |
| return -ENOENT; |
| } |
| if (!new_dir || !S_ISDIR(new_dir->i_mode)) { |
| printk("nfs_rename: new inode is NULL or not a directory\n"); |
| iput(old_dir); |
| iput(new_dir); |
| return -ENOENT; |
| } |
| if (old_len > NFS_MAXNAMLEN || new_len > NFS_MAXNAMLEN) { |
| iput(old_dir); |
| iput(new_dir); |
| return -ENAMETOOLONG; |
| } |
| error = nfs_proc_rename(NFS_SERVER(old_dir), |
| NFS_FH(old_dir), old_name, |
| NFS_FH(new_dir), new_name); |
| if (!error) { |
| nfs_lookup_cache_remove(old_dir, NULL, old_name); |
| nfs_lookup_cache_remove(new_dir, NULL, new_name); |
| } |
| iput(old_dir); |
| iput(new_dir); |
| return error; |
| } |
| |
| /* |
| * Many nfs protocol calls return the new file attributes after |
| * an operation. Here we update the inode to reflect the state |
| * of the server's inode. |
| */ |
| |
| void nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr) |
| { |
| int was_empty; |
| |
| if (!inode || !fattr) { |
| printk("nfs_refresh_inode: inode or fattr is NULL\n"); |
| return; |
| } |
| if (inode->i_ino != fattr->fileid) { |
| printk("nfs_refresh_inode: inode number mismatch\n"); |
| return; |
| } |
| was_empty = inode->i_mode == 0; |
| inode->i_mode = fattr->mode; |
| inode->i_nlink = fattr->nlink; |
| inode->i_uid = fattr->uid; |
| inode->i_gid = fattr->gid; |
| inode->i_size = fattr->size; |
| inode->i_blksize = fattr->blocksize; |
| if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) |
| inode->i_rdev = fattr->rdev; |
| else |
| inode->i_rdev = 0; |
| inode->i_blocks = fattr->blocks; |
| inode->i_atime = fattr->atime.seconds; |
| inode->i_mtime = fattr->mtime.seconds; |
| inode->i_ctime = fattr->ctime.seconds; |
| if (was_empty) { |
| if (S_ISREG(inode->i_mode)) |
| inode->i_op = &nfs_file_inode_operations; |
| else if (S_ISDIR(inode->i_mode)) |
| inode->i_op = &nfs_dir_inode_operations; |
| else if (S_ISLNK(inode->i_mode)) |
| inode->i_op = &nfs_symlink_inode_operations; |
| else if (S_ISCHR(inode->i_mode)) |
| inode->i_op = &chrdev_inode_operations; |
| else if (S_ISBLK(inode->i_mode)) |
| inode->i_op = &blkdev_inode_operations; |
| else if (S_ISFIFO(inode->i_mode)) |
| init_fifo(inode); |
| else |
| inode->i_op = NULL; |
| } |
| nfs_lookup_cache_refresh(inode, fattr); |
| } |
| |