diff options
Diffstat (limited to 'fs/nfs')
-rw-r--r-- | fs/nfs/Makefile | 31 | ||||
-rw-r--r-- | fs/nfs/dir.c | 609 | ||||
-rw-r--r-- | fs/nfs/file.c | 237 | ||||
-rw-r--r-- | fs/nfs/inode.c | 240 | ||||
-rw-r--r-- | fs/nfs/mmap.c | 103 | ||||
-rw-r--r-- | fs/nfs/proc.c | 931 | ||||
-rw-r--r-- | fs/nfs/sock.c | 242 | ||||
-rw-r--r-- | fs/nfs/symlink.c | 116 |
8 files changed, 2509 insertions, 0 deletions
diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile new file mode 100644 index 000000000..8610c95b1 --- /dev/null +++ b/fs/nfs/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the linux nfs-filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +.c.s: + $(CC) $(CFLAGS) -S $< +.c.o: + $(CC) $(CFLAGS) -c $< +.s.o: + $(AS) -o $*.o $< + +OBJS= proc.o sock.o inode.o file.o dir.o \ + symlink.o mmap.o + +nfs.o: $(OBJS) + $(LD) -r -o nfs.o $(OBJS) + +dep: + $(CPP) -M *.c > .depend + +# +# include a dependency file if one exists +# +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c new file mode 100644 index 000000000..62a3e0821 --- /dev/null +++ b/fs/nfs/dir.c @@ -0,0 +1,609 @@ +/* + * 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/malloc.h> +#include <linux/mm.h> + +#include <asm/segment.h> /* for fs functions */ + +#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de))) +#define ROUND_UP(x) (((x)+3) & ~3) + +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 ROUND_UP(NAME_OFFSET(dirent)+i+1); + } + 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 | S_IRWXUGO; /* 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); +} + diff --git a/fs/nfs/file.c b/fs/nfs/file.c new file mode 100644 index 000000000..e71d29483 --- /dev/null +++ b/fs/nfs/file.c @@ -0,0 +1,237 @@ +/* + * linux/fs/nfs/file.c + * + * Copyright (C) 1992 Rick Sladkey + * + * Changes Copyright (C) 1994 by Florian La Roche + * - Do not copy data too often around in the kernel. + * - In nfs_file_read the return value of kmalloc wasn't checked. + * - Put in a better version of read look-ahead buffering. Original idea + * and implementation by Wai S Kok elekokws@ee.nus.sg. + * + * Expire cache on write to a file by Wai S Kok (Oct 1994). + * + * nfs regular file handling functions + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/mm.h> +#include <linux/nfs_fs.h> +#include <linux/malloc.h> + +static int nfs_file_read(struct inode *, struct file *, char *, int); +static int nfs_file_write(struct inode *, struct file *, char *, int); +static int nfs_fsync(struct inode *, struct file *); + +static struct file_operations nfs_file_operations = { + NULL, /* lseek - default */ + nfs_file_read, /* read */ + nfs_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + nfs_mmap, /* mmap */ + NULL, /* no special open is needed */ + NULL, /* release */ + nfs_fsync, /* fsync */ +}; + +struct inode_operations nfs_file_inode_operations = { + &nfs_file_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + NULL /* truncate */ +}; + +/* Once data is inserted, it can only be deleted, if (in_use==0). */ +struct read_cache { + int in_use; /* currently in use? */ + unsigned long inode_num; /* inode number */ + off_t file_pos; /* file position */ + int len; /* size of data */ + unsigned long time; /* time, this entry was inserted */ + char * buf; /* data */ + int buf_size; /* size of buffer */ +}; + +#define READ_CACHE_SIZE 5 +#define EXPIRE_CACHE (HZ * 3) /* keep no longer than 3 seconds */ + +unsigned long num_requests = 0; +unsigned long num_cache_hits = 0; + +static int tail = 0; /* next cache slot to replace */ + +static struct read_cache cache[READ_CACHE_SIZE] = { + { 0, 0, -1, 0, 0, NULL, 0 }, + { 0, 0, -1, 0, 0, NULL, 0 }, + { 0, 0, -1, 0, 0, NULL, 0 }, + { 0, 0, -1, 0, 0, NULL, 0 }, + { 0, 0, -1, 0, 0, NULL, 0 } }; + +static int nfs_fsync(struct inode *inode, struct file *file) +{ + return 0; +} + +static int nfs_file_read(struct inode *inode, struct file *file, char *buf, + int count) +{ + int result, hunk, i, n, fs; + struct nfs_fattr fattr; + char *data; + off_t pos; + + if (!inode) { + printk("nfs_file_read: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("nfs_file_read: read from non-file, mode %07o\n", + inode->i_mode); + return -EINVAL; + } + pos = file->f_pos; + if (pos + count > inode->i_size) + count = inode->i_size - pos; + if (count <= 0) + return 0; + ++num_requests; + cli(); + for (i = 0; i < READ_CACHE_SIZE; i++) + if ((cache[i].inode_num == inode->i_ino) + && (cache[i].file_pos <= pos) + && (cache[i].file_pos + cache[i].len >= pos + count) + && (abs(jiffies - cache[i].time) <= EXPIRE_CACHE)) + break; + if (i < READ_CACHE_SIZE) { + ++cache[i].in_use; + sti(); + ++num_cache_hits; + memcpy_tofs(buf, cache[i].buf + pos - cache[i].file_pos, count); + --cache[i].in_use; + file->f_pos += count; + return count; + } + sti(); + n = NFS_SERVER(inode)->rsize; + for (i = 0; i < count - n; i += n) { + result = nfs_proc_read(NFS_SERVER(inode), NFS_FH(inode), + pos, n, buf, &fattr, 1); + if (result < 0) + return result; + pos += result; + buf += result; + if (result < n) { + file->f_pos = pos; + nfs_refresh_inode(inode, &fattr); + return i + result; + } + } + fs = 0; + if (!(data = (char *)kmalloc(n, GFP_KERNEL))) { + data = buf; + fs = 1; + } + result = nfs_proc_read(NFS_SERVER(inode), NFS_FH(inode), + pos, n, data, &fattr, fs); + if (result < 0) { + if (!fs) + kfree_s(data, n); + return result; + } + hunk = count - i; + if (result < hunk) + hunk = result; + if (fs) { + file->f_pos = pos + hunk; + nfs_refresh_inode(inode, &fattr); + return i + hunk; + } + memcpy_tofs(buf, data, hunk); + file->f_pos = pos + hunk; + nfs_refresh_inode(inode, &fattr); + cli(); + if (cache[tail].in_use == 0) { + if (cache[tail].buf) + kfree_s(cache[tail].buf, cache[tail].buf_size); + cache[tail].buf = data; + cache[tail].buf_size = n; + cache[tail].inode_num = inode->i_ino; + cache[tail].file_pos = pos; + cache[tail].len = result; + cache[tail].time = jiffies; + if (++tail >= READ_CACHE_SIZE) + tail = 0; + } else + kfree_s(data, n); + sti(); + return i + hunk; +} + +static int nfs_file_write(struct inode *inode, struct file *file, char *buf, + int count) +{ + int result, hunk, i, n, pos; + struct nfs_fattr fattr; + + if (!inode) { + printk("nfs_file_write: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("nfs_file_write: write to non-file, mode %07o\n", + inode->i_mode); + return -EINVAL; + } + if (count <= 0) + return 0; + + cli(); + /* If hit, cache is dirty and must be expired. */ + for (i = 0; i < READ_CACHE_SIZE; i++) + if(cache[i].inode_num == inode->i_ino) + cache[i].time -= EXPIRE_CACHE; + sti(); + + pos = file->f_pos; + if (file->f_flags & O_APPEND) + pos = inode->i_size; + n = NFS_SERVER(inode)->wsize; + for (i = 0; i < count; i += n) { + hunk = count - i; + if (hunk >= n) + hunk = n; + result = nfs_proc_write(NFS_SERVER(inode), NFS_FH(inode), + pos, hunk, buf, &fattr); + if (result < 0) + return result; + pos += hunk; + buf += hunk; + if (hunk < n) { + i += hunk; + break; + } + } + file->f_pos = pos; + nfs_refresh_inode(inode, &fattr); + return i; +} + diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c new file mode 100644 index 000000000..17f2cb6f0 --- /dev/null +++ b/fs/nfs/inode.c @@ -0,0 +1,240 @@ +/* + * linux/fs/nfs/inode.c + * + * Copyright (C) 1992 Rick Sladkey + * + * nfs inode and superblock handling functions + */ + +#include <asm/system.h> +#include <asm/segment.h> + +#include <linux/sched.h> +#include <linux/nfs_fs.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/locks.h> + +extern int close_fp(struct file *filp, unsigned int fd); + +static int nfs_notify_change(struct inode *, struct iattr *); +static void nfs_put_inode(struct inode *); +static void nfs_put_super(struct super_block *); +static void nfs_statfs(struct super_block *, struct statfs *); + +static struct super_operations nfs_sops = { + NULL, /* read inode */ + nfs_notify_change, /* notify change */ + NULL, /* write inode */ + nfs_put_inode, /* put inode */ + nfs_put_super, /* put superblock */ + NULL, /* write superblock */ + nfs_statfs, /* stat filesystem */ + NULL +}; + +static void nfs_put_inode(struct inode * inode) +{ + clear_inode(inode); +} + +void nfs_put_super(struct super_block *sb) +{ + /* No locks should be open on this, so 0 should be safe as a fd. */ + close_fp(sb->u.nfs_sb.s_server.file, 0); + lock_super(sb); + sb->s_dev = 0; + unlock_super(sb); +} + +/* + * The way this works is that the mount process passes a structure + * in the data argument which contains an open socket to the NFS + * server and the root file handle obtained from the server's mount + * daemon. We stash theses away in the private superblock fields. + * Later we can add other mount parameters like caching values. + */ + +struct super_block *nfs_read_super(struct super_block *sb, void *raw_data, + int silent) +{ + struct nfs_mount_data *data = (struct nfs_mount_data *) raw_data; + struct nfs_server *server; + unsigned int fd; + struct file *filp; + dev_t dev = sb->s_dev; + + if (!data) { + printk("nfs_read_super: missing data argument\n"); + sb->s_dev = 0; + return NULL; + } + fd = data->fd; + if (data->version != NFS_MOUNT_VERSION) { + printk("nfs warning: mount version %s than kernel\n", + data->version < NFS_MOUNT_VERSION ? "older" : "newer"); + } + if (fd >= NR_OPEN || !(filp = current->files->fd[fd])) { + printk("nfs_read_super: invalid file descriptor\n"); + sb->s_dev = 0; + return NULL; + } + if (!S_ISSOCK(filp->f_inode->i_mode)) { + printk("nfs_read_super: not a socket\n"); + sb->s_dev = 0; + return NULL; + } + filp->f_count++; + lock_super(sb); + sb->s_blocksize = 1024; /* XXX */ + sb->s_blocksize_bits = 10; + sb->s_magic = NFS_SUPER_MAGIC; + sb->s_dev = dev; + sb->s_op = &nfs_sops; + server = &sb->u.nfs_sb.s_server; + server->file = filp; + server->lock = 0; + server->wait = NULL; + server->flags = data->flags; + server->rsize = data->rsize; + if (server->rsize <= 0) + server->rsize = NFS_DEF_FILE_IO_BUFFER_SIZE; + else if (server->rsize >= NFS_MAX_FILE_IO_BUFFER_SIZE) + server->rsize = NFS_MAX_FILE_IO_BUFFER_SIZE; + server->wsize = data->wsize; + if (server->wsize <= 0) + server->wsize = NFS_DEF_FILE_IO_BUFFER_SIZE; + else if (server->wsize >= NFS_MAX_FILE_IO_BUFFER_SIZE) + server->wsize = NFS_MAX_FILE_IO_BUFFER_SIZE; + server->timeo = data->timeo*HZ/10; + server->retrans = data->retrans; + server->acregmin = data->acregmin*HZ; + server->acregmax = data->acregmax*HZ; + server->acdirmin = data->acdirmin*HZ; + server->acdirmax = data->acdirmax*HZ; + strcpy(server->hostname, data->hostname); + sb->u.nfs_sb.s_root = data->root; + unlock_super(sb); + if (!(sb->s_mounted = nfs_fhget(sb, &data->root, NULL))) { + sb->s_dev = 0; + printk("nfs_read_super: get root inode failed\n"); + return NULL; + } + return sb; +} + +void nfs_statfs(struct super_block *sb, struct statfs *buf) +{ + int error; + struct nfs_fsinfo res; + + put_fs_long(NFS_SUPER_MAGIC, &buf->f_type); + error = nfs_proc_statfs(&sb->u.nfs_sb.s_server, &sb->u.nfs_sb.s_root, + &res); + if (error) { + printk("nfs_statfs: statfs error = %d\n", -error); + res.bsize = res.blocks = res.bfree = res.bavail = 0; + } + put_fs_long(res.bsize, &buf->f_bsize); + put_fs_long(res.blocks, &buf->f_blocks); + put_fs_long(res.bfree, &buf->f_bfree); + put_fs_long(res.bavail, &buf->f_bavail); + put_fs_long(0, &buf->f_files); + put_fs_long(0, &buf->f_ffree); + /* We should really try to interrogate the remote server to find + it's maximum name length here */ + put_fs_long(NAME_MAX, &buf->f_namelen); +} + +/* + * This is our own version of iget that looks up inodes by file handle + * instead of inode number. We use this technique instead of using + * the vfs read_inode function because there is no way to pass the + * file handle or current attributes into the read_inode function. + * We just have to be careful not to subvert iget's special handling + * of mount points. + */ + +struct inode *nfs_fhget(struct super_block *sb, struct nfs_fh *fhandle, + struct nfs_fattr *fattr) +{ + struct nfs_fattr newfattr; + int error; + struct inode *inode; + + if (!sb) { + printk("nfs_fhget: super block is NULL\n"); + return NULL; + } + if (!fattr) { + error = nfs_proc_getattr(&sb->u.nfs_sb.s_server, fhandle, + &newfattr); + if (error) { + printk("nfs_fhget: getattr error = %d\n", -error); + return NULL; + } + fattr = &newfattr; + } + if (!(inode = iget(sb, fattr->fileid))) { + printk("nfs_fhget: iget failed\n"); + return NULL; + } + if (inode->i_dev == sb->s_dev) { + if (inode->i_ino != fattr->fileid) { + printk("nfs_fhget: unexpected inode from iget\n"); + return inode; + } + *NFS_FH(inode) = *fhandle; + nfs_refresh_inode(inode, fattr); + } + return inode; +} + +int nfs_notify_change(struct inode *inode, struct iattr *attr) +{ + struct nfs_sattr sattr; + struct nfs_fattr fattr; + int error; + + if (attr->ia_valid & ATTR_MODE) + sattr.mode = attr->ia_mode; + else + sattr.mode = (unsigned) -1; + + if (attr->ia_valid & ATTR_UID) + sattr.uid = attr->ia_uid; + else + sattr.uid = (unsigned) -1; + + if (attr->ia_valid & ATTR_GID) + sattr.gid = attr->ia_gid; + else + sattr.gid = (unsigned) -1; + + if (attr->ia_valid & ATTR_SIZE) + sattr.size = S_ISREG(inode->i_mode) ? attr->ia_size : -1; + else + sattr.size = (unsigned) -1; + + if (attr->ia_valid & ATTR_MTIME) { + sattr.mtime.seconds = attr->ia_mtime; + sattr.mtime.useconds = 0; + } else + sattr.mtime.seconds = sattr.mtime.useconds = (unsigned) -1; + + if (attr->ia_valid & ATTR_ATIME) { + sattr.atime.seconds = attr->ia_atime; + sattr.atime.useconds = 0; + } else + sattr.atime.seconds = sattr.atime.useconds = (unsigned) -1; + + error = nfs_proc_setattr(NFS_SERVER(inode), NFS_FH(inode), + &sattr, &fattr); + if (!error) + nfs_refresh_inode(inode, &fattr); + inode->i_dirt = 0; + return error; +} diff --git a/fs/nfs/mmap.c b/fs/nfs/mmap.c new file mode 100644 index 000000000..811176a69 --- /dev/null +++ b/fs/nfs/mmap.c @@ -0,0 +1,103 @@ +/* + * fs/nfs/mmap.c by Jon Tombs 15 Aug 1993 + * + * This code is from + * linux/mm/mmap.c which was written by obz, Linus and Eric + * and + * linux/mm/memory.c by Linus Torvalds and others + * + * Copyright (C) 1993 + * + */ +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/shm.h> +#include <linux/errno.h> +#include <linux/mman.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/nfs_fs.h> + +#include <asm/segment.h> +#include <asm/system.h> + +/* + * Fill in the supplied page for mmap + */ +static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area, + unsigned long address, unsigned long page, int no_share) +{ + struct inode * inode = area->vm_inode; + unsigned int clear; + unsigned long tmp; + int n; + int i; + int pos; + struct nfs_fattr fattr; + + address &= PAGE_MASK; + pos = address - area->vm_start + area->vm_offset; + + clear = 0; + if (address + PAGE_SIZE > area->vm_end) { + clear = address + PAGE_SIZE - area->vm_end; + } + + n = NFS_SERVER(inode)->rsize; /* what we can read in one go */ + + for (i = 0; i < (PAGE_SIZE - clear); i += n) { + int hunk, result; + + hunk = PAGE_SIZE - i; + if (hunk > n) + hunk = n; + result = nfs_proc_read(NFS_SERVER(inode), NFS_FH(inode), + pos, hunk, (char *) (page + i), &fattr, 0); + if (result < 0) + break; + pos += result; + if (result < n) { + i += result; + break; + } + } + +#ifdef doweneedthishere + nfs_refresh_inode(inode, &fattr); +#endif + + tmp = page + PAGE_SIZE; + while (clear--) { + *(char *)--tmp = 0; + } + return page; +} +struct vm_operations_struct nfs_file_mmap = { + NULL, /* open */ + NULL, /* close */ + nfs_file_mmap_nopage, /* nopage */ + NULL, /* wppage */ + NULL, /* share */ + NULL, /* unmap */ +}; + + +/* This is used for a general mmap of a nfs file */ +int nfs_mmap(struct inode * inode, struct file * file, struct vm_area_struct * vma) +{ + if (vma->vm_page_prot & PAGE_RW) /* only PAGE_COW or read-only supported now */ + return -EINVAL; + if (!inode->i_sb || !S_ISREG(inode->i_mode)) + return -EACCES; + if (!IS_RDONLY(inode)) { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + + vma->vm_inode = inode; + inode->i_count++; + vma->vm_ops = &nfs_file_mmap; + return 0; +} diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c new file mode 100644 index 000000000..fd01dad99 --- /dev/null +++ b/fs/nfs/proc.c @@ -0,0 +1,931 @@ +/* + * linux/fs/nfs/proc.c + * + * Copyright (C) 1992, 1993, 1994 Rick Sladkey + * + * OS-independent nfs remote procedure call functions + * + * Tuned by Alan Cox <A.Cox@swansea.ac.uk> for >3K buffers + * so at last we can have decent(ish) throughput off a + * Sun server. + * + * Coding optimized and cleaned up by Florian La Roche. + * Note: Error returns are optimized for NFS_OK, which isn't translated via + * nfs_stat_to_errno(), but happens to be already the right return code. + * + * FixMe: We ought to define a sensible small max size for + * things like getattr that are tiny packets and use the + * old get_free_page stuff with it. + * + * Also, the code currently doesn't check the size of the packet, when + * it decodes the packet. + * + * Feel free to fix it and mail me the diffs if it worries you. + */ + +/* + * Defining NFS_PROC_DEBUG causes a lookup of a file named + * "xyzzy" to toggle debugging. Just cd to an NFS-mounted + * filesystem and type 'ls xyzzy' to turn on debugging. + */ + +#if 0 +#define NFS_PROC_DEBUG +#endif + +#include <linux/config.h> +#include <linux/param.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/nfs_fs.h> +#include <linux/utsname.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/in.h> +#include <asm/segment.h> + +#ifdef NFS_PROC_DEBUG + +static int proc_debug = 0; +#define PRINTK(format, args...) \ + do { \ + if (proc_debug) \ + printk(format , ## args); \ + } while (0) + +#else /* !NFS_PROC_DEBUG */ + +#define PRINTK(format, args...) do ; while (0) + +#endif /* !NFS_PROC_DEBUG */ + +/* Mapping from NFS error code to "errno" error code. */ +#define errno_NFSERR_IO EIO + +static int *nfs_rpc_header(int *p, int procedure, int ruid); +static int *nfs_rpc_verify(int *p); +static int nfs_stat_to_errno(int stat); + +/* + * Our memory allocation and release functions. + */ + +#define NFS_SLACK_SPACE 1024 /* Total overkill */ +/* !!! Be careful, this constant is now also used in sock.c... + We should easily convert to not using it anymore for most cases... */ + +static inline int *nfs_rpc_alloc(int size) +{ + int *i; + + while (!(i = (int *)kmalloc(size+NFS_SLACK_SPACE,GFP_NFS))) { + schedule(); + } + return i; +} + +static inline void nfs_rpc_free(int *p) +{ + kfree((void *)p); +} + +/* + * Here are a bunch of xdr encode/decode functions that convert + * between machine dependent and xdr data formats. + */ + +#define QUADLEN(len) (((len) + 3) >> 2) + +static inline int *xdr_encode_fhandle(int *p, struct nfs_fh *fhandle) +{ + *((struct nfs_fh *) p) = *fhandle; + return p + QUADLEN(sizeof(*fhandle)); +} + +static inline int *xdr_decode_fhandle(int *p, struct nfs_fh *fhandle) +{ + *fhandle = *((struct nfs_fh *) p); + return p + QUADLEN(sizeof(*fhandle)); +} + +static inline int *xdr_encode_string(int *p, const char *string) +{ + int len = strlen(string); + int quadlen = QUADLEN(len); + + p[quadlen] = 0; + *p++ = htonl(len); + memcpy(p, string, len); + return p + quadlen; +} + +static inline int *xdr_decode_string(int *p, char *string, unsigned int maxlen) +{ + unsigned int len = ntohl(*p++); + if (len > maxlen) + return NULL; + memcpy(string, p, len); + string[len] = '\0'; + return p + QUADLEN(len); +} + +static inline int *xdr_decode_string2(int *p, char **string, unsigned int *len, + unsigned int maxlen) +{ + *len = ntohl(*p++); + if (*len > maxlen) + return NULL; + *string = (char *) p; + return p + QUADLEN(*len); +} + + +static inline int *xdr_encode_data(int *p, char *data, int len) +{ + int quadlen = QUADLEN(len); + + p[quadlen] = 0; + *p++ = htonl(len); + memcpy_fromfs(p, data, len); + return p + quadlen; +} + +static inline int *xdr_decode_data(int *p, char *data, int *lenp, int maxlen, + int fs) +{ + unsigned len = *lenp = ntohl(*p++); + if (len > maxlen) + return NULL; + if (fs) + memcpy_tofs(data, p, len); + else + memcpy(data, p, len); + return p + QUADLEN(len); +} + +static int *xdr_decode_fattr(int *p, struct nfs_fattr *fattr) +{ + fattr->type = (enum nfs_ftype) ntohl(*p++); + fattr->mode = ntohl(*p++); + fattr->nlink = ntohl(*p++); + fattr->uid = ntohl(*p++); + fattr->gid = ntohl(*p++); + fattr->size = ntohl(*p++); + fattr->blocksize = ntohl(*p++); + fattr->rdev = ntohl(*p++); + fattr->blocks = ntohl(*p++); + fattr->fsid = ntohl(*p++); + fattr->fileid = ntohl(*p++); + fattr->atime.seconds = ntohl(*p++); + fattr->atime.useconds = ntohl(*p++); + fattr->mtime.seconds = ntohl(*p++); + fattr->mtime.useconds = ntohl(*p++); + fattr->ctime.seconds = ntohl(*p++); + fattr->ctime.useconds = ntohl(*p++); + return p; +} + +static int *xdr_encode_sattr(int *p, struct nfs_sattr *sattr) +{ + *p++ = htonl(sattr->mode); + *p++ = htonl(sattr->uid); + *p++ = htonl(sattr->gid); + *p++ = htonl(sattr->size); + *p++ = htonl(sattr->atime.seconds); + *p++ = htonl(sattr->atime.useconds); + *p++ = htonl(sattr->mtime.seconds); + *p++ = htonl(sattr->mtime.useconds); + return p; +} + +static int *xdr_decode_entry(int *p, struct nfs_entry *entry) +{ + entry->fileid = ntohl(*p++); + if (!(p = xdr_decode_string(p, entry->name, NFS_MAXNAMLEN))) + return NULL; + entry->cookie = ntohl(*p++); + entry->eof = 0; + return p; +} + +static int *xdr_decode_fsinfo(int *p, struct nfs_fsinfo *res) +{ + res->tsize = ntohl(*p++); + res->bsize = ntohl(*p++); + res->blocks = ntohl(*p++); + res->bfree = ntohl(*p++); + res->bavail = ntohl(*p++); + return p; +} + +/* + * One function for each procedure in the NFS protocol. + */ + +int nfs_proc_getattr(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call getattr\n"); + if (!(p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_GETATTR, ruid); + p = xdr_encode_fhandle(p, fhandle); + if ((status = nfs_rpc_call(server, p0, p, server->rsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply getattr\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply getattr failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_setattr(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_sattr *sattr, struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call setattr\n"); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_SETATTR, ruid); + p = xdr_encode_fhandle(p, fhandle); + p = xdr_encode_sattr(p, sattr); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply setattr\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply setattr failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_lookup(struct nfs_server *server, struct nfs_fh *dir, const char *name, + struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call lookup %s\n", name); +#ifdef NFS_PROC_DEBUG + if (!strcmp(name, "xyzzy")) + proc_debug = 1 - proc_debug; +#endif + if (!(p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_LOOKUP, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + if ((status = nfs_rpc_call(server, p0, p, server->rsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fhandle(p, fhandle); + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply lookup\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply lookup failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_readlink(struct nfs_server *server, struct nfs_fh *fhandle, + int **p0, char **string, unsigned int *len, unsigned int maxlen) +{ + int *p; + int status, ruid = 0; + + PRINTK("NFS call readlink\n"); + if (!(*p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(*p0, NFSPROC_READLINK, ruid); + p = xdr_encode_fhandle(p, fhandle); + if ((status = nfs_rpc_call(server, *p0, p, server->rsize)) < 0) + return status; + if (!(p = nfs_rpc_verify(*p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + if (!(p = xdr_decode_string2(p, string, len, maxlen))) { + printk("nfs_proc_readlink: giant pathname\n"); + status = -errno_NFSERR_IO; + } + else /* status = 0, */ + PRINTK("NFS reply readlink\n"); + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply readlink failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + return status; +} + +int nfs_proc_read(struct nfs_server *server, struct nfs_fh *fhandle, + int offset, int count, char *data, struct nfs_fattr *fattr, int fs) +{ + int *p, *p0; + int status; + int ruid = 0; + int len; + + PRINTK("NFS call read %d @ %d\n", count, offset); + if (!(p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_READ, ruid); + p = xdr_encode_fhandle(p, fhandle); + *p++ = htonl(offset); + *p++ = htonl(count); + *p++ = htonl(count); /* traditional, could be any value */ + if ((status = nfs_rpc_call(server, p0, p, server->rsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fattr(p, fattr); + if (!(p = xdr_decode_data(p, data, &len, count, fs))) { + printk("nfs_proc_read: giant data size\n"); + status = -errno_NFSERR_IO; + } + else { + status = len; + PRINTK("NFS reply read %d\n", len); + } + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply read failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_write(struct nfs_server *server, struct nfs_fh *fhandle, + int offset, int count, char *data, struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call write %d @ %d\n", count, offset); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_WRITE, ruid); + p = xdr_encode_fhandle(p, fhandle); + *p++ = htonl(offset); /* traditional, could be any value */ + *p++ = htonl(offset); + *p++ = htonl(count); /* traditional, could be any value */ + p = xdr_encode_data(p, data, count); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply write\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply write failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_create(struct nfs_server *server, struct nfs_fh *dir, + const char *name, struct nfs_sattr *sattr, + struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call create %s\n", name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_CREATE, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + p = xdr_encode_sattr(p, sattr); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fhandle(p, fhandle); + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply create\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply create failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_remove(struct nfs_server *server, struct nfs_fh *dir, const char *name) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call remove %s\n", name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_REMOVE, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + PRINTK("NFS reply remove\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply remove failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_rename(struct nfs_server *server, + struct nfs_fh *old_dir, const char *old_name, + struct nfs_fh *new_dir, const char *new_name) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call rename %s -> %s\n", old_name, new_name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_RENAME, ruid); + p = xdr_encode_fhandle(p, old_dir); + p = xdr_encode_string(p, old_name); + p = xdr_encode_fhandle(p, new_dir); + p = xdr_encode_string(p, new_name); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + PRINTK("NFS reply rename\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply rename failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_link(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_fh *dir, const char *name) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call link %s\n", name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_LINK, ruid); + p = xdr_encode_fhandle(p, fhandle); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + PRINTK("NFS reply link\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply link failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_symlink(struct nfs_server *server, struct nfs_fh *dir, + const char *name, const char *path, struct nfs_sattr *sattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call symlink %s -> %s\n", name, path); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_SYMLINK, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + p = xdr_encode_string(p, path); + p = xdr_encode_sattr(p, sattr); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + PRINTK("NFS reply symlink\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply symlink failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_mkdir(struct nfs_server *server, struct nfs_fh *dir, + const char *name, struct nfs_sattr *sattr, + struct nfs_fh *fhandle, struct nfs_fattr *fattr) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call mkdir %s\n", name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_MKDIR, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + p = xdr_encode_sattr(p, sattr); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fhandle(p, fhandle); + p = xdr_decode_fattr(p, fattr); + PRINTK("NFS reply mkdir\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply mkdir failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_rmdir(struct nfs_server *server, struct nfs_fh *dir, const char *name) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call rmdir %s\n", name); + if (!(p0 = nfs_rpc_alloc(server->wsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_RMDIR, ruid); + p = xdr_encode_fhandle(p, dir); + p = xdr_encode_string(p, name); + if ((status = nfs_rpc_call(server, p0, p, server->wsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + PRINTK("NFS reply rmdir\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply rmdir failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_readdir(struct nfs_server *server, struct nfs_fh *fhandle, + int cookie, int count, struct nfs_entry *entry) +{ + int *p, *p0; + int status; + int ruid = 0; + int i; + int size; + int eof; + + PRINTK("NFS call readdir %d @ %d\n", count, cookie); + size = server->rsize; + if (!(p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_READDIR, ruid); + p = xdr_encode_fhandle(p, fhandle); + *p++ = htonl(cookie); + *p++ = htonl(size); + if ((status = nfs_rpc_call(server, p0, p, server->rsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + for (i = 0; i < count && *p++; i++) { + if (!(p = xdr_decode_entry(p, entry++))) + break; + } + if (!p) { + printk("nfs_proc_readdir: giant filename\n"); + status = -errno_NFSERR_IO; + } + else { + eof = (i == count && !*p++ && *p++) + || (i < count && *p++); + if (eof && i) + entry[-1].eof = 1; + PRINTK("NFS reply readdir %d %s\n", i, + eof ? "eof" : ""); + status = i; + } + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply readdir failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +int nfs_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle, + struct nfs_fsinfo *res) +{ + int *p, *p0; + int status; + int ruid = 0; + + PRINTK("NFS call statfs\n"); + if (!(p0 = nfs_rpc_alloc(server->rsize))) + return -EIO; +retry: + p = nfs_rpc_header(p0, NFSPROC_STATFS, ruid); + p = xdr_encode_fhandle(p, fhandle); + if ((status = nfs_rpc_call(server, p0, p, server->rsize)) < 0) { + nfs_rpc_free(p0); + return status; + } + if (!(p = nfs_rpc_verify(p0))) + status = -errno_NFSERR_IO; + else if ((status = ntohl(*p++)) == NFS_OK) { + p = xdr_decode_fsinfo(p, res); + PRINTK("NFS reply statfs\n"); + /* status = 0; */ + } + else { + if (!ruid && current->fsuid == 0 && current->uid != 0) { + ruid = 1; + goto retry; + } + PRINTK("NFS reply statfs failed = %d\n", status); + status = -nfs_stat_to_errno(status); + } + nfs_rpc_free(p0); + return status; +} + +/* + * Here are a few RPC-assist functions. + */ + +static int *nfs_rpc_header(int *p, int procedure, int ruid) +{ + int *p1, *p2; + int i; + static int xid = 0; + unsigned char *sys = (unsigned char *) system_utsname.nodename; + + if (xid == 0) { + xid = CURRENT_TIME; + xid ^= (sys[3]<<24) | (sys[2]<<16) | (sys[1]<<8) | sys[0]; + } + *p++ = htonl(++xid); + *p++ = htonl(RPC_CALL); + *p++ = htonl(RPC_VERSION); + *p++ = htonl(NFS_PROGRAM); + *p++ = htonl(NFS_VERSION); + *p++ = htonl(procedure); + *p++ = htonl(RPC_AUTH_UNIX); + p1 = p++; + *p++ = htonl(CURRENT_TIME); /* traditional, could be anything */ + p = xdr_encode_string(p, (char *) sys); + *p++ = htonl(ruid ? current->uid : current->fsuid); + *p++ = htonl(current->egid); + p2 = p++; + for (i = 0; i < 16 && i < NGROUPS && current->groups[i] != NOGROUP; i++) + *p++ = htonl(current->groups[i]); + *p2 = htonl(i); + *p1 = htonl((p - (p1 + 1)) << 2); + *p++ = htonl(RPC_AUTH_NULL); + *p++ = htonl(0); + return p; +} + +static int *nfs_rpc_verify(int *p) +{ + unsigned int n; + + p++; + if ((n = ntohl(*p++)) != RPC_REPLY) { + printk("nfs_rpc_verify: not an RPC reply: %d\n", n); + return NULL; + } + if ((n = ntohl(*p++)) != RPC_MSG_ACCEPTED) { + printk("nfs_rpc_verify: RPC call rejected: %d\n", n); + return NULL; + } + switch (n = ntohl(*p++)) { + case RPC_AUTH_NULL: case RPC_AUTH_UNIX: case RPC_AUTH_SHORT: + break; + default: + printk("nfs_rpc_verify: bad RPC authentication type: %d\n", n); + return NULL; + } + if ((n = ntohl(*p++)) > 400) { + printk("nfs_rpc_verify: giant auth size\n"); + return NULL; + } + p += QUADLEN(n); + if ((n = ntohl(*p++)) != RPC_SUCCESS) { + printk("nfs_rpc_verify: RPC call failed: %d\n", n); + return NULL; + } + return p; +} + +/* + * We need to translate between nfs status return values and + * the local errno values which may not be the same. + */ + +static struct { + int stat; + int errno; +} nfs_errtbl[] = { + { NFS_OK, 0 }, + { NFSERR_PERM, EPERM }, + { NFSERR_NOENT, ENOENT }, + { NFSERR_IO, errno_NFSERR_IO }, + { NFSERR_NXIO, ENXIO }, + { NFSERR_ACCES, EACCES }, + { NFSERR_EXIST, EEXIST }, + { NFSERR_NODEV, ENODEV }, + { NFSERR_NOTDIR, ENOTDIR }, + { NFSERR_ISDIR, EISDIR }, + { NFSERR_INVAL, EINVAL }, + { NFSERR_FBIG, EFBIG }, + { NFSERR_NOSPC, ENOSPC }, + { NFSERR_ROFS, EROFS }, + { NFSERR_NAMETOOLONG, ENAMETOOLONG }, + { NFSERR_NOTEMPTY, ENOTEMPTY }, + { NFSERR_DQUOT, EDQUOT }, + { NFSERR_STALE, ESTALE }, +#ifdef EWFLUSH + { NFSERR_WFLUSH, EWFLUSH }, +#endif + { -1, EIO } +}; + +static int nfs_stat_to_errno(int stat) +{ + int i; + + for (i = 0; nfs_errtbl[i].stat != -1; i++) { + if (nfs_errtbl[i].stat == stat) + return nfs_errtbl[i].errno; + } + printk("nfs_stat_to_errno: bad nfs status return value: %d\n", stat); + return nfs_errtbl[i].errno; +} + diff --git a/fs/nfs/sock.c b/fs/nfs/sock.c new file mode 100644 index 000000000..2455d938a --- /dev/null +++ b/fs/nfs/sock.c @@ -0,0 +1,242 @@ +/* + * linux/fs/nfs/sock.c + * + * Copyright (C) 1992, 1993 Rick Sladkey + * + * low-level nfs remote procedure call interface + * + * FIXES + * + * 2/7/94 James Bottomley and Jon Peatfield DAMTP, Cambridge University + * + * An xid mismatch no longer causes the request to be trashed. + * + * Peter Eriksson - incorrect XID used to confuse Linux + * Florian La Roche - use the correct max size, if reading a packet and + * also verify, if the whole packet has been read... + * more checks should be done in proc.c... + * + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/nfs_fs.h> +#include <linux/errno.h> +#include <linux/socket.h> +#include <linux/fcntl.h> +#include <asm/segment.h> +#include <linux/in.h> +#include <linux/net.h> +#include <linux/mm.h> + +/* JEJB/JSP 2/7/94 + * this must match the value of NFS_SLACK_SPACE in linux/fs/nfs/proc.c + * ***FIXME*** should probably put this in nfs_fs.h */ +#define NFS_SLACK_SPACE 1024 + + +extern struct socket *socki_lookup(struct inode *inode); + +#define _S(nr) (1<<((nr)-1)) + +/* + * We violate some modularity principles here by poking around + * in some socket internals. Besides having to call socket + * functions from kernel-space instead of user space, the socket + * interface does not lend itself well to being cleanly called + * without a file descriptor. Since the nfs calls can run on + * behalf of any process, the superblock maintains a file pointer + * to the server socket. + */ + +static int do_nfs_rpc_call(struct nfs_server *server, int *start, int *end, int size) +{ + struct file *file; + struct inode *inode; + struct socket *sock; + unsigned short fs; + int result; + int xid; + int len; + select_table wait_table; + struct select_table_entry entry; + int (*select) (struct inode *, struct file *, int, select_table *); + int init_timeout, max_timeout; + int timeout; + int retrans; + int major_timeout_seen; + char *server_name; + int n; + int addrlen; + unsigned long old_mask; + /* JEJB/JSP 2/7/94 + * This is for a 4 byte recv of the xid only */ + int recv_xid; + + xid = start[0]; + len = ((char *) end) - ((char *) start); + file = server->file; + inode = file->f_inode; + select = file->f_op->select; + sock = socki_lookup(inode); + if (!sock) { + printk("nfs_rpc_call: socki_lookup failed\n"); + return -EBADF; + } + init_timeout = server->timeo; + max_timeout = NFS_MAX_RPC_TIMEOUT*HZ/10; + retrans = server->retrans; + major_timeout_seen = 0; + server_name = server->hostname; + old_mask = current->blocked; + current->blocked |= ~(_S(SIGKILL) +#if 0 + | _S(SIGSTOP) +#endif + | ((server->flags & NFS_MOUNT_INTR) + ? ((current->sigaction[SIGINT - 1].sa_handler == SIG_DFL + ? _S(SIGINT) : 0) + | (current->sigaction[SIGQUIT - 1].sa_handler == SIG_DFL + ? _S(SIGQUIT) : 0)) + : 0)); + fs = get_fs(); + set_fs(get_ds()); + for (n = 0, timeout = init_timeout; ; n++, timeout <<= 1) { + result = sock->ops->send(sock, (void *) start, len, 0, 0); + if (result < 0) { + printk("nfs_rpc_call: send error = %d\n", result); + break; + } + re_select: + wait_table.nr = 0; + wait_table.entry = &entry; + current->state = TASK_INTERRUPTIBLE; + if (!select(inode, file, SEL_IN, &wait_table) + && !select(inode, file, SEL_IN, NULL)) { + if (timeout > max_timeout) { + /* JEJB/JSP 2/7/94 + * This is useful to see if the system is + * hanging */ + printk("NFS max timeout reached on %s\n", + server_name); + timeout = max_timeout; + } + current->timeout = jiffies + timeout; + schedule(); + remove_wait_queue(entry.wait_address, &entry.wait); + current->state = TASK_RUNNING; + if (current->signal & ~current->blocked) { + current->timeout = 0; + result = -ERESTARTSYS; + break; + } + if (!current->timeout) { + if (n < retrans) + continue; + if (server->flags & NFS_MOUNT_SOFT) { + printk("NFS server %s not responding, " + "timed out\n", server_name); + result = -EIO; + break; + } + n = 0; + timeout = init_timeout; + init_timeout <<= 1; + if (!major_timeout_seen) { + printk("NFS server %s not responding, " + "still trying\n", server_name); + } + major_timeout_seen = 1; + continue; + } + else + current->timeout = 0; + } + else if (wait_table.nr) + remove_wait_queue(entry.wait_address, &entry.wait); + current->state = TASK_RUNNING; + addrlen = 0; + /* JEJB/JSP 2/7/94 + * Get the xid from the next packet using a peek, so keep it + * on the recv queue. If it is wrong, it will be some reply + * we don't now need, so discard it */ + result = sock->ops->recvfrom(sock, (void *)&recv_xid, + sizeof(recv_xid), 1, MSG_PEEK, + NULL, &addrlen); + if (result < 0) { + if (result == -EAGAIN) { +#if 0 + printk("nfs_rpc_call: bad select ready\n"); +#endif + goto re_select; + } + if (result == -ECONNREFUSED) { +#if 0 + printk("nfs_rpc_call: server playing coy\n"); +#endif + goto re_select; + } + if (result != -ERESTARTSYS) { + printk("nfs_rpc_call: recv error = %d\n", + -result); + } + break; + } + if (recv_xid == xid) { + if (major_timeout_seen) + printk("NFS server %s OK\n", server_name); + break; + } + /* JEJB/JSP 2/7/94 + * we have xid mismatch, so discard the packet and start + * again. What a hack! but I can't call recvfrom with + * a null buffer yet. */ + (void)sock->ops->recvfrom(sock, (void *)&recv_xid, + sizeof(recv_xid), 1, 0, NULL, + &addrlen); +#if 0 + printk("nfs_rpc_call: XID mismatch\n"); +#endif + goto re_select; + } + /* JEJB/JSP 2/7/94 + * + * we have the correct xid, so read into the correct place and + * return it + * + */ + result=sock->ops->recvfrom(sock, (void *)start, + size + 1024, 1, 0, NULL, + /* Here is NFS_SLACK_SPACE..., hack */ + &addrlen); + if (result < 0) { + printk("NFS: notice message: result=%d\n", result); + } else if (result < addrlen) { + printk("NFS: just caught a too small read memory size..., email to NET channel\n"); + printk("NFS: result=%d,addrlen=%d\n", result, addrlen); + result = -EIO; + } + current->blocked = old_mask; + set_fs(fs); + return result; +} + +/* + * For now we lock out other simultaneous nfs calls for the same filesystem + * because we are single-threaded and don't want to get mismatched + * RPC replies. + */ + +int nfs_rpc_call(struct nfs_server *server, int *start, int *end, int size) +{ + int result; + + while (server->lock) + sleep_on(&server->wait); + server->lock = 1; + result = do_nfs_rpc_call(server, start, end, size); + server->lock = 0; + wake_up(&server->wait); + return result; +} + diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c new file mode 100644 index 000000000..4cbe631c6 --- /dev/null +++ b/fs/nfs/symlink.c @@ -0,0 +1,116 @@ +/* + * linux/fs/nfs/symlink.c + * + * Copyright (C) 1992 Rick Sladkey + * + * Optimization changes Copyright (C) 1994 Florian La Roche + * + * nfs symlink handling code + */ + +#include <asm/segment.h> + +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/nfs_fs.h> +#include <linux/stat.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/string.h> + +static int nfs_readlink(struct inode *, char *, int); +static int nfs_follow_link(struct inode *, struct inode *, int, int, + struct inode **); + +/* + * symlinks can't do much... + */ +struct inode_operations nfs_symlink_inode_operations = { + NULL, /* no file-operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + nfs_readlink, /* readlink */ + nfs_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + +static int nfs_follow_link(struct inode *dir, struct inode *inode, + int flag, int mode, struct inode **res_inode) +{ + int error, *mem; + unsigned int len; + char *res, *res2; + + *res_inode = NULL; + if (!dir) { + dir = current->fs->root; + dir->i_count++; + } + if (!inode) { + iput(dir); + return -ENOENT; + } + if (!S_ISLNK(inode->i_mode)) { + iput(dir); + *res_inode = inode; + return 0; + } + if (current->link_count > 5) { + iput(inode); + iput(dir); + return -ELOOP; + } + error = nfs_proc_readlink(NFS_SERVER(inode), NFS_FH(inode), &mem, + &res, &len, NFS_MAXPATHLEN); + if (error) { + iput(inode); + iput(dir); + kfree(mem); + return error; + } + while ((res2 = (char *) kmalloc(NFS_MAXPATHLEN + 1, GFP_NFS)) == NULL) { + schedule(); + } + memcpy(res2, res, len); + res2[len] = '\0'; + kfree(mem); + iput(inode); + current->link_count++; + error = open_namei(res2, flag, mode, res_inode, dir); + current->link_count--; + kfree_s(res2, NFS_MAXPATHLEN + 1); + return error; +} + +static int nfs_readlink(struct inode *inode, char *buffer, int buflen) +{ + int error, *mem; + unsigned int len; + char *res; + + if (!S_ISLNK(inode->i_mode)) { + iput(inode); + return -EINVAL; + } + if (buflen > NFS_MAXPATHLEN) + buflen = NFS_MAXPATHLEN; + error = nfs_proc_readlink(NFS_SERVER(inode), NFS_FH(inode), &mem, + &res, &len, buflen); + iput(inode); + if (! error) { + memcpy_tofs(buffer, res, len); + put_fs_byte('\0', buffer + len); + error = len; + } + kfree(mem); + return error; +} |