URL
https://opencores.org/ocsvn/or1k_old/or1k_old/trunk
Subversion Repositories or1k_old
Compare Revisions
- This comparison shows the changes necessary to convert path
/or1k_old/trunk/rc203soc/sw/uClinux/fs/nfs
- from Rev 1765 to Rev 1782
- ↔ Reverse comparison
Rev 1765 → Rev 1782
/dir.c
0,0 → 1,759
/* |
* linux/fs/nfs/dir.c |
* |
* Copyright (C) 1992 Rick Sladkey |
* |
* nfs directory handling functions |
* |
* 10 Apr 1996 Added silly rename for unlink --okir |
*/ |
|
/* |
* Fixes: |
* Ion Badulescu <ionut@cs.columbia.edu> : FIFO's need special handling in NFSv2 |
*/ |
|
#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 */ |
|
static int nfs_dir_open(struct inode * inode, struct file * file); |
static int nfs_dir_read(struct inode *, struct file *, char *, int); |
static int nfs_readdir(struct inode *, struct file *, void *, filldir_t); |
static int nfs_lookup(struct inode *, const char *, int, struct inode **); |
static int nfs_create(struct inode *, const char *, int, int, struct inode **); |
static int nfs_mkdir(struct inode *, const char *, int, int); |
static int nfs_rmdir(struct inode *, const char *, int); |
static int nfs_unlink(struct inode *, const char *, int); |
static int nfs_symlink(struct inode *, const char *, int, const char *); |
static int nfs_link(struct inode *, struct inode *, const char *, int); |
static int nfs_mknod(struct inode *, const char *, int, int, int); |
static int nfs_rename(struct inode *, const char *, int, |
struct inode *, const char *, int, int); |
|
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 */ |
nfs_dir_open, /* open - revalidate */ |
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, /* readpage */ |
NULL, /* writepage */ |
NULL, /* bmap */ |
NULL, /* truncate */ |
NULL /* permission */ |
}; |
|
static inline void revalidate_dir(struct nfs_server * server, struct inode * dir) |
{ |
struct nfs_fattr fattr; |
|
if (jiffies - NFS_READTIME(dir) < NFS_ATTRTIMEO(dir)) |
return; |
|
NFS_READTIME(dir) = jiffies; |
if (nfs_proc_getattr(server, NFS_FH(dir), &fattr) == 0) { |
nfs_refresh_inode(dir, &fattr); |
if (fattr.mtime.seconds == NFS_OLDMTIME(dir)) { |
if ((NFS_ATTRTIMEO(dir) <<= 1) > server->acdirmax) |
NFS_ATTRTIMEO(dir) = server->acdirmax; |
return; |
} |
NFS_OLDMTIME(dir) = fattr.mtime.seconds; |
} |
/* invalidate directory cache here when we _really_ start caching */ |
} |
|
static int nfs_dir_open(struct inode * dir, struct file * file) |
{ |
revalidate_dir(NFS_SERVER(dir), dir); |
return 0; |
} |
|
static int nfs_dir_read(struct inode *inode, struct file *filp, char *buf, |
int count) |
{ |
return -EISDIR; |
} |
|
static struct nfs_entry *c_entry = NULL; |
|
/* |
* 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, |
void *dirent, filldir_t filldir) |
{ |
static kdev_t c_dev = 0; |
static int c_ino; |
static int c_size; |
|
int result; |
int i, index = 0; |
struct nfs_entry *entry; |
|
if (!inode || !S_ISDIR(inode->i_mode)) { |
printk("nfs_readdir: inode is NULL or not a directory\n"); |
return -EBADF; |
} |
|
revalidate_dir(NFS_SERVER(inode), inode); |
|
/* 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); |
if (c_entry == NULL) { |
printk("nfs_readdir: no MEMORY for cache\n"); |
return -ENOMEM; |
} |
for (i = 0; i < NFS_READDIR_CACHE_SIZE; i++) { |
c_entry[i].name = (char *) kmalloc(NFS_MAXNAMLEN + 1, |
GFP_KERNEL); |
if (c_entry[i].name == NULL) { |
printk("nfs_readdir: no MEMORY for cache\n"); |
while (--i>=0) |
kfree(c_entry[i].name); |
kfree(c_entry); |
c_entry = NULL; |
return -ENOMEM; |
} |
} |
} |
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 + (index = 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 + (index = 0); |
} |
} |
|
/* if we found it in the cache or from an nfs call, return results */ |
if (!entry) |
return 0; |
while (index < c_size) { |
int nextpos = entry->cookie; |
if (filldir(dirent, entry->name, strlen(entry->name), filp->f_pos, entry->fileid) < 0) |
break; |
filp->f_pos = nextpos; |
/* revalidate the cache if we slept in filldir() */ |
if (inode->i_dev != c_dev) |
break; |
if (inode->i_ino != c_ino) |
break; |
if (nextpos != entry->cookie) |
break; |
index++; |
entry++; |
} |
return 0; |
} |
|
/* |
* free cache memory |
* called from cleanup_module |
*/ |
|
void nfs_kfree_cache(void) |
{ |
int i; |
|
if (c_entry == NULL) |
return; |
for (i = 0; i < NFS_READDIR_CACHE_SIZE; i++) |
kfree(c_entry[i].name); |
kfree(c_entry); |
c_entry = NULL; |
} |
|
|
/* |
* 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 { |
kdev_t 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)->acdirmin : NFS_SERVER(dir)->acregmin); |
} |
|
static void nfs_lookup_cache_remove(struct inode *dir, struct inode *inode, |
const char *filename) |
{ |
struct nfs_lookup_cache_entry *entry; |
kdev_t 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; |
kdev_t 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; |
} |
if (S_ISFIFO(mode)) |
sattr.mode = (mode & ~S_IFMT) | S_IFCHR; |
else |
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 == -EINVAL && (S_ISFIFO(mode))) { |
sattr.mode = mode; |
error = nfs_proc_create(NFS_SERVER(dir), NFS_FH(dir), |
name, &sattr, &fhandle, &fattr); |
} |
if (!error) |
{ |
nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
/* The parent dir inode count may have changed ! */ |
nfs_lookup_cache_remove( NULL, dir, NULL); |
} |
|
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) { |
if (fattr.fileid == dir->i_ino) |
printk("Sony NewsOS 4.1R buggy nfs server?\n"); |
else |
nfs_lookup_cache_add(dir, name, &fhandle, &fattr); |
} |
iput(dir); |
/* The parent dir link count may have changed */ |
nfs_lookup_cache_remove( NULL, dir, NULL); |
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); |
nfs_lookup_cache_remove(dir, NULL, name); |
iput(dir); |
return error; |
} |
|
static int nfs_sillyrename(struct inode *dir, const char *name, int len) |
{ |
struct inode *inode; |
char silly[16]; |
int slen, ret; |
|
dir->i_count++; |
if (nfs_lookup(dir, name, len, &inode) < 0) |
return -EIO; /* arbitrary */ |
if (inode->i_count == 1 || NFS_RENAMED_DIR(inode)) { |
iput(inode); |
return -EIO; |
} |
slen = sprintf(silly, ".nfs%ld", inode->i_ino); |
|
if (len == slen && !strncmp(name, silly, len)) { |
iput(inode); |
return -EIO; /* DWIM */ |
} |
ret = nfs_proc_rename(NFS_SERVER(dir), NFS_FH(dir), name, |
NFS_FH(dir), silly, 0); |
if (ret >= 0) { |
nfs_lookup_cache_remove(dir, NULL, name); |
nfs_lookup_cache_remove(dir, NULL, silly); |
NFS_RENAMED_DIR(inode) = dir; |
dir->i_count++; |
} |
iput(inode); |
return ret; |
} |
|
void nfs_sillyrename_cleanup(struct inode *inode) |
{ |
struct inode *dir = NFS_RENAMED_DIR(inode); |
char silly[14]; |
int error, slen; |
|
slen = sprintf(silly, ".nfs%ld", inode->i_ino); |
error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), silly); |
nfs_lookup_cache_remove(dir, NULL, silly); |
if (error < 0) |
printk("NFS silly_rename cleanup failed (err = %d)\n", |
-error); |
NFS_RENAMED_DIR(inode) = NULL; |
iput(dir); |
} |
|
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; |
} |
if ((error = nfs_sillyrename(dir, name, len)) < 0) { |
error = nfs_proc_remove(NFS_SERVER(dir), NFS_FH(dir), name); |
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); |
|
nfs_lookup_cache_remove(dir, oldinode, NULL); |
NFS_CACHEINV(oldinode); |
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 must_be_dir) |
{ |
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, |
must_be_dir); |
|
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); |
|
/* |
* Some (broken?) NFS servers return 0 as the file type. |
* We'll assume that 0 means the file type has not changed. |
*/ |
if(!(fattr->mode & S_IFMT)){ |
inode->i_mode = (inode->i_mode & S_IFMT) | |
(fattr->mode & ~S_IFMT); |
}else{ |
inode->i_mode = fattr->mode; |
} |
inode->i_nlink = fattr->nlink; |
inode->i_uid = fattr->uid; |
inode->i_gid = fattr->gid; |
|
/* Size changed from outside: invalidate caches on next read */ |
if (inode->i_size != fattr->size) |
NFS_CACHEINV(inode); |
if (NFS_OLDMTIME(inode) != fattr->mtime.seconds) |
NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode); |
inode->i_size = fattr->size; |
if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) |
inode->i_rdev = to_kdev_t(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 (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)) { |
if (was_empty) |
init_fifo(inode); |
} else |
inode->i_op = NULL; |
nfs_lookup_cache_refresh(inode, fattr); |
} |
|
/inode.c
0,0 → 1,381
/* |
* linux/fs/nfs/inode.c |
* |
* Copyright (C) 1992 Rick Sladkey |
* |
* nfs inode and superblock handling functions |
* |
* Modularised by Alan Cox <Alan.Cox@linux.org>, while hacking some |
* experimental NFS changes. Modularisation taken straight from SYS5 fs. |
* |
* Change to nfs_read_super() to permit NFS mounts to multi-homed hosts. |
* J.S.Peatfield@damtp.cam.ac.uk |
* |
*/ |
|
#include <linux/module.h> |
|
#include <linux/sched.h> |
#include <linux/nfs_fs.h> |
#include <linux/nfsiod.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> |
#include <linux/smp.h> |
#include <linux/smp_lock.h> |
|
#include <asm/system.h> |
#include <asm/segment.h> |
|
/* This is for kernel_thread */ |
#define __KERNEL_SYSCALLS__ |
#include <linux/unistd.h> |
|
extern int close_fp(struct file *filp); |
|
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_read_inode(struct inode *); |
static void nfs_statfs(struct super_block *, struct statfs *, int bufsiz); |
|
static struct super_operations nfs_sops = { |
nfs_read_inode, /* 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 |
}; |
|
/* |
* The "read_inode" function doesn't actually do anything: |
* the real data is filled in later in nfs_fhget. Here we |
* just mark the cache times invalid, and zero out i_mode |
* (the latter makes "nfs_refresh_inode" do the right thing |
* wrt pipe inodes) |
*/ |
static void nfs_read_inode(struct inode * inode) |
{ |
int rsize = inode->i_sb->u.nfs_sb.s_server.rsize; |
int size = inode->i_sb->u.nfs_sb.s_server.wsize; |
|
if (rsize > size) |
size = rsize; |
inode->i_blksize = size; |
inode->i_mode = 0; |
inode->i_op = NULL; |
NFS_CACHEINV(inode); |
} |
|
static void nfs_put_inode(struct inode * inode) |
{ |
if (NFS_RENAMED_DIR(inode)) |
nfs_sillyrename_cleanup(inode); |
if (inode->i_pipe) |
clear_inode(inode); |
} |
|
void nfs_put_super(struct super_block *sb) |
{ |
close_fp(sb->u.nfs_sb.s_server.file); |
rpc_closesock(sb->u.nfs_sb.s_server.rsock); |
lock_super(sb); |
sb->s_dev = 0; |
unlock_super(sb); |
MOD_DEC_USE_COUNT; |
} |
|
/* |
* 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 these 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; |
|
kdev_t dev = sb->s_dev; |
|
MOD_INC_USE_COUNT; |
if (!data) { |
printk("nfs_read_super: missing data argument\n"); |
sb->s_dev = 0; |
MOD_DEC_USE_COUNT; |
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; |
MOD_DEC_USE_COUNT; |
return NULL; |
} |
if (!S_ISSOCK(filp->f_inode->i_mode)) { |
printk("nfs_read_super: not a socket\n"); |
sb->s_dev = 0; |
MOD_DEC_USE_COUNT; |
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); |
|
/* Start of JSP NFS patch */ |
/* Check if passed address in data->addr */ |
if (data->addr.sin_addr.s_addr == INADDR_ANY) { /* No address passed */ |
if (((struct sockaddr_in *)(&server->toaddr))->sin_addr.s_addr == INADDR_ANY) { |
printk("NFS: Error passed unconnected socket and no address\n") ; |
MOD_DEC_USE_COUNT; |
return NULL ; |
} else { |
/* Need access to socket internals JSP */ |
struct socket *sock; |
int dummylen ; |
|
/* printk("NFS: using socket address\n") ;*/ |
|
sock = &((filp->f_inode)->u.socket_i); |
|
/* extract the other end of the socket into server->toaddr */ |
sock->ops->getname(sock, &(server->toaddr), &dummylen, 1) ; |
} |
} else { |
/* printk("NFS: copying passed addr to server->toaddr\n") ;*/ |
memcpy((char *)&(server->toaddr),(char *)(&data->addr),sizeof(server->toaddr)); |
} |
/* End of JSP NFS patch */ |
|
if ((server->rsock = rpc_makesock(filp)) == NULL) { |
printk("NFS: cannot create RPC socket.\n"); |
MOD_DEC_USE_COUNT; |
return NULL; |
} |
|
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"); |
MOD_DEC_USE_COUNT; |
return NULL; |
} |
return sb; |
} |
|
void nfs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz) |
{ |
int error; |
struct nfs_fsinfo res; |
struct statfs tmp; |
|
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; |
} |
tmp.f_type = NFS_SUPER_MAGIC; |
tmp.f_bsize = res.bsize; |
tmp.f_blocks = res.blocks; |
tmp.f_bfree = res.bfree; |
tmp.f_bavail = res.bavail; |
tmp.f_files = 0; |
tmp.f_ffree = 0; |
tmp.f_namelen = NAME_MAX; |
memcpy_tofs(buf, &tmp, bufsiz); |
} |
|
/* |
* 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; |
|
sattr.mode = (unsigned) -1; |
if (attr->ia_valid & ATTR_MODE) |
sattr.mode = attr->ia_mode; |
|
sattr.uid = (unsigned) -1; |
if (attr->ia_valid & ATTR_UID) |
sattr.uid = attr->ia_uid; |
|
sattr.gid = (unsigned) -1; |
if (attr->ia_valid & ATTR_GID) |
sattr.gid = attr->ia_gid; |
|
|
sattr.size = (unsigned) -1; |
if (attr->ia_valid & ATTR_SIZE) |
sattr.size = S_ISREG(inode->i_mode) ? attr->ia_size : -1; |
|
sattr.mtime.seconds = sattr.mtime.useconds = (unsigned) -1; |
if (attr->ia_valid & ATTR_MTIME) { |
sattr.mtime.seconds = attr->ia_mtime; |
sattr.mtime.useconds = 0; |
} |
|
sattr.atime.seconds = sattr.atime.useconds = (unsigned) -1; |
if (attr->ia_valid & ATTR_ATIME) { |
sattr.atime.seconds = attr->ia_atime; |
sattr.atime.useconds = 0; |
} |
|
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; |
} |
|
/* Every kernel module contains stuff like this. */ |
|
static struct file_system_type nfs_fs_type = { |
nfs_read_super, "nfs", 0, NULL |
}; |
|
/* |
* Start up an nfsiod process. This is an awful hack, because when running |
* as a module, we will keep insmod's memory. Besides, the current->comm |
* hack won't work in this case |
* The best would be to have a syscall for nfs client control that (among |
* other things) forks biod's. |
* Alternatively, we might want to have the idle task spawn biod's on demand. |
*/ |
static int run_nfsiod(void *dummy) |
{ |
int ret; |
|
#ifdef __SMP__ |
lock_kernel(); |
syscall_count++; |
#endif |
|
MOD_INC_USE_COUNT; |
exit_mm(current); |
current->session = 1; |
current->pgrp = 1; |
sprintf(current->comm, "nfsiod"); |
#ifndef MODULE |
current->blocked = ~0UL; |
#endif |
ret = nfsiod(); |
MOD_DEC_USE_COUNT; |
return ret; |
} |
|
int init_nfs_fs(void) |
{ |
/* Fork four biod's */ |
kernel_thread(run_nfsiod, NULL, 0); |
kernel_thread(run_nfsiod, NULL, 0); |
kernel_thread(run_nfsiod, NULL, 0); |
kernel_thread(run_nfsiod, NULL, 0); |
return register_filesystem(&nfs_fs_type); |
} |
|
#ifdef MODULE |
int init_module(void) |
{ |
int status; |
|
if ((status = init_nfs_fs()) == 0) |
register_symtab(0); |
return status; |
} |
|
void cleanup_module(void) |
{ |
unregister_filesystem(&nfs_fs_type); |
nfs_kfree_cache(); |
} |
|
#endif |
/file.c
0,0 → 1,157
/* |
* 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). |
* |
* Total rewrite of read side for new NFS buffer cache.. Linus. |
* |
* nfs regular file handling functions |
*/ |
|
#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> |
#include <linux/pagemap.h> |
|
#include <asm/segment.h> |
#include <asm/system.h> |
|
static int nfs_file_mmap(struct inode *, struct file *, struct vm_area_struct *); |
static int nfs_file_read(struct inode *, struct file *, char *, int); |
static int nfs_file_write(struct inode *, struct file *, const 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_file_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 */ |
nfs_readpage, /* readpage */ |
NULL, /* writepage */ |
NULL, /* bmap */ |
NULL /* truncate */ |
}; |
|
static inline void revalidate_inode(struct nfs_server * server, struct inode * inode) |
{ |
struct nfs_fattr fattr; |
|
if (jiffies - NFS_READTIME(inode) < NFS_ATTRTIMEO(inode)) |
return; |
|
NFS_READTIME(inode) = jiffies; |
if (nfs_proc_getattr(server, NFS_FH(inode), &fattr) == 0) { |
nfs_refresh_inode(inode, &fattr); |
if (fattr.mtime.seconds == NFS_OLDMTIME(inode)) { |
if ((NFS_ATTRTIMEO(inode) <<= 1) > server->acregmax) |
NFS_ATTRTIMEO(inode) = server->acregmax; |
return; |
} |
NFS_OLDMTIME(inode) = fattr.mtime.seconds; |
} |
invalidate_inode_pages(inode); |
} |
|
|
static int nfs_file_read(struct inode * inode, struct file * file, |
char * buf, int count) |
{ |
revalidate_inode(NFS_SERVER(inode), inode); |
return generic_file_read(inode, file, buf, count); |
} |
|
static int nfs_file_mmap(struct inode * inode, struct file * file, struct vm_area_struct * vma) |
{ |
revalidate_inode(NFS_SERVER(inode), inode); |
return generic_file_mmap(inode, file, vma); |
} |
|
static int nfs_fsync(struct inode *inode, struct file *file) |
{ |
return 0; |
} |
|
static int nfs_file_write(struct inode *inode, struct file *file, const char *buf, |
int count) |
{ |
int result, written, wsize; |
struct nfs_fattr fattr; |
unsigned long pos; |
|
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; |
|
pos = file->f_pos; |
if (file->f_flags & O_APPEND) |
pos = inode->i_size; |
wsize = NFS_SERVER(inode)->wsize; |
result = 0; |
written = 0; |
while (written < count) { |
int hunk = count - written; |
if (hunk >= wsize) |
hunk = wsize; |
result = nfs_proc_write(inode, |
pos, hunk, buf, &fattr); |
if (result < 0) |
break; |
pos += hunk; |
buf += hunk; |
written += hunk; |
if (hunk < wsize) |
break; |
} |
if (!written) |
return result; |
file->f_pos = pos; |
if (pos > inode->i_size) |
inode->i_size = pos; |
/* Avoid possible Solaris 2.5 nfsd bug */ |
if (inode->i_ino == fattr.fileid) |
nfs_refresh_inode(inode, &fattr); |
return written; |
} |
|
/sock.c
0,0 → 1,127
/* |
* 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/sched.h> |
#include <linux/nfs_fs.h> |
#include <linux/errno.h> |
#include <linux/socket.h> |
#include <linux/fcntl.h> |
#include <linux/in.h> |
#include <linux/net.h> |
#include <linux/mm.h> |
#include <linux/rpcsock.h> |
|
#include <asm/segment.h> |
|
#define _S(nr) (1<<((nr)-1)) |
|
/* |
* Place a synchronous call to the NFS server, meaning that the process |
* sleeps in rpc_call until it either receives a reply or a major timeout |
* occurs. |
* This is now merely a front-end to nfs_rpc_doio. |
*/ |
int |
nfs_rpc_call(struct nfs_server *server, int *start, int *end, int size) |
{ |
struct rpc_ioreq req; |
|
size += 1024; /* account for NFS slack space. ugly */ |
|
req.rq_addr = &server->toaddr; |
req.rq_alen = sizeof(server->toaddr); |
req.rq_slot = NULL; |
|
req.rq_svec[0].iov_base = start; |
req.rq_svec[0].iov_len = (end - start) << 2; |
req.rq_slen = (end - start) << 2; |
req.rq_snr = 1; |
req.rq_rvec[0].iov_base = start; |
req.rq_rvec[0].iov_len = size; |
req.rq_rlen = size; |
req.rq_rnr = 1; |
|
return nfs_rpc_doio(server, &req, 0); |
} |
|
int |
nfs_rpc_doio(struct nfs_server *server, struct rpc_ioreq *req, int async) |
{ |
struct rpc_timeout timeout; |
unsigned long maxtimeo; |
unsigned long oldmask; |
int major_timeout_seen, result; |
|
timeout.to_initval = server->timeo; |
timeout.to_maxval = NFS_MAX_RPC_TIMEOUT*HZ/10; |
timeout.to_retries = server->retrans; |
timeout.to_exponential = 1; |
|
oldmask = current->blocked; |
current->blocked |= ~(_S(SIGKILL) |
| ((server->flags & NFS_MOUNT_INTR) |
? ((current->sig->action[SIGINT - 1].sa_handler == SIG_DFL |
? _S(SIGINT) : 0) |
| (current->sig->action[SIGQUIT - 1].sa_handler == SIG_DFL |
? _S(SIGQUIT) : 0)) |
: 0)); |
|
major_timeout_seen = 0; |
maxtimeo = timeout.to_maxval; |
|
do { |
result = rpc_doio(server->rsock, req, &timeout, async); |
rpc_release(server->rsock, req); /* Release slot */ |
|
if (current->signal & ~current->blocked) |
result = -ERESTARTSYS; |
if (result == -ETIMEDOUT) { |
if (async) |
break; |
if (server->flags & NFS_MOUNT_SOFT) { |
printk("NFS server %s not responding, " |
"timed out.\n", server->hostname); |
result = -EIO; |
break; |
} |
if (!major_timeout_seen) { |
printk("NFS server %s not responding, " |
"still trying.\n", server->hostname); |
major_timeout_seen = 1; |
} |
if ((timeout.to_initval <<= 1) >= maxtimeo) { |
timeout.to_initval = maxtimeo; |
} |
} else if (result < 0 && result != -ERESTARTSYS) { |
printk("NFS: notice message: result = %d.\n", result); |
} |
} while (result == -ETIMEDOUT && !(server->flags & NFS_MOUNT_SOFT)); |
|
if (result >= 0 && major_timeout_seen) |
printk("NFS server %s OK.\n", server->hostname); |
/* 20 is the minimum RPC reply header size */ |
if (result >= 0 && result < 20) { |
printk("NFS: too small read memory size (%d bytes)\n", result); |
result = -EIO; |
} |
|
current->blocked = oldmask; |
return result; |
} |
/nfsiod.c
0,0 → 1,124
/* |
* linux/fs/nfs/nfsiod.c |
* |
* Async NFS RPC call support. |
* |
* When a process wants to place an asynchronous RPC call, it reserves |
* an nfsiod slot, fills in all necessary fields including the callback |
* handler field, and enqueues the request. |
* |
* This will wake up nfsiod, which calls nfs_rpc_doio to collect the |
* reply. It then dispatches the result to the caller via the callback |
* function, including result value and request pointer. It then re-inserts |
* itself into the free list. |
* |
* Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> |
*/ |
|
#include <linux/sched.h> |
#include <linux/nfs_fs.h> |
#include <linux/string.h> |
#include <linux/errno.h> |
#include <linux/rpcsock.h> |
#include <linux/nfsiod.h> |
|
static struct nfsiod_req * free_list = NULL; |
static int active = 0; |
|
#undef DEBUG_NFSIOD |
#ifdef DEBUG_NFSIOD |
#define dprintk(args...) printk(## args) |
#else |
#define dprintk(args...) /* nothing */ |
#endif |
|
|
/* |
* Reserve an nfsiod slot and initialize the request struct |
*/ |
struct nfsiod_req * |
nfsiod_reserve(struct nfs_server *server) |
{ |
struct nfsiod_req *req; |
|
if (!(req = free_list)) { |
dprintk("BIO: nfsiod_reserve: no free nfsiods\n"); |
return NULL; |
} |
free_list = req->rq_next; |
memset(&req->rq_rpcreq, 0, sizeof(struct rpc_ioreq)); |
|
if (rpc_reserve(server->rsock, &req->rq_rpcreq, 1) < 0) { |
dprintk("BIO: nfsiod_reserve failed to reserve RPC slot\n"); |
req->rq_next = free_list; |
free_list = req; |
return NULL; |
} |
|
req->rq_server = server; |
return req; |
} |
|
void |
nfsiod_release(struct nfsiod_req *req) |
{ |
dprintk("BIO: nfsiod_release called\n"); |
rpc_release(req->rq_server->rsock, &req->rq_rpcreq); |
memset(&req->rq_rpcreq, 0, sizeof(struct rpc_ioreq)); |
req->rq_next = free_list; |
free_list = req; |
} |
|
/* |
* Transmit a request and put it on nfsiod's list of pending requests. |
*/ |
void |
nfsiod_enqueue(struct nfsiod_req *req) |
{ |
dprintk("BIO: enqueuing request %p\n", &req->rq_rpcreq); |
wake_up(&req->rq_wait); |
schedule(); |
} |
|
/* |
* This is the main nfsiod loop. |
*/ |
int |
nfsiod(void) |
{ |
struct nfsiod_req request, *req = &request; |
int result; |
|
dprintk("BIO: nfsiod %d starting\n", current->pid); |
while (1) { |
/* Insert request into free list */ |
memset(req, 0, sizeof(*req)); |
req->rq_next = free_list; |
free_list = req; |
|
/* Wait until user enqueues request */ |
dprintk("BIO: before: now %d nfsiod's active\n", active); |
dprintk("BIO: nfsiod %d waiting\n", current->pid); |
#ifndef MODULE |
current->signal = 0; |
#endif |
interruptible_sleep_on(&req->rq_wait); |
#ifdef MODULE |
if (current->signal & ~current->blocked) |
break; |
#endif |
if (!req->rq_rpcreq.rq_slot) |
continue; |
dprintk("BIO: nfsiod %d woken up; calling nfs_rpc_doio.\n", |
current->pid); |
active++; |
dprintk("BIO: before: now %d nfsiod's active\n", active); |
do { |
result = nfs_rpc_doio(req->rq_server, |
&req->rq_rpcreq, 1); |
} while (!req->rq_callback(result, req)); |
active--; |
} |
|
return 0; |
} |
/proc.c
0,0 → 1,1044
/* |
* 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. |
*/ |
|
/* |
* Fixes: |
* Ion Badulescu <ionut@cs.columbia.edu> : FIFO's need special handling in NFSv2 |
*/ |
|
/* |
* 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/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 <linux/pagemap.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, const 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) |
{ |
unsigned len = *lenp = ntohl(*p++); |
if (len > maxlen) |
return NULL; |
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++); |
if (fattr->type == NFCHR && fattr->rdev == NFS_FIFO_DEV) { |
fattr->type = NFFIFO; |
fattr->mode = (fattr->mode & ~S_IFMT) | S_IFIFO; |
fattr->rdev = 0; |
} |
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 *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))) { |
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_read_request(struct rpc_ioreq *req, struct nfs_server *server, |
struct nfs_fh *fh, unsigned long offset, |
unsigned long count, __u32 *buf) |
{ |
__u32 *p, *p0; |
int len; |
|
PRINTK("NFS reqst read %ld @ %ld\n", count, offset); |
if (!(p0 = nfs_rpc_alloc(NFS_SLACK_SPACE))) |
return -EIO; |
|
p = nfs_rpc_header(p0, NFSPROC_READ, 0); |
p = xdr_encode_fhandle(p, fh); |
*p++ = htonl(offset); |
*p++ = htonl(count); |
*p++ = htonl(count); /* traditional, could be any value */ |
req->rq_svec[0].iov_base = p0; |
req->rq_svec[0].iov_len = (p - p0) << 2; |
req->rq_slen = (p - p0) << 2; |
req->rq_snr = 1; |
|
len = (6 + 1 + 17 + 1); /* standard READ reply header */ |
req->rq_rvec[0].iov_base = p0; |
req->rq_rvec[0].iov_len = len << 2; |
req->rq_rvec[1].iov_base = buf; |
req->rq_rvec[1].iov_len = count; |
req->rq_rvec[2].iov_base = p0 + len; /* spill buffer */ |
req->rq_rvec[2].iov_len = (NFS_SLACK_SPACE - len) << 2; |
req->rq_rlen = count + NFS_SLACK_SPACE; |
req->rq_rnr = 3; |
|
req->rq_addr = &server->toaddr; |
req->rq_alen = sizeof(server->toaddr); |
|
return rpc_transmit(server->rsock, req); |
} |
|
int |
nfs_proc_read_reply(struct rpc_ioreq *req, struct nfs_fattr *fattr) |
{ |
int status; |
__u32 *p0, *p; |
int count; |
|
p0 = (__u32 *) req->rq_rvec[0].iov_base; |
|
if (!(p = nfs_rpc_verify(p0))) { |
/* Tell the upper layers to retry */ |
status = -EAGAIN; |
/* status = -errno_NFSERR_IO; */ |
} else if ((status = ntohl(*p++)) == NFS_OK) { |
p = xdr_decode_fattr(p, fattr); |
count = ntohl(*p++); |
if (p != req->rq_rvec[2].iov_base) { |
/* unexpected RPC reply header size. punt. |
* fixme: move iovec contents to align data |
* on page boundary and adjust RPC header size |
* guess. */ |
status = -errno_NFSERR_IO; |
PRINTK("NFS reply read odd header size %d\n", |
(p - p0) << 2); |
} else { |
status = count; |
PRINTK("NFS reply read %d\n", count); |
} |
} |
else { |
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 inode * inode, int offset, |
int count, const char *data, struct nfs_fattr *fattr) |
{ |
int *p, *p0; |
int status; |
int ruid = 0; |
void * kdata; /* address of kernel copy */ |
struct nfs_server * server = NFS_SERVER(inode); |
struct nfs_fh *fhandle = NFS_FH(inode); |
|
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 */ |
kdata = (void *) (p+1); /* start of data in RPC buffer */ |
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) { |
update_vm_cache(inode, offset, kdata, count); |
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 must_be_dir) |
{ |
int *p, *p0; |
int status; |
int ruid = 0; |
|
/* |
* Disallow "rename()" with trailing slashes over NFS: getting |
* POSIX.1 behaviour is just too unlikely. |
*/ |
if (must_be_dir) |
return -EINVAL; |
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. |
*/ |
|
int *rpc_header(int *p, int procedure, int program, int version, |
int uid, int gid, int *groups) |
{ |
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(program); |
*p++ = htonl(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(uid); |
*p++ = htonl(gid); |
p2 = p++; |
for (i = 0; i < 16 && i < NGROUPS && groups[i] != NOGROUP; i++) |
*p++ = htonl(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_header(int *p, int procedure, int ruid) |
{ |
return rpc_header(p, procedure, NFS_PROGRAM, NFS_VERSION, |
(ruid ? current->uid : current->fsuid), |
current->egid, current->groups); |
} |
|
|
int *rpc_verify(int *p) |
{ |
unsigned int n; |
|
p++; |
if ((n = ntohl(*p++)) != RPC_REPLY) { |
printk("nfs_rpc_verify: not an RPC reply: %x\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; |
} |
|
|
static int *nfs_rpc_verify(int *p) |
{ |
return rpc_verify(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_EAGAIN, EAGAIN }, |
{ NFSERR_ACCES, EACCES }, |
{ NFSERR_EXIST, EEXIST }, |
{ NFSERR_XDEV, EXDEV }, |
{ 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; |
} |
|
/rpcsock.c
0,0 → 1,584
/* |
* linux/fs/nfs/rpcsock.c |
* |
* This is a generic RPC call interface for datagram sockets that is able |
* to place several concurrent RPC requests at the same time. It works like |
* this: |
* |
* - When a process places a call, it allocates a request slot if |
* one is available. Otherwise, it sleeps on the backlog queue |
* (rpc_reserve). |
* - Then, the message is transmitted via rpc_send (exported by name of |
* rpc_transmit). |
* - Finally, the process waits for the call to complete (rpc_doio): |
* The first process on the receive queue waits for the next RPC packet, |
* and peeks at the XID. If it finds a matching request, it receives |
* the datagram on behalf of that process and wakes it up. Otherwise, |
* the datagram is discarded. |
* - If the process having received the datagram was the first one on |
* the receive queue, it wakes up the next one to listen for replies. |
* - It then removes itself from the request queue (rpc_release). |
* If there are more callers waiting on the backlog queue, they are |
* woken up, too. |
* |
* Mar 1996: |
* - Split up large functions into smaller chunks as per Linus' coding |
* style. Found an interesting bug this way, too. |
* - Added entry points for nfsiod. |
* |
* Copyright (C) 1995, 1996, Olaf Kirch <okir@monad.swb.de> |
*/ |
|
#include <linux/types.h> |
#include <linux/malloc.h> |
#include <linux/sched.h> |
#include <linux/nfs_fs.h> |
#include <linux/errno.h> |
#include <linux/socket.h> |
#include <linux/fcntl.h> |
#include <linux/in.h> |
#include <linux/net.h> |
#include <linux/mm.h> |
#include <linux/rpcsock.h> |
|
#include <linux/udp.h> |
#include <net/sock.h> |
|
#include <asm/segment.h> |
|
#define msleep(sec) { current->timeout = sec * HZ / 1000; \ |
current->state = TASK_INTERRUPTIBLE; \ |
schedule(); \ |
} |
|
#undef DEBUG_RPC |
#ifdef DEBUG_RPC |
#define dprintk(args...) printk(## args) |
#else |
#define dprintk(args...) |
#endif |
|
|
/* |
* Insert new request into wait list. We make sure list is sorted by |
* increasing timeout value. |
*/ |
static inline void |
rpc_insque(struct rpc_sock *rsock, struct rpc_wait *slot) |
{ |
struct rpc_wait *next = rsock->pending; |
|
slot->w_next = next; |
slot->w_prev = NULL; |
if (next) |
next->w_prev = slot; |
rsock->pending = slot; |
slot->w_queued = 1; |
|
dprintk("RPC: inserted %p into queue\n", slot); |
} |
|
/* |
* Remove request from request queue |
*/ |
static inline void |
rpc_remque(struct rpc_sock *rsock, struct rpc_wait *slot) |
{ |
struct rpc_wait *prev = slot->w_prev, |
*next = slot->w_next; |
|
if (prev != NULL) |
prev->w_next = next; |
else |
rsock->pending = next; |
if (next != NULL) |
next->w_prev = prev; |
|
slot->w_queued = 0; |
dprintk("RPC: removed %p from queue, head now %p.\n", |
slot, rsock->pending); |
} |
|
/* |
* Write data to socket. |
*/ |
static inline int |
rpc_sendmsg(struct rpc_sock *rsock, struct iovec *iov, int nr, int len, |
struct sockaddr *sap, int salen) |
{ |
struct socket *sock = rsock->sock; |
struct msghdr msg; |
unsigned long oldfs; |
int result; |
|
msg.msg_iov = iov; |
msg.msg_iovlen = nr; |
msg.msg_name = sap; |
msg.msg_namelen = salen; |
msg.msg_control = NULL; |
|
oldfs = get_fs(); |
set_fs(get_ds()); |
result = sock->ops->sendmsg(sock, &msg, len, 0, 0); |
set_fs(oldfs); |
|
dprintk("RPC: rpc_sendmsg(iov %p, len %d) = %d\n", iov, len, result); |
return result; |
} |
/* |
* Read data from socket |
*/ |
static inline int |
rpc_recvmsg(struct rpc_sock *rsock, struct iovec *iov, |
int nr, int len, int flags) |
{ |
struct socket *sock = rsock->sock; |
struct sockaddr sa; |
struct msghdr msg; |
unsigned long oldfs; |
int result, alen; |
|
msg.msg_iov = iov; |
msg.msg_iovlen = nr; |
msg.msg_name = &sa; |
msg.msg_namelen = sizeof(sa); |
msg.msg_control = NULL; |
|
oldfs = get_fs(); |
set_fs(get_ds()); |
result = sock->ops->recvmsg(sock, &msg, len, 1, flags, &alen); |
set_fs(oldfs); |
|
dprintk("RPC: rpc_recvmsg(iov %p, len %d) = %d\n", iov, len, result); |
return result; |
} |
|
/* |
* This code is slightly complicated. Since the networking code does not |
* honor the current->timeout value, we have to select on the socket. |
*/ |
static inline int |
rpc_select(struct rpc_sock *rsock) |
{ |
struct select_table_entry entry; |
struct file *file = rsock->file; |
select_table wait_table; |
|
dprintk("RPC: selecting on socket...\n"); |
wait_table.nr = 0; |
wait_table.entry = &entry; |
current->state = TASK_INTERRUPTIBLE; |
if (!file->f_op->select(file->f_inode, file, SEL_IN, &wait_table) |
&& !file->f_op->select(file->f_inode, file, SEL_IN, NULL)) { |
schedule(); |
remove_wait_queue(entry.wait_address, &entry.wait); |
current->state = TASK_RUNNING; |
if (current->signal & ~current->blocked) |
return -ERESTARTSYS; |
if (current->timeout == 0) |
return -ETIMEDOUT; |
} else if (wait_table.nr) |
remove_wait_queue(entry.wait_address, &entry.wait); |
current->state = TASK_RUNNING; |
dprintk("RPC: ...Okay, there appears to be some data.\n"); |
return 0; |
} |
|
/* |
* Reserve an RPC call slot. nocwait determines whether we wait in case |
* of congestion or not. |
*/ |
int |
rpc_reserve(struct rpc_sock *rsock, struct rpc_ioreq *req, int nocwait) |
{ |
struct rpc_wait *slot; |
|
req->rq_slot = NULL; |
|
while (!(slot = rsock->free) || rsock->cong >= rsock->cwnd) { |
if (nocwait) { |
current->timeout = 0; |
return -ENOBUFS; |
} |
dprintk("RPC: rpc_reserve waiting on backlog\n"); |
interruptible_sleep_on(&rsock->backlog); |
if (current->timeout == 0) |
return -ETIMEDOUT; |
if (current->signal & ~current->blocked) |
return -ERESTARTSYS; |
if (rsock->shutdown) |
return -EIO; |
} |
|
rsock->free = slot->w_next; |
rsock->cong += RPC_CWNDSCALE; /* bump congestion value */ |
|
slot->w_queued = 0; |
slot->w_gotit = 0; |
slot->w_req = req; |
|
dprintk("RPC: reserved slot %p\n", slot); |
req->rq_slot = slot; |
return 0; |
} |
|
/* |
* Release an RPC call slot |
*/ |
void |
rpc_release(struct rpc_sock *rsock, struct rpc_ioreq *req) |
{ |
struct rpc_wait *slot = req->rq_slot; |
|
if (slot != NULL) { |
dprintk("RPC: release slot %p\n", slot); |
|
/* Wake up the next receiver */ |
if (slot == rsock->pending && slot->w_next != NULL) |
wake_up(&slot->w_next->w_wait); |
|
/* remove slot from queue of pending */ |
if (slot->w_queued) |
rpc_remque(rsock, slot); |
slot->w_next = rsock->free; |
rsock->free = slot; |
|
/* decrease congestion value */ |
rsock->cong -= RPC_CWNDSCALE; |
if (rsock->cong < rsock->cwnd && rsock->backlog) |
wake_up(&rsock->backlog); |
if (rsock->shutdown) |
wake_up(&rsock->shutwait); |
|
req->rq_slot = NULL; |
} |
} |
|
/* |
* Adjust RPC congestion window |
*/ |
static void |
rpc_cwnd_adjust(struct rpc_sock *rsock, int timeout) |
{ |
unsigned long cwnd = rsock->cwnd; |
|
if (!timeout) { |
if (rsock->cong >= cwnd) { |
/* The (cwnd >> 1) term makes sure |
* the result gets rounded properly. */ |
cwnd += (RPC_CWNDSCALE * RPC_CWNDSCALE + |
(cwnd >> 1)) / cwnd; |
if (cwnd > RPC_MAXCWND) |
cwnd = RPC_MAXCWND; |
} |
} else { |
if ((cwnd >>= 1) < RPC_CWNDSCALE) |
cwnd = RPC_CWNDSCALE; |
dprintk("RPC: cwnd decrease %08lx\n", cwnd); |
} |
dprintk("RPC: cong %08lx, cwnd was %08lx, now %08lx\n", |
rsock->cong, rsock->cwnd, cwnd); |
|
rsock->cwnd = cwnd; |
} |
|
static inline void |
rpc_send_check(char *where, u32 *ptr) |
{ |
if (ptr[1] != htonl(RPC_CALL) || ptr[2] != htonl(RPC_VERSION)) { |
printk("RPC: %s sending evil packet:\n" |
" %08x %08x %08x %08x %08x %08x %08x %08x\n", |
where, |
ptr[0], ptr[1], ptr[2], ptr[3], |
ptr[4], ptr[5], ptr[6], ptr[7]); |
} |
} |
|
/* |
* Place the actual RPC call. |
* We have to copy the iovec because sendmsg fiddles with its contents. |
*/ |
static inline int |
rpc_send(struct rpc_sock *rsock, struct rpc_wait *slot) |
{ |
struct rpc_ioreq *req = slot->w_req; |
struct iovec iov[UIO_MAXIOV]; |
|
if (rsock->shutdown) |
return -EIO; |
|
memcpy(iov, req->rq_svec, req->rq_snr * sizeof(iov[0])); |
slot->w_xid = *(u32 *)(iov[0].iov_base); |
if (!slot->w_queued) |
rpc_insque(rsock, slot); |
|
dprintk("rpc_send(%p, %x)\n", slot, slot->w_xid); |
rpc_send_check("rpc_send", (u32 *) req->rq_svec[0].iov_base); |
return rpc_sendmsg(rsock, iov, req->rq_snr, req->rq_slen, |
req->rq_addr, req->rq_alen); |
} |
|
/* |
* This is the same as rpc_send but for the functions exported to nfsiod |
*/ |
int |
rpc_transmit(struct rpc_sock *rsock, struct rpc_ioreq *req) |
{ |
rpc_send_check("rpc_transmit", (u32 *) req->rq_svec[0].iov_base); |
return rpc_send(rsock, req->rq_slot); |
} |
|
/* |
* Receive and dispatch a single reply |
*/ |
static inline int |
rpc_grok(struct rpc_sock *rsock) |
{ |
struct rpc_wait *rovr; |
struct rpc_ioreq *req; |
struct iovec iov[UIO_MAXIOV]; |
u32 xid; |
int safe, result; |
|
iov[0].iov_base = (void *) &xid; |
iov[0].iov_len = sizeof(xid); |
result = rpc_recvmsg(rsock, iov, 1, sizeof(xid), MSG_PEEK); |
|
if (result < 0) { |
switch (-result) { |
case EAGAIN: case ECONNREFUSED: |
return 0; |
case ERESTARTSYS: |
return result; |
default: |
dprintk("rpc_grok: recv error = %d\n", result); |
} |
} |
if (result < 4) { |
printk(KERN_WARNING "RPC: impossible RPC reply size %d\n", |
result); |
iov[0].iov_base=(void*)&xid; /* xid=32bits, which is large enough */ |
iov[0].iov_len=result; |
rpc_recvmsg(rsock, iov, 1, result, 0); |
return 0; |
} |
|
dprintk("RPC: rpc_grok: got xid %08lx\n", (unsigned long) xid); |
|
/* Look for the caller */ |
safe = 0; |
for (rovr = rsock->pending; rovr; rovr = rovr->w_next) { |
if (rovr->w_xid == xid) |
break; |
if (safe++ > RPC_MAXREQS) { |
printk(KERN_WARNING "RPC: loop in request Q!!\n"); |
rovr = NULL; |
break; |
} |
} |
|
if (!rovr || rovr->w_gotit) { |
/* discard dgram */ |
dprintk("RPC: rpc_grok: %s.\n", |
rovr? "duplicate reply" : "bad XID"); |
iov[0].iov_base = (void *) &xid; |
iov[0].iov_len = sizeof(xid); |
rpc_recvmsg(rsock, iov, 1, sizeof(xid), 0); |
return 0; |
} |
req = rovr->w_req; |
|
/* Now receive the reply... Copy the iovec first because of |
* memcpy_fromiovec fiddling. */ |
memcpy(iov, req->rq_rvec, req->rq_rnr * sizeof(iov[0])); |
result = rpc_recvmsg(rsock, iov, req->rq_rnr, req->rq_rlen, 0); |
rovr->w_result = result; |
rovr->w_gotit = 1; |
|
/* ... and wake up the process */ |
wake_up(&rovr->w_wait); |
|
return result; |
} |
|
/* |
* Wait for the reply to our call. |
*/ |
static int |
rpc_recv(struct rpc_sock *rsock, struct rpc_wait *slot) |
{ |
int result; |
|
do { |
/* If we are not the receiver, wait on the sidelines */ |
dprintk("RPC: rpc_recv TP1\n"); |
while (rsock->pending != slot) { |
if (!slot->w_gotit) |
interruptible_sleep_on(&slot->w_wait); |
if (slot->w_gotit) |
return slot->w_result; /* quite important */ |
if (current->signal & ~current->blocked) |
return -ERESTARTSYS; |
if (rsock->shutdown) |
return -EIO; |
if (current->timeout == 0) |
return -ETIMEDOUT; |
} |
|
/* Wait for data to arrive */ |
if ((result = rpc_select(rsock)) < 0) { |
dprintk("RPC: select error = %d\n", result); |
return result; |
} |
|
/* Receive and dispatch */ |
if ((result = rpc_grok(rsock)) < 0) |
return result; |
} while (current->timeout && !slot->w_gotit); |
|
return slot->w_gotit? slot->w_result : -ETIMEDOUT; |
} |
|
/* |
* Generic RPC call routine. This handles retries and timeouts etc pp. |
* |
* If sent is non-null, it assumes the called has already sent out the |
* message, so it won't need to do so unless a timeout occurs. |
*/ |
int |
rpc_doio(struct rpc_sock *rsock, struct rpc_ioreq *req, |
struct rpc_timeout *strategy, int sent) |
{ |
struct rpc_wait *slot; |
int result, retries; |
unsigned long timeout; |
|
timeout = strategy->to_initval; |
retries = 0; |
slot = req->rq_slot; |
|
do { |
dprintk("RPC: rpc_doio: TP1 (req %p)\n", req); |
current->timeout = jiffies + timeout; |
if (slot == NULL) { |
result = rpc_reserve(rsock, req, 0); |
if (result == -ETIMEDOUT) |
goto timedout; |
if (result < 0) |
break; |
slot = req->rq_slot; |
rpc_send_check("rpc_doio", |
(u32 *) req->rq_svec[0].iov_base); |
rpc_insque(rsock, slot); |
} |
|
/* This check is for loopback NFS. Sometimes replies come |
* in before biod has called rpc_doio... */ |
if (slot->w_gotit) { |
result = slot->w_result; |
break; |
} |
|
dprintk("RPC: rpc_doio: TP2\n"); |
if (sent || (result = rpc_send(rsock, slot)) >= 0) { |
result = rpc_recv(rsock, slot); |
sent = 0; |
} |
|
if (result != -ETIMEDOUT) { |
/* dprintk("RPC: rpc_recv returned %d\n", result); */ |
rpc_cwnd_adjust(rsock, 0); |
break; |
} |
|
rpc_cwnd_adjust(rsock, 1); |
|
timedout: |
dprintk("RPC: rpc_recv returned timeout.\n"); |
if (strategy->to_exponential) |
timeout <<= 1; |
else |
timeout += strategy->to_increment; |
if (strategy->to_maxval && timeout >= strategy->to_maxval) |
timeout = strategy->to_maxval; |
if (strategy->to_retries && ++retries >= strategy->to_retries) |
break; |
} while (1); |
|
dprintk("RPC: rpc_doio: TP3\n"); |
current->timeout = 0; |
return result; |
} |
|
/* |
*/ |
int |
rpc_call(struct rpc_sock *rsock, struct rpc_ioreq *req, |
struct rpc_timeout *strategy) |
{ |
int result; |
|
result = rpc_doio(rsock, req, strategy, 0); |
if (req->rq_slot == NULL) |
printk(KERN_WARNING "RPC: bad: rq_slot == NULL\n"); |
rpc_release(rsock, req); |
return result; |
} |
|
struct rpc_sock * |
rpc_makesock(struct file *file) |
{ |
struct rpc_sock *rsock; |
struct socket *sock; |
struct sock *sk; |
struct rpc_wait *slot; |
int i; |
|
dprintk("RPC: make RPC socket...\n"); |
sock = &file->f_inode->u.socket_i; |
if (sock->type != SOCK_DGRAM || sock->ops->family != AF_INET) { |
printk(KERN_WARNING "RPC: only UDP sockets supported\n"); |
return NULL; |
} |
sk = (struct sock *) sock->data; |
|
if ((rsock = kmalloc(sizeof(struct rpc_sock), GFP_KERNEL)) == NULL) |
return NULL; |
memset(rsock, 0, sizeof(*rsock)); /* Nnnngh! */ |
|
rsock->sock = sock; |
rsock->inet = sk; |
rsock->file = file; |
rsock->cwnd = RPC_INITCWND; |
|
dprintk("RPC: slots %p, %p, ...\n", rsock->waiting, rsock->waiting + 1); |
rsock->free = rsock->waiting; |
for (i = 0, slot = rsock->waiting; i < RPC_MAXREQS-1; i++, slot++) |
slot->w_next = slot + 1; |
slot->w_next = NULL; |
|
dprintk("RPC: made socket %p\n", rsock); |
return rsock; |
} |
|
int |
rpc_closesock(struct rpc_sock *rsock) |
{ |
unsigned long t0 = jiffies; |
|
rsock->shutdown = 1; |
while (rsock->pending || waitqueue_active(&rsock->backlog)) { |
interruptible_sleep_on(&rsock->shutwait); |
if (current->signal & ~current->blocked) |
return -EINTR; |
#if 1 |
if (t0 && t0 - jiffies > 60 * HZ) { |
printk(KERN_WARNING "RPC: hanging in rpc_closesock.\n"); |
t0 = 0; |
} |
#endif |
} |
|
kfree(rsock); |
return 0; |
} |
/symlink.c
0,0 → 1,118
/* |
* linux/fs/nfs/symlink.c |
* |
* Copyright (C) 1992 Rick Sladkey |
* |
* Optimization changes Copyright (C) 1994 Florian La Roche |
* |
* nfs symlink handling code |
*/ |
|
#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> |
|
#include <asm/segment.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, /* readpage */ |
NULL, /* writepage */ |
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_user('\0', buffer + len); |
error = len; |
} |
kfree(mem); |
return error; |
} |
/bio.c
0,0 → 1,225
/* |
* linux/fs/nfs/bio.c |
* |
* Block I/O for NFS |
* |
* Partial copy of Linus' read cache modifications to fs/nfs/file.c |
* modified for async RPC by okir@monad.swb.de |
* |
* We do an ugly hack here in order to return proper error codes to the |
* user program when a read request failed. This is a huge problem because |
* generic_file_read only checks the return value of inode->i_op->readpage() |
* which is usually 0 for async RPC. To overcome this obstacle, we set |
* the error bit of the page to 1 when an error occurs, and make nfs_readpage |
* transmit requests synchronously when encountering this. |
* |
* Another possible solution to this problem may be to have a cache of recent |
* RPC call results indexed by page pointer, or even a result code field |
* in struct page. |
* |
* June 96: Added retries of RPCs that seem to have failed for a transient |
* reason. |
*/ |
|
#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/nfsiod.h> |
#include <linux/malloc.h> |
#include <linux/pagemap.h> |
|
#include <asm/segment.h> |
#include <asm/system.h> |
|
#undef DEBUG_BIO |
#ifdef DEBUG_BIO |
#define dprintk(args...) printk(## args) |
#else |
#define dprintk(args...) /* nothing */ |
#endif |
|
static inline int |
do_read_nfs_sync(struct inode * inode, struct page * page) |
{ |
struct nfs_fattr fattr; |
int result, refresh = 0; |
int count = PAGE_SIZE; |
int rsize = NFS_SERVER(inode)->rsize; |
char *buf = (char *) page_address(page); |
unsigned long pos = page->offset; |
|
dprintk("NFS: do_read_nfs_sync(%p)\n", page); |
|
set_bit(PG_locked, &page->flags); |
clear_bit(PG_error, &page->flags); |
|
do { |
if (count < rsize) |
rsize = count; |
result = nfs_proc_read(NFS_SERVER(inode), NFS_FH(inode), |
pos, rsize, buf, &fattr); |
dprintk("nfs_proc_read(%s, (%x,%lx), %ld, %d, %p) = %d\n", |
NFS_SERVER(inode)->hostname, |
inode->i_dev, inode->i_ino, |
pos, rsize, buf, result); |
/* |
* Even if we had a partial success we can't mark the page |
* cache valid. |
*/ |
if (result < 0) |
goto io_error; |
refresh = 1; |
count -= result; |
pos += result; |
buf += result; |
if (result < rsize) |
break; |
} while (count); |
|
memset(buf, 0, count); |
set_bit(PG_uptodate, &page->flags); |
result = 0; |
|
io_error: |
if (refresh) |
nfs_refresh_inode(inode, &fattr); |
clear_bit(PG_locked, &page->flags); |
wake_up(&page->wait); |
return result; |
} |
|
/* |
* This is the function to (re-) transmit an NFS readahead request |
*/ |
static int |
nfsiod_read_setup(struct nfsiod_req *req) |
{ |
struct inode *inode = req->rq_inode; |
struct page *page = req->rq_page; |
|
return nfs_proc_read_request(&req->rq_rpcreq, |
NFS_SERVER(inode), NFS_FH(inode), |
page->offset, PAGE_SIZE, |
(__u32 *) page_address(page)); |
} |
|
/* |
* This is the callback from nfsiod telling us whether a reply was |
* received or some error occurred (timeout or socket shutdown). |
*/ |
static int |
nfsiod_read_result(int result, struct nfsiod_req *req) |
{ |
struct nfs_server *server = NFS_SERVER(req->rq_inode); |
struct page *page = req->rq_page; |
static int succ = 0, fail = 0; |
int i; |
|
dprintk("BIO: received callback for page %p, result %d\n", |
page, result); |
|
if (result >= 0) { |
struct nfs_fattr fattr; |
|
result = nfs_proc_read_reply(&req->rq_rpcreq, &fattr); |
if (result >= 0) { |
nfs_refresh_inode(req->rq_inode, &fattr); |
if (result < PAGE_SIZE) |
memset((u8 *) page_address(page)+result, |
0, PAGE_SIZE-result); |
} |
} else |
if (result == -ETIMEDOUT && !(server->flags & NFS_MOUNT_SOFT)) { |
/* XXX: Theoretically, we'd have to increment the initial |
* timeo here; but I'm not going to bother with this now |
* because this old nfsiod stuff will soon die anyway. |
*/ |
result = -EAGAIN; |
} |
|
if (result == -EAGAIN && req->rq_retries--) { |
dprintk("BIO: retransmitting request.\n"); |
memset(&req->rq_rpcreq, 0, sizeof(struct rpc_ioreq)); |
while (rpc_reserve(server->rsock, &req->rq_rpcreq, 1) < 0) |
schedule(); |
current->fsuid = req->rq_fsuid; |
current->fsgid = req->rq_fsgid; |
for (i = 0; i < NGROUPS; i++) |
current->groups[i] = req->rq_groups[i]; |
nfsiod_read_setup(req); |
return 0; |
} |
if (result >= 0) { |
set_bit(PG_uptodate, &page->flags); |
succ++; |
} else { |
dprintk("BIO: %d successful reads, %d failures\n", succ, fail); |
set_bit(PG_error, &page->flags); |
fail++; |
} |
clear_bit(PG_locked, &page->flags); |
wake_up(&page->wait); |
free_page(page_address(page)); |
return 1; |
} |
|
static inline int |
do_read_nfs_async(struct inode *inode, struct page *page) |
{ |
struct nfsiod_req *req; |
int result, i; |
|
dprintk("NFS: do_read_nfs_async(%p)\n", page); |
|
set_bit(PG_locked, &page->flags); |
clear_bit(PG_error, &page->flags); |
|
if (!(req = nfsiod_reserve(NFS_SERVER(inode)))) |
return -EAGAIN; |
|
req->rq_retries = 5; |
req->rq_callback = nfsiod_read_result; |
req->rq_inode = inode; |
req->rq_page = page; |
|
req->rq_fsuid = current->fsuid; |
req->rq_fsgid = current->fsgid; |
for (i = 0; i < NGROUPS; i++) |
req->rq_groups[i] = current->groups[i]; |
|
if ((result = nfsiod_read_setup(req)) >= 0) { |
page->count++; |
nfsiod_enqueue(req); |
} else { |
dprintk("NFS: deferring async READ request.\n"); |
nfsiod_release(req); |
clear_bit(PG_locked, &page->flags); |
wake_up(&page->wait); |
} |
|
return result < 0? result : 0; |
} |
|
int |
nfs_readpage(struct inode *inode, struct page *page) |
{ |
unsigned long address; |
int error = -1; |
|
/* In case we're called from a page fault we want to */ |
/* make sure we're runnable before we schedule.. */ |
current->state = TASK_RUNNING; |
dprintk("NFS: nfs_readpage %08lx\n", page_address(page)); |
address = page_address(page); |
page->count++; |
if (!PageError(page) && NFS_SERVER(inode)->rsize >= PAGE_SIZE) |
error = do_read_nfs_async(inode, page); |
if (error < 0) /* couldn't enqueue */ |
error = do_read_nfs_sync(inode, page); |
free_page(address); |
return error; |
} |
/nfsroot.c
0,0 → 1,1724
/* |
* linux/fs/nfs/nfsroot.c -- version 2.3 |
* |
* Copyright (C) 1995, 1996 Gero Kuhlmann <gero@gkminix.han.de> |
* |
* For parts of this file: |
* Copyright (C) 1996 Martin Mares <mj@k332.feld.cvut.cz> |
* |
* Allow an NFS filesystem to be mounted as root. The way this works is: |
* (1) Determine the local IP address via RARP or BOOTP or from the |
* kernel command line. |
* (2) Handle RPC negotiation with the system which replied to RARP or |
* was reported as a boot server by BOOTP or manually. |
* (3) The actual mounting is done later, when init() is running. |
* |
* |
* Changes: |
* |
* R. Drahtmueller : Set IFF_MULTICAST in dev->flags if applicable. |
* Alan Cox : Removed get_address name clash with FPU. |
* Alan Cox : Reformatted a bit. |
* Gero Kuhlmann : Code cleanup |
* Michael Rausch : Fixed recognition of an incoming RARP answer. |
* Martin Mares : (2.0) Auto-configuration via BOOTP supported. |
* Martin Mares : Manual selection of interface & BOOTP/RARP. |
* Martin Mares : Using network routes instead of host routes, |
* allowing the default configuration to be used |
* for normal operation of the host. |
* Martin Mares : Randomized timer with exponential backoff |
* installed to minimize network congestion. |
* Martin Mares : Code cleanup. |
* Martin Mares : (2.1) BOOTP and RARP made configuration options. |
* Martin Mares : Server hostname generation fixed. |
* Gerd Knorr : Fixed wired inode handling |
* Martin Mares : (2.2) "0.0.0.0" addresses from command line ignored. |
* Martin Mares : RARP replies not tested for server address. |
* Gero Kuhlmann : (2.3) Some bug fixes and code cleanup again (please |
* send me your new patches _before_ bothering |
* Linus so that I don' always have to cleanup |
* _afterwards_ - thanks) |
* Gero Kuhlmann : Last changes of Martin Mares undone. |
* Gero Kuhlmann : RARP replies are tested for specified server |
* again. However, it's now possible to have |
* different RARP and NFS servers. |
* Gero Kuhlmann : "0.0.0.0" addresses from command line are |
* now mapped to INADDR_NONE. |
* Gero Kuhlmann : Fixed a bug which prevented BOOTP path name |
* from being used (thanks to Leo Spiekman) |
* Andy Walker : Allow to specify the NFS server in nfs_root |
* without giving a path name |
* Swen Th=FCmmler : Allow to specify the NFS options in nfs_root |
* without giving a path name. Fix BOOTP request |
* for domainname (domainname is NIS domain, not |
* DNS domain!). Skip dummy devices for BOOTP. |
* Jacek Zapala : Fixed a bug which prevented server-ip address |
* from nfsroot parameter from being used. |
* |
*/ |
|
|
/* Define this to allow debugging output */ |
#undef NFSROOT_DEBUG |
#undef NFSROOT_BOOTP_DEBUG |
|
|
#include <linux/config.h> |
#include <linux/types.h> |
#include <linux/string.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/fs.h> |
#include <linux/random.h> |
#include <linux/fcntl.h> |
|
#include <asm/param.h> |
#include <linux/utsname.h> |
#include <linux/in.h> |
#include <linux/if.h> |
#include <linux/inet.h> |
#include <linux/net.h> |
#include <linux/netdevice.h> |
#include <linux/if_arp.h> |
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) |
#include <net/ax25.h> /* For AX25_P_IP */ |
#endif |
#include <linux/skbuff.h> |
#include <linux/socket.h> |
#include <linux/route.h> |
#include <linux/nfs.h> |
#include <linux/nfs_fs.h> |
#include <linux/nfs_mount.h> |
#include <linux/in.h> |
#include <net/route.h> |
#include <net/sock.h> |
|
#include <asm/segment.h> |
|
/* Range of privileged ports */ |
#define STARTPORT 600 |
#define ENDPORT 1023 |
#define NPORTS (ENDPORT - STARTPORT + 1) |
|
|
/* Define the timeout for waiting for a RARP/BOOTP reply */ |
#define CONF_BASE_TIMEOUT (HZ*5) /* Initial timeout: 5 seconds */ |
#define CONF_RETRIES 10 /* 10 retries */ |
#define CONF_TIMEOUT_RANDOM (HZ) /* Maximum amount of randomization */ |
#define CONF_TIMEOUT_MULT *5/4 /* Speed of timeout growth */ |
#define CONF_TIMEOUT_MAX (HZ*30) /* Maximum allowed timeout */ |
|
|
/* List of open devices */ |
struct open_dev { |
struct device *dev; |
unsigned short old_flags; |
struct open_dev *next; |
}; |
|
static struct open_dev *open_base = NULL; |
|
|
/* IP configuration */ |
static struct device *root_dev = NULL; /* Device selected for booting */ |
static char user_dev_name[IFNAMSIZ]; /* Name of user-selected boot device */ |
static struct sockaddr_in myaddr; /* My IP address */ |
static struct sockaddr_in server; /* Server IP address */ |
static struct sockaddr_in gateway; /* Gateway IP address */ |
static struct sockaddr_in netmask; /* Netmask for local subnet */ |
|
|
/* BOOTP/RARP variables */ |
static int bootp_flag; /* User said: Use BOOTP! */ |
static int rarp_flag; /* User said: Use RARP! */ |
static int bootp_dev_count = 0; /* Number of devices allowing BOOTP */ |
static int rarp_dev_count = 0; /* Number of devices allowing RARP */ |
static struct sockaddr_in rarp_serv; /* IP address of RARP server */ |
|
#if defined(CONFIG_RNFS_BOOTP) || defined(CONFIG_RNFS_RARP) |
#define CONFIG_RNFS_DYNAMIC /* Enable dynamic IP config */ |
static volatile int pkt_arrived; /* BOOTP/RARP packet detected */ |
|
#define ARRIVED_BOOTP 1 |
#define ARRIVED_RARP 2 |
#endif |
|
|
/* NFS-related data */ |
static struct nfs_mount_data nfs_data; /* NFS mount info */ |
static char nfs_path[NFS_MAXPATHLEN] = ""; /* Name of directory to mount */ |
static int nfs_port; /* Port to connect to for NFS */ |
|
|
/* Yes, we use sys_socket, but there's no include file for it */ |
extern asmlinkage int sys_socket(int family, int type, int protocol); |
|
|
|
/*************************************************************************** |
|
Device Handling Subroutines |
|
***************************************************************************/ |
|
/* |
* Setup and initialize all network devices. If there is a user-preferred |
* interface, ignore all other interfaces. |
*/ |
static int root_dev_open(void) |
{ |
struct open_dev *openp, **last; |
struct device *dev; |
unsigned short old_flags; |
|
last = &open_base; |
for (dev = dev_base; dev != NULL; dev = dev->next) { |
if (dev->type < ARPHRD_SLIP && |
dev->family == AF_INET && |
!(dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) && |
(0 != strncmp(dev->name, "dummy", 5)) && |
(!user_dev_name[0] || !strcmp(dev->name, user_dev_name))) { |
/* First up the interface */ |
old_flags = dev->flags; |
#ifdef CONFIG_IP_MULTICAST |
dev->flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING | IFF_MULTICAST; |
#else |
dev->flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING; |
#endif |
if (!(old_flags & IFF_UP) && dev_open(dev)) { |
dev->flags = old_flags; |
continue; |
} |
openp = (struct open_dev *) kmalloc(sizeof(struct open_dev), |
GFP_ATOMIC); |
if (openp == NULL) |
continue; |
openp->dev = dev; |
openp->old_flags = old_flags; |
*last = openp; |
last = &openp->next; |
bootp_dev_count++; |
if (!(dev->flags & IFF_NOARP)) |
rarp_dev_count++; |
#ifdef NFSROOT_DEBUG |
printk(KERN_NOTICE "Root-NFS: Opened %s\n", dev->name); |
#endif |
} |
} |
*last = NULL; |
|
if (!bootp_dev_count && !rarp_dev_count) { |
printk(KERN_ERR "Root-NFS: Unable to open at least one network device\n"); |
return -1; |
} |
return 0; |
} |
|
|
/* |
* Restore the state of all devices. However, keep the root device open |
* for the upcoming mount. |
*/ |
static void root_dev_close(void) |
{ |
struct open_dev *openp; |
struct open_dev *nextp; |
|
openp = open_base; |
while (openp != NULL) { |
nextp = openp->next; |
openp->next = NULL; |
if (openp->dev != root_dev) { |
if (!(openp->old_flags & IFF_UP)) |
dev_close(openp->dev); |
openp->dev->flags = openp->old_flags; |
} |
kfree_s(openp, sizeof(struct open_dev)); |
openp = nextp; |
} |
} |
|
|
|
/*************************************************************************** |
|
RARP Subroutines |
|
***************************************************************************/ |
|
#ifdef CONFIG_RNFS_RARP |
|
extern void arp_send(int type, int ptype, unsigned long target_ip, |
struct device *dev, unsigned long src_ip, |
unsigned char *dest_hw, unsigned char *src_hw, |
unsigned char *target_hw); |
|
static int root_rarp_recv(struct sk_buff *skb, struct device *dev, |
struct packet_type *pt); |
|
|
static struct packet_type rarp_packet_type = { |
0, /* Should be: __constant_htons(ETH_P_RARP) |
* - but this _doesn't_ come out constant! */ |
NULL, /* Listen to all devices */ |
root_rarp_recv, |
NULL, |
NULL |
}; |
|
|
/* |
* Register the packet type for RARP |
*/ |
static void root_rarp_open(void) |
{ |
rarp_packet_type.type = htons(ETH_P_RARP); |
dev_add_pack(&rarp_packet_type); |
} |
|
|
/* |
* Deregister the RARP packet type |
*/ |
static void root_rarp_close(void) |
{ |
rarp_packet_type.type = htons(ETH_P_RARP); |
dev_remove_pack(&rarp_packet_type); |
} |
|
|
/* |
* Receive RARP packets. |
*/ |
static int root_rarp_recv(struct sk_buff *skb, struct device *dev, struct packet_type *pt) |
{ |
struct arphdr *rarp = (struct arphdr *)skb->h.raw; |
unsigned char *rarp_ptr = (unsigned char *) (rarp + 1); |
unsigned long sip, tip; |
unsigned char *sha, *tha; /* s for "source", t for "target" */ |
|
/* If this test doesn't pass, it's not IP, or we should ignore it anyway */ |
if (rarp->ar_hln != dev->addr_len || dev->type != ntohs(rarp->ar_hrd)) { |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
|
/* If it's not a RARP reply, delete it. */ |
if (rarp->ar_op != htons(ARPOP_RREPLY)) { |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
|
/* If it's not ethernet or AX25, delete it. */ |
if ((rarp->ar_pro != htons(ETH_P_IP) && dev->type != ARPHRD_AX25) || |
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE) |
(rarp->ar_pro != htons(AX25_P_IP) && dev->type == ARPHRD_AX25) || |
#endif |
rarp->ar_pln != 4) { |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
|
/* Extract variable width fields */ |
sha = rarp_ptr; |
rarp_ptr += dev->addr_len; |
memcpy(&sip, rarp_ptr, 4); |
rarp_ptr += 4; |
tha = rarp_ptr; |
rarp_ptr += dev->addr_len; |
memcpy(&tip, rarp_ptr, 4); |
|
/* Discard packets which are not meant for us. */ |
if (memcmp(tha, dev->dev_addr, dev->addr_len)) { |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
/* Discard packets which are not from specified server. */ |
if (rarp_flag && !bootp_flag && |
rarp_serv.sin_addr.s_addr != INADDR_NONE && |
rarp_serv.sin_addr.s_addr != sip) { |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
|
/* |
* The packet is what we were looking for. Setup the global |
* variables. |
*/ |
cli(); |
if (pkt_arrived) { |
sti(); |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
pkt_arrived = ARRIVED_RARP; |
sti(); |
root_dev = dev; |
|
if (myaddr.sin_addr.s_addr == INADDR_NONE) { |
myaddr.sin_family = dev->family; |
myaddr.sin_addr.s_addr = tip; |
} |
if (server.sin_addr.s_addr == INADDR_NONE) { |
server.sin_family = dev->family; |
server.sin_addr.s_addr = sip; |
} |
kfree_skb(skb, FREE_READ); |
return 0; |
} |
|
|
/* |
* Send RARP request packet over all devices which allow RARP. |
*/ |
static void root_rarp_send(void) |
{ |
struct open_dev *openp; |
struct device *dev; |
int num = 0; |
|
for (openp = open_base; openp != NULL; openp = openp->next) { |
dev = openp->dev; |
if (!(dev->flags & IFF_NOARP)) { |
arp_send(ARPOP_RREQUEST, ETH_P_RARP, 0, dev, 0, NULL, |
dev->dev_addr, dev->dev_addr); |
num++; |
} |
} |
} |
#endif |
|
|
|
/*************************************************************************** |
|
BOOTP Subroutines |
|
***************************************************************************/ |
|
#ifdef CONFIG_RNFS_BOOTP |
|
static struct device *bootp_dev = NULL; /* Device selected as best BOOTP target */ |
|
static int bootp_xmit_fd = -1; /* Socket descriptor for transmit */ |
static struct socket *bootp_xmit_sock; /* The socket itself */ |
static int bootp_recv_fd = -1; /* Socket descriptor for receive */ |
static struct socket *bootp_recv_sock; /* The socket itself */ |
|
struct bootp_pkt { /* BOOTP packet format */ |
u8 op; /* 1=request, 2=reply */ |
u8 htype; /* HW address type */ |
u8 hlen; /* HW address length */ |
u8 hops; /* Used only by gateways */ |
u32 xid; /* Transaction ID */ |
u16 secs; /* Seconds since we started */ |
u16 flags; /* Just what is says */ |
u32 client_ip; /* Client's IP address if known */ |
u32 your_ip; /* Assigned IP address */ |
u32 server_ip; /* Server's IP address */ |
u32 relay_ip; /* IP address of BOOTP relay */ |
u8 hw_addr[16]; /* Client's HW address */ |
u8 serv_name[64]; /* Server host name */ |
u8 boot_file[128]; /* Name of boot file */ |
u8 vendor_area[128]; /* Area for extensions */ |
}; |
|
#define BOOTP_REQUEST 1 |
#define BOOTP_REPLY 2 |
|
static struct bootp_pkt *xmit_bootp; /* Packet being transmitted */ |
static struct bootp_pkt *recv_bootp; /* Packet being received */ |
|
static int bootp_have_route = 0; /* BOOTP route installed */ |
|
|
/* |
* Free BOOTP packet buffers |
*/ |
static void root_free_bootp(void) |
{ |
if (xmit_bootp) { |
kfree_s(xmit_bootp, sizeof(struct bootp_pkt)); |
xmit_bootp = NULL; |
} |
if (recv_bootp) { |
kfree_s(recv_bootp, sizeof(struct bootp_pkt)); |
recv_bootp = NULL; |
} |
} |
|
|
/* |
* Allocate memory for BOOTP packet buffers |
*/ |
static inline int root_alloc_bootp(void) |
{ |
if (!(xmit_bootp = kmalloc(sizeof(struct bootp_pkt), GFP_KERNEL)) || |
!(recv_bootp = kmalloc(sizeof(struct bootp_pkt), GFP_KERNEL))) { |
printk("BOOTP: Out of memory!"); |
return -1; |
} |
return 0; |
} |
|
|
/* |
* Create default route for BOOTP sending |
*/ |
static int root_add_bootp_route(void) |
{ |
struct rtentry route; |
|
memset(&route, 0, sizeof(route)); |
route.rt_dev = bootp_dev->name; |
route.rt_mss = bootp_dev->mtu; |
route.rt_flags = RTF_UP; |
((struct sockaddr_in *) &(route.rt_dst)) -> sin_addr.s_addr = 0; |
((struct sockaddr_in *) &(route.rt_dst)) -> sin_family = AF_INET; |
((struct sockaddr_in *) &(route.rt_genmask)) -> sin_addr.s_addr = 0; |
((struct sockaddr_in *) &(route.rt_genmask)) -> sin_family = AF_INET; |
if (ip_rt_new(&route)) { |
printk(KERN_ERR "BOOTP: Adding of route failed!\n"); |
return -1; |
} |
bootp_have_route = 1; |
return 0; |
} |
|
|
/* |
* Delete default route for BOOTP sending |
*/ |
static int root_del_bootp_route(void) |
{ |
struct rtentry route; |
|
if (!bootp_have_route) |
return 0; |
memset(&route, 0, sizeof(route)); |
((struct sockaddr_in *) &(route.rt_dst)) -> sin_addr.s_addr = 0; |
((struct sockaddr_in *) &(route.rt_genmask)) -> sin_addr.s_addr = 0; |
if (ip_rt_kill(&route)) { |
printk(KERN_ERR "BOOTP: Deleting of route failed!\n"); |
return -1; |
} |
bootp_have_route = 0; |
return 0; |
} |
|
|
/* |
* Open UDP socket. |
*/ |
static int root_open_udp_sock(int *fd, struct socket **sock) |
{ |
struct file *file; |
struct inode *inode; |
|
*fd = sys_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
if (*fd >= 0) { |
file = current->files->fd[*fd]; |
inode = file->f_inode; |
*sock = &inode->u.socket_i; |
return 0; |
} |
|
printk(KERN_ERR "BOOTP: Cannot open UDP socket!\n"); |
return -1; |
} |
|
|
/* |
* Connect UDP socket. |
*/ |
static int root_connect_udp_sock(struct socket *sock, u32 addr, u16 port) |
{ |
struct sockaddr_in sa; |
int result; |
|
sa.sin_family = AF_INET; |
sa.sin_addr.s_addr = htonl(addr); |
sa.sin_port = htons(port); |
result = sock->ops->connect(sock, (struct sockaddr *) &sa, sizeof(sa), 0); |
if (result < 0) { |
printk(KERN_ERR "BOOTP: connect() failed\n"); |
return -1; |
} |
return 0; |
} |
|
|
/* |
* Bind UDP socket. |
*/ |
static int root_bind_udp_sock(struct socket *sock, u32 addr, u16 port) |
{ |
struct sockaddr_in sa; |
int result; |
|
sa.sin_family = AF_INET; |
sa.sin_addr.s_addr = htonl(addr); |
sa.sin_port = htons(port); |
result = sock->ops->bind(sock, (struct sockaddr *) &sa, sizeof(sa)); |
if (result < 0) { |
printk(KERN_ERR "BOOTP: bind() failed\n"); |
return -1; |
} |
return 0; |
} |
|
|
/* |
* Send UDP packet. |
*/ |
static inline int root_send_udp(struct socket *sock, void *buf, int size) |
{ |
u32 oldfs; |
int result; |
struct msghdr msg; |
struct iovec iov; |
|
oldfs = get_fs(); |
set_fs(get_ds()); |
iov.iov_base = buf; |
iov.iov_len = size; |
msg.msg_name = NULL; |
msg.msg_iov = &iov; |
msg.msg_iovlen = 1; |
msg.msg_control = NULL; |
result = sock->ops->sendmsg(sock, &msg, size, 0, 0); |
set_fs(oldfs); |
return (result != size); |
} |
|
|
/* |
* Try to receive UDP packet. |
*/ |
static inline int root_recv_udp(struct socket *sock, void *buf, int size) |
{ |
u32 oldfs; |
int result; |
struct msghdr msg; |
struct iovec iov; |
|
oldfs = get_fs(); |
set_fs(get_ds()); |
iov.iov_base = buf; |
iov.iov_len = size; |
msg.msg_name = NULL; |
msg.msg_iov = &iov; |
msg.msg_iovlen = 1; |
msg.msg_control = NULL; |
msg.msg_namelen = 0; |
result = sock->ops->recvmsg(sock, &msg, size, O_NONBLOCK, 0, &msg.msg_namelen); |
set_fs(oldfs); |
return result; |
} |
|
|
/* |
* Initialize BOOTP extension fields in the request. |
*/ |
static void root_bootp_init_ext(u8 *e) |
{ |
*e++ = 99; /* RFC1048 Magic Cookie */ |
*e++ = 130; |
*e++ = 83; |
*e++ = 99; |
*e++ = 1; /* Subnet mask request */ |
*e++ = 4; |
e += 4; |
*e++ = 3; /* Default gateway request */ |
*e++ = 4; |
e += 4; |
*e++ = 12; /* Host name request */ |
*e++ = 32; |
e += 32; |
*e++ = 40; /* NIS Domain name request */ |
*e++ = 32; |
e += 32; |
*e++ = 17; /* Boot path */ |
*e++ = 32; |
e += 32; |
*e = 255; /* End of the list */ |
} |
|
|
/* |
* Deinitialize the BOOTP mechanism. |
*/ |
static void root_bootp_close(void) |
{ |
if (bootp_xmit_fd != -1) |
sys_close(bootp_xmit_fd); |
if (bootp_recv_fd != -1) |
sys_close(bootp_recv_fd); |
root_del_bootp_route(); |
root_free_bootp(); |
} |
|
|
/* |
* Initialize the BOOTP mechanism. |
*/ |
static int root_bootp_open(void) |
{ |
struct open_dev *openp; |
struct device *dev, *best_dev; |
|
/* |
* Select the best interface for BOOTP. We try to select a first |
* Ethernet-like interface. It's shame I know no simple way how to send |
* BOOTP's to all interfaces, but it doesn't apply to usual diskless |
* stations as they don't have multiple interfaces. |
*/ |
|
best_dev = NULL; |
for (openp = open_base; openp != NULL; openp = openp->next) { |
dev = openp->dev; |
if (dev->flags & IFF_BROADCAST) { |
if (!best_dev || |
((best_dev->flags & IFF_NOARP) && !(dev->flags & IFF_NOARP))) |
best_dev = dev; |
} |
} |
|
if (!best_dev) { |
printk(KERN_ERR "BOOTP: This cannot happen!\n"); |
return -1; |
} |
bootp_dev = best_dev; |
|
/* Allocate memory for BOOTP packets */ |
if (root_alloc_bootp()) |
return -1; |
|
/* Construct BOOTP request */ |
memset(xmit_bootp, 0, sizeof(struct bootp_pkt)); |
xmit_bootp->op = BOOTP_REQUEST; |
get_random_bytes(&xmit_bootp->xid, sizeof(xmit_bootp->xid)); |
xmit_bootp->htype = best_dev->type; |
xmit_bootp->hlen = best_dev->addr_len; |
memcpy(xmit_bootp->hw_addr, best_dev->dev_addr, best_dev->addr_len); |
root_bootp_init_ext(xmit_bootp->vendor_area); |
#ifdef NFSROOT_BOOTP_DEBUG |
{ |
int x; |
printk(KERN_NOTICE "BOOTP: XID=%08x, DE=%s, HT=%02x, HL=%02x, HA=", |
xmit_bootp->xid, |
best_dev->name, |
xmit_bootp->htype, |
xmit_bootp->hlen); |
for(x=0; x<xmit_bootp->hlen; x++) |
printk("%02x", xmit_bootp->hw_addr[x]); |
printk("\n"); |
} |
#endif |
|
/* Create default route to that interface */ |
if (root_add_bootp_route()) |
return -1; |
|
/* Open the sockets */ |
if (root_open_udp_sock(&bootp_xmit_fd, &bootp_xmit_sock) || |
root_open_udp_sock(&bootp_recv_fd, &bootp_recv_sock)) |
return -1; |
|
/* Bind/connect the sockets */ |
((struct sock *) bootp_xmit_sock->data) -> broadcast = 1; |
((struct sock *) bootp_xmit_sock->data) -> reuse = 1; |
((struct sock *) bootp_recv_sock->data) -> reuse = 1; |
if (root_bind_udp_sock(bootp_recv_sock, INADDR_ANY, 68) || |
root_bind_udp_sock(bootp_xmit_sock, INADDR_ANY, 68) || |
root_connect_udp_sock(bootp_xmit_sock, INADDR_BROADCAST, 67)) |
return -1; |
|
return 0; |
} |
|
|
/* |
* Send BOOTP request. |
*/ |
static int root_bootp_send(u32 jiffies) |
{ |
xmit_bootp->secs = htons(jiffies / HZ); |
return root_send_udp(bootp_xmit_sock, xmit_bootp, sizeof(struct bootp_pkt)); |
} |
|
|
/* |
* Copy BOOTP-supplied string if not already set. |
*/ |
static int root_bootp_string(char *dest, char *src, int len, int max) |
{ |
if (*dest || !len) |
return 0; |
if (len > max-1) |
len = max-1; |
strncpy(dest, src, len); |
dest[len] = '\0'; |
return 1; |
} |
|
|
/* |
* Process BOOTP extension. |
*/ |
static void root_do_bootp_ext(u8 *ext) |
{ |
#ifdef NFSROOT_BOOTP_DEBUG |
u8 *c; |
|
printk("BOOTP: Got extension %02x",*ext); |
for(c=ext+2; c<ext+2+ext[1]; c++) |
printk(" %02x", *c); |
printk("\n"); |
#endif |
|
switch (*ext++) { |
case 1: /* Subnet mask */ |
if (netmask.sin_addr.s_addr == INADDR_NONE) |
memcpy(&netmask.sin_addr.s_addr, ext+1, 4); |
break; |
case 3: /* Default gateway */ |
if (gateway.sin_addr.s_addr == INADDR_NONE) |
memcpy(&gateway.sin_addr.s_addr, ext+1, 4); |
break; |
case 12: /* Host name */ |
root_bootp_string(system_utsname.nodename, ext+1, *ext, __NEW_UTS_LEN); |
break; |
case 40: /* NIS Domain name */ |
root_bootp_string(system_utsname.domainname, ext+1, *ext, __NEW_UTS_LEN); |
break; |
case 17: /* Root path */ |
root_bootp_string(nfs_path, ext+1, *ext, NFS_MAXPATHLEN); |
break; |
} |
} |
|
|
/* |
* Receive BOOTP request. |
*/ |
static void root_bootp_recv(void) |
{ |
int len; |
u8 *ext, *end, *opt; |
|
len = root_recv_udp(bootp_recv_sock, recv_bootp, sizeof(struct bootp_pkt)); |
if (len < 0) |
return; |
|
/* Check consistency of incoming packet */ |
if (len < 300 || /* See RFC 1542:2.1 */ |
recv_bootp->op != BOOTP_REPLY || |
recv_bootp->htype != xmit_bootp->htype || |
recv_bootp->hlen != xmit_bootp->hlen || |
recv_bootp->xid != xmit_bootp->xid) { |
#ifdef NFSROOT_BOOTP_DEBUG |
printk("?"); |
#endif |
return; |
} |
|
/* Record BOOTP packet arrival in the global variables */ |
cli(); |
if (pkt_arrived) { |
sti(); |
return; |
} |
pkt_arrived = ARRIVED_BOOTP; |
sti(); |
root_dev = bootp_dev; |
|
/* Extract basic fields */ |
myaddr.sin_addr.s_addr = recv_bootp->your_ip; |
if (server.sin_addr.s_addr==INADDR_NONE) |
server.sin_addr.s_addr = recv_bootp->server_ip; |
|
/* Parse extensions */ |
if (recv_bootp->vendor_area[0] == 99 && /* Check magic cookie */ |
recv_bootp->vendor_area[1] == 130 && |
recv_bootp->vendor_area[2] == 83 && |
recv_bootp->vendor_area[3] == 99) { |
ext = &recv_bootp->vendor_area[4]; |
end = (u8 *) recv_bootp + len; |
while (ext < end && *ext != 255) { |
if (*ext == 0) /* Padding */ |
ext++; |
else { |
opt = ext; |
ext += ext[1] + 2; |
if (ext <= end) |
root_do_bootp_ext(opt); |
} |
} |
} |
} |
#endif |
|
|
|
/*************************************************************************** |
|
Dynamic configuration of IP. |
|
***************************************************************************/ |
|
#ifdef CONFIG_RNFS_DYNAMIC |
|
/* |
* Determine client and server IP numbers and appropriate device by using |
* the RARP and BOOTP protocols. |
*/ |
static int root_auto_config(void) |
{ |
int retries; |
unsigned long timeout, jiff; |
unsigned long start_jiffies; |
|
/* |
* If neither BOOTP nor RARP was selected, return with an error. This |
* routine gets only called when some pieces of information are mis- |
* sing, and without BOOTP and RARP we are not able to get that in- |
* formation. |
*/ |
if (!bootp_flag && !rarp_flag) { |
printk(KERN_ERR "Root-NFS: Neither RARP nor BOOTP selected.\n"); |
return -1; |
} |
|
#ifdef CONFIG_RNFS_BOOTP |
if (bootp_flag && !bootp_dev_count) { |
printk(KERN_ERR "Root-NFS: No suitable device for BOOTP found.\n"); |
bootp_flag = 0; |
} |
#else |
bootp_flag = 0; |
#endif |
|
#ifdef CONFIG_RNFS_RARP |
if (rarp_flag && !rarp_dev_count) { |
printk(KERN_ERR "Root-NFS: No suitable device for RARP found.\n"); |
rarp_flag = 0; |
} |
#else |
rarp_flag = 0; |
#endif |
|
if (!bootp_flag && !rarp_flag) |
/* Error message already printed */ |
return -1; |
|
/* |
* Setup RARP and BOOTP protocols |
*/ |
#ifdef CONFIG_RNFS_RARP |
if (rarp_flag) |
root_rarp_open(); |
#endif |
#ifdef CONFIG_RNFS_BOOTP |
if (bootp_flag && root_bootp_open() < 0) { |
root_bootp_close(); |
return -1; |
} |
#endif |
|
/* |
* Send requests and wait, until we get an answer. This loop |
* seems to be a terrible waste of CPU time, but actually there is |
* only one process running at all, so we don't need to use any |
* scheduler functions. |
* [Actually we could now, but the nothing else running note still |
* applies.. - AC] |
*/ |
printk(KERN_NOTICE "Sending %s%s%s requests...", |
bootp_flag ? "BOOTP" : "", |
bootp_flag && rarp_flag ? " and " : "", |
rarp_flag ? "RARP" : ""); |
start_jiffies = jiffies; |
retries = CONF_RETRIES; |
get_random_bytes(&timeout, sizeof(timeout)); |
timeout = CONF_BASE_TIMEOUT + (timeout % (unsigned) CONF_TIMEOUT_RANDOM); |
for(;;) { |
#ifdef CONFIG_RNFS_BOOTP |
if (bootp_flag && root_bootp_send(jiffies - start_jiffies) < 0) { |
printk(" BOOTP failed!\n"); |
root_bootp_close(); |
bootp_flag = 0; |
if (!rarp_flag) |
break; |
} |
#endif |
#ifdef CONFIG_RNFS_RARP |
if (rarp_flag) |
root_rarp_send(); |
#endif |
printk("."); |
jiff = jiffies + timeout; |
while (jiffies < jiff && !pkt_arrived) |
#ifdef CONFIG_RNFS_BOOTP |
root_bootp_recv(); |
#else |
; |
#endif |
if (pkt_arrived) |
break; |
if (! --retries) { |
printk(" timed out!\n"); |
break; |
} |
timeout = timeout CONF_TIMEOUT_MULT; |
if (timeout > CONF_TIMEOUT_MAX) |
timeout = CONF_TIMEOUT_MAX; |
} |
|
#ifdef CONFIG_RNFS_RARP |
if (rarp_flag) |
root_rarp_close(); |
#endif |
#ifdef CONFIG_RNFS_BOOTP |
if (bootp_flag) |
root_bootp_close(); |
#endif |
|
if (!pkt_arrived) |
return -1; |
|
printk(" OK\n"); |
printk(KERN_NOTICE "Root-NFS: Got %s answer from %s, ", |
(pkt_arrived == ARRIVED_BOOTP) ? "BOOTP" : "RARP", |
in_ntoa(server.sin_addr.s_addr)); |
printk("my address is %s\n", in_ntoa(myaddr.sin_addr.s_addr)); |
|
return 0; |
} |
#endif |
|
|
|
/*************************************************************************** |
|
Parsing of options |
|
***************************************************************************/ |
|
|
/* |
* The following integer options are recognized |
*/ |
static struct nfs_int_opts { |
char *name; |
int *val; |
} root_int_opts[] = { |
{ "port", &nfs_port }, |
{ "rsize", &nfs_data.rsize }, |
{ "wsize", &nfs_data.wsize }, |
{ "timeo", &nfs_data.timeo }, |
{ "retrans", &nfs_data.retrans }, |
{ "acregmin", &nfs_data.acregmin }, |
{ "acregmax", &nfs_data.acregmax }, |
{ "acdirmin", &nfs_data.acdirmin }, |
{ "acdirmax", &nfs_data.acdirmax }, |
{ NULL, NULL } |
}; |
|
|
/* |
* And now the flag options |
*/ |
static struct nfs_bool_opts { |
char *name; |
int and_mask; |
int or_mask; |
} root_bool_opts[] = { |
{ "soft", ~NFS_MOUNT_SOFT, NFS_MOUNT_SOFT }, |
{ "hard", ~NFS_MOUNT_SOFT, 0 }, |
{ "intr", ~NFS_MOUNT_INTR, NFS_MOUNT_INTR }, |
{ "nointr", ~NFS_MOUNT_INTR, 0 }, |
{ "posix", ~NFS_MOUNT_POSIX, NFS_MOUNT_POSIX }, |
{ "noposix", ~NFS_MOUNT_POSIX, 0 }, |
{ "cto", ~NFS_MOUNT_NOCTO, 0 }, |
{ "nocto", ~NFS_MOUNT_NOCTO, NFS_MOUNT_NOCTO }, |
{ "ac", ~NFS_MOUNT_NOAC, 0 }, |
{ "noac", ~NFS_MOUNT_NOAC, NFS_MOUNT_NOAC }, |
{ NULL, 0, 0 } |
}; |
|
|
/* |
* Prepare the NFS data structure and parse any options. This tries to |
* set as many values in the nfs_data structure as known right now. |
*/ |
static int root_nfs_name(char *name) |
{ |
char buf[NFS_MAXPATHLEN]; |
char *cp, *cq, *options, *val; |
int octets = 0; |
|
/* It is possible to override the server IP number here */ |
cp = cq = name; |
while (octets < 4) { |
while (*cp >= '0' && *cp <= '9') |
cp++; |
if (cp == cq || cp - cq > 3) |
break; |
if (*cp == '.' || octets == 3) |
octets++; |
if (octets < 4) |
cp++; |
cq = cp; |
} |
if (octets == 4 && (*cp == ':' || *cp == '\0')) { |
if (*cp == ':') |
*cp++ = '\0'; |
server.sin_addr.s_addr = in_aton(name); |
name = cp; |
} |
|
/* Clear the nfs_data structure and setup the server hostname */ |
memset(&nfs_data, 0, sizeof(nfs_data)); |
strncpy(nfs_data.hostname, in_ntoa(server.sin_addr.s_addr), |
sizeof(nfs_data.hostname)-1); |
|
/* Set the name of the directory to mount */ |
if (nfs_path[0] == '\0' || strncmp(name, "default", 7)) |
strncpy(buf, name, NFS_MAXPATHLEN); |
else |
strncpy(buf, nfs_path, NFS_MAXPATHLEN); |
if ((options = strchr(buf, ','))) |
*options++ = '\0'; |
if (!strcmp(buf, "default")) |
strcpy(buf, NFS_ROOT); |
cp = in_ntoa(myaddr.sin_addr.s_addr); |
if (strlen(buf) + strlen(cp) > NFS_MAXPATHLEN) { |
printk(KERN_ERR "Root-NFS: Pathname for remote directory too long.\n"); |
return -1; |
} |
/* update nfs_path with path from nfsroot=... command line parameter */ |
if (*buf) |
sprintf(nfs_path, buf, cp); |
|
/* Set some default values */ |
nfs_port = -1; |
nfs_data.version = NFS_MOUNT_VERSION; |
nfs_data.flags = 0; |
nfs_data.rsize = NFS_DEF_FILE_IO_BUFFER_SIZE; |
nfs_data.wsize = NFS_DEF_FILE_IO_BUFFER_SIZE; |
nfs_data.timeo = 7; |
nfs_data.retrans = 3; |
nfs_data.acregmin = 3; |
nfs_data.acregmax = 60; |
nfs_data.acdirmin = 30; |
nfs_data.acdirmax = 60; |
|
/* Process any options */ |
if (options) { |
cp = strtok(options, ","); |
while (cp) { |
if ((val = strchr(cp, '='))) { |
struct nfs_int_opts *opts = root_int_opts; |
*val++ = '\0'; |
while (opts->name && strcmp(opts->name, cp)) |
opts++; |
if (opts->name) |
*(opts->val) = (int) simple_strtoul(val, NULL, 10); |
} else { |
struct nfs_bool_opts *opts = root_bool_opts; |
while (opts->name && strcmp(opts->name, cp)) |
opts++; |
if (opts->name) { |
nfs_data.flags &= opts->and_mask; |
nfs_data.flags |= opts->or_mask; |
} |
} |
cp = strtok(NULL, ","); |
} |
} |
return 0; |
} |
|
|
/* |
* Tell the user what's going on. |
*/ |
#ifdef NFSROOT_DEBUG |
static void root_nfs_print(void) |
{ |
#define IN_NTOA(x) (((x) == INADDR_NONE) ? "none" : in_ntoa(x)) |
|
printk(KERN_NOTICE "Root-NFS: IP config: dev=%s, ", |
root_dev ? root_dev->name : "none"); |
printk("local=%s, ", IN_NTOA(myaddr.sin_addr.s_addr)); |
printk("server=%s, ", IN_NTOA(server.sin_addr.s_addr)); |
printk("gw=%s, ", IN_NTOA(gateway.sin_addr.s_addr)); |
printk("mask=%s, ", IN_NTOA(netmask.sin_addr.s_addr)); |
printk("host=%s, domain=%s\n", |
system_utsname.nodename[0] ? system_utsname.nodename : "none", |
system_utsname.domainname[0] ? system_utsname.domainname : "none"); |
printk(KERN_NOTICE "Root-NFS: Mounting %s on server %s as root\n", |
nfs_path, nfs_data.hostname); |
printk(KERN_NOTICE "Root-NFS: rsize = %d, wsize = %d, timeo = %d, retrans = %d\n", |
nfs_data.rsize, nfs_data.wsize, nfs_data.timeo, nfs_data.retrans); |
printk(KERN_NOTICE "Root-NFS: acreg (min,max) = (%d,%d), acdir (min,max) = (%d,%d)\n", |
nfs_data.acregmin, nfs_data.acregmax, |
nfs_data.acdirmin, nfs_data.acdirmax); |
printk(KERN_NOTICE "Root-NFS: port = %d, flags = %08x\n", |
nfs_port, nfs_data.flags); |
|
#undef IN_NTOA |
} |
#endif |
|
|
/* |
* Decode any IP configuration options in the "nfsaddrs" kernel command |
* line parameter. It consists of option fields separated by colons in |
* the following order: |
* |
* <client-ip>:<server-ip>:<gw-ip>:<netmask>:<host name>:<device>:<bootp|rarp> |
* |
* Any of the fields can be empty which means to use a default value: |
* <client-ip> - address given by BOOTP or RARP |
* <server-ip> - address of host returning BOOTP or RARP packet |
* <gw-ip> - none, or the address returned by BOOTP |
* <netmask> - automatically determined from <client-ip>, or the |
* one returned by BOOTP |
* <host name> - <client-ip> in ASCII notation, or the name returned |
* by BOOTP |
* <device> - use all available devices for RARP and the first |
* one for BOOTP |
* <bootp|rarp> - use both protocols to determine my own address |
*/ |
static void root_nfs_addrs(char *addrs) |
{ |
char *cp, *ip, *dp; |
int num = 0; |
|
/* Clear all addresses and strings */ |
myaddr.sin_family = server.sin_family = rarp_serv.sin_family = |
gateway.sin_family = netmask.sin_family = AF_INET; |
myaddr.sin_addr.s_addr = server.sin_addr.s_addr = rarp_serv.sin_addr.s_addr = |
gateway.sin_addr.s_addr = netmask.sin_addr.s_addr = INADDR_NONE; |
system_utsname.nodename[0] = '\0'; |
system_utsname.domainname[0] = '\0'; |
user_dev_name[0] = '\0'; |
bootp_flag = rarp_flag = 1; |
|
/* The following is just a shortcut for automatic IP configuration */ |
if (!strcmp(addrs, "bootp")) { |
rarp_flag = 0; |
return; |
} else if (!strcmp(addrs, "rarp")) { |
bootp_flag = 0; |
return; |
} else if (!strcmp(addrs, "both")) { |
return; |
} |
|
/* Parse the whole string */ |
ip = addrs; |
while (ip && *ip) { |
if ((cp = strchr(ip, ':'))) |
*cp++ = '\0'; |
if (strlen(ip) > 0) { |
#ifdef NFSROOT_DEBUG |
printk(KERN_NOTICE "Root-NFS: Config string num %d is \"%s\"\n", |
num, ip); |
#endif |
switch (num) { |
case 0: |
if ((myaddr.sin_addr.s_addr = in_aton(ip)) == INADDR_ANY) |
myaddr.sin_addr.s_addr = INADDR_NONE; |
break; |
case 1: |
if ((server.sin_addr.s_addr = in_aton(ip)) == INADDR_ANY) |
server.sin_addr.s_addr = INADDR_NONE; |
break; |
case 2: |
if ((gateway.sin_addr.s_addr = in_aton(ip)) == INADDR_ANY) |
gateway.sin_addr.s_addr = INADDR_NONE; |
break; |
case 3: |
if ((netmask.sin_addr.s_addr = in_aton(ip)) == INADDR_ANY) |
netmask.sin_addr.s_addr = INADDR_NONE; |
break; |
case 4: |
if ((dp = strchr(ip, '.'))) { |
*dp++ = '\0'; |
strncpy(system_utsname.domainname, dp, __NEW_UTS_LEN); |
system_utsname.domainname[__NEW_UTS_LEN] = '\0'; |
} |
strncpy(system_utsname.nodename, ip, __NEW_UTS_LEN); |
system_utsname.nodename[__NEW_UTS_LEN] = '\0'; |
break; |
case 5: |
strncpy(user_dev_name, ip, IFNAMSIZ); |
user_dev_name[IFNAMSIZ-1] = '\0'; |
break; |
case 6: |
if (!strcmp(ip, "rarp")) |
bootp_flag = 0; |
else if (!strcmp(ip, "bootp")) |
rarp_flag = 0; |
else if (strcmp(ip, "both")) |
bootp_flag = rarp_flag = 0; |
break; |
default: |
break; |
} |
} |
ip = cp; |
num++; |
} |
rarp_serv = server; |
} |
|
|
/* |
* Set the interface address and configure a route to the server. |
*/ |
static int root_nfs_setup(void) |
{ |
struct rtentry route; |
|
/* Set the default system name in case none was previously found */ |
if (!system_utsname.nodename[0]) { |
strncpy(system_utsname.nodename, in_ntoa(myaddr.sin_addr.s_addr), __NEW_UTS_LEN); |
system_utsname.nodename[__NEW_UTS_LEN] = '\0'; |
} |
|
/* Set the correct netmask */ |
if (netmask.sin_addr.s_addr == INADDR_NONE) |
netmask.sin_addr.s_addr = ip_get_mask(myaddr.sin_addr.s_addr); |
|
/* Setup the device correctly */ |
root_dev->family = myaddr.sin_family; |
root_dev->pa_addr = myaddr.sin_addr.s_addr; |
root_dev->pa_mask = netmask.sin_addr.s_addr; |
root_dev->pa_brdaddr = root_dev->pa_addr | ~root_dev->pa_mask; |
root_dev->pa_dstaddr = 0; |
|
/* |
* Now add a route to the server. If there is no gateway given, |
* the server is on the same subnet, so we establish only a route to |
* the local network. Otherwise we create a route to the gateway (the |
* same local network router as in the former case) and then setup a |
* gatewayed default route. Note that this gives sufficient network |
* setup even for full system operation in all common cases. |
*/ |
memset(&route, 0, sizeof(route)); /* Local subnet route */ |
route.rt_dev = root_dev->name; |
route.rt_mss = root_dev->mtu; |
route.rt_flags = RTF_UP; |
*((struct sockaddr_in *) &(route.rt_dst)) = myaddr; |
(((struct sockaddr_in *) &(route.rt_dst)))->sin_addr.s_addr &= netmask.sin_addr.s_addr; |
*((struct sockaddr_in *) &(route.rt_genmask)) = netmask; |
if (ip_rt_new(&route)) { |
printk(KERN_ERR "Root-NFS: Adding of local route failed!\n"); |
return -1; |
} |
|
if (gateway.sin_addr.s_addr != INADDR_NONE) { /* Default route */ |
(((struct sockaddr_in *) &(route.rt_dst)))->sin_addr.s_addr = INADDR_ANY; |
(((struct sockaddr_in *) &(route.rt_genmask)))->sin_addr.s_addr = INADDR_ANY; |
*((struct sockaddr_in *) &(route.rt_gateway)) = gateway; |
route.rt_flags |= RTF_GATEWAY; |
if ((gateway.sin_addr.s_addr ^ myaddr.sin_addr.s_addr) & netmask.sin_addr.s_addr) { |
printk(KERN_ERR "Root-NFS: Gateway not on local network!\n"); |
return -1; |
} |
if (ip_rt_new(&route)) { |
printk(KERN_ERR "Root-NFS: Adding of default route failed!\n"); |
return -1; |
} |
} else if ((server.sin_addr.s_addr ^ myaddr.sin_addr.s_addr) & netmask.sin_addr.s_addr) { |
printk(KERN_ERR "Root-NFS: Boot server not on local network and no default gateway configured!\n"); |
return -1; |
} |
|
return 0; |
} |
|
|
/* |
* Get the necessary IP addresses and prepare for mounting the required |
* NFS filesystem. |
*/ |
int nfs_root_init(char *nfsname, char *nfsaddrs) |
{ |
/* |
* Decode IP addresses and other configuration info contained |
* in the nfsaddrs string (which came from the kernel command |
* line). |
*/ |
root_nfs_addrs(nfsaddrs); |
|
/* |
* Setup all network devices |
*/ |
if (root_dev_open() < 0) |
return -1; |
|
/* |
* If the config information is insufficient (e.g., our IP address or |
* IP address of the boot server is missing or we have multiple network |
* interfaces and no default was set), use BOOTP or RARP to get the |
* missing values. |
* |
* Note that we don't try to set up correct routes for multiple |
* interfaces (could be solved by trying icmp echo requests), because |
* it's only necessary in the rare case of multiple ethernet devices |
* in the (diskless) system and if the server is on another subnet. |
* If only one interface is installed, the routing is obvious. |
*/ |
if ((myaddr.sin_addr.s_addr == INADDR_NONE || |
server.sin_addr.s_addr == INADDR_NONE || |
(open_base != NULL && open_base->next != NULL)) |
#ifdef CONFIG_RNFS_DYNAMIC |
&& root_auto_config() < 0 |
#endif |
) { |
root_dev_close(); |
return -1; |
} |
if (root_dev == NULL) { |
if (open_base != NULL && open_base->next == NULL) { |
root_dev = open_base->dev; |
} else { |
printk(KERN_ERR "Root-NFS: Multiple devices and no server\n"); |
root_dev_close(); |
return -1; |
} |
} |
|
/* |
* Close all network devices except the device which connects to |
* server |
*/ |
root_dev_close(); |
|
/* |
* Decode the root directory path name and NFS options from |
* the kernel command line. This has to go here in order to |
* be able to use the client IP address for the remote root |
* directory (necessary for pure RARP booting). |
*/ |
if (root_nfs_name(nfsname) < 0) |
return -1; |
|
/* |
* Setup devices and routes. The server directory is actually |
* mounted after init() has been started. |
*/ |
if (root_nfs_setup() < 0) |
return -1; |
|
#ifdef NFSROOT_DEBUG |
root_nfs_print(); |
#endif |
|
return 0; |
} |
|
|
/*************************************************************************** |
|
Routines to actually mount the root directory |
|
***************************************************************************/ |
|
static struct file *nfs_file; /* File descriptor pointing to inode */ |
static struct inode *nfs_sock_inode; /* Inode containing socket */ |
static int *rpc_packet = NULL; /* RPC packet */ |
|
|
/* |
* Open a UDP socket. |
*/ |
static int root_nfs_open(void) |
{ |
/* Open the socket */ |
if ((nfs_data.fd = sys_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { |
printk(KERN_ERR "Root-NFS: Cannot open UDP socket for NFS!\n"); |
return -1; |
} |
nfs_file = current->files->fd[nfs_data.fd]; |
nfs_sock_inode = nfs_file->f_inode; |
return 0; |
} |
|
|
/* |
* Close the UDP file descriptor. If nfs_read_super is successful, it |
* increases the reference count, so we can simply close the file, and |
* the socket keeps open. |
*/ |
static void root_nfs_close(void) |
{ |
/* |
* The following close doesn't touch the server structure, which |
* now contains a file pointer pointing into nowhere. The system |
* _should_ crash as soon as someone tries to select on the root |
* filesystem. Haven't tried it yet - we can still change it back |
* to the old way of keeping a static copy of all important data |
* structures, including their pointers. At least this should be |
* checked out _carefully_ before going into a public release |
* kernel. - GK |
*/ |
sys_close(nfs_data.fd); |
} |
|
|
/* |
* Find a suitable listening port and bind to it |
*/ |
static int root_nfs_bind(void) |
{ |
int res = -1; |
short port = STARTPORT; |
struct sockaddr_in *sin = &myaddr; |
int i; |
|
if (nfs_sock_inode->u.socket_i.ops->bind) { |
for (i = 0; i < NPORTS && res < 0; i++) { |
sin->sin_port = htons(port++); |
if (port > ENDPORT) { |
port = STARTPORT; |
} |
res = nfs_sock_inode->u.socket_i.ops->bind(&nfs_sock_inode->u.socket_i, |
(struct sockaddr *)sin, |
sizeof(struct sockaddr_in)); |
} |
} |
if (res < 0) { |
printk(KERN_ERR "Root-NFS: Cannot find a suitable listening port\n"); |
root_nfs_close(); |
return -1; |
} |
#ifdef NFSROOT_DEBUG |
printk(KERN_NOTICE "Root-NFS: Binding to listening port %d\n", port); |
#endif |
return 0; |
} |
|
|
/* |
* Send an RPC request and wait for the answer |
*/ |
static int *root_nfs_call(int *end) |
{ |
struct socket *sock; |
int dummylen; |
static struct nfs_server s = { |
0, /* struct file * */ |
0, /* struct rsock * */ |
{ |
0, "", |
}, /* toaddr */ |
0, /* lock */ |
NULL, /* wait queue */ |
NFS_MOUNT_SOFT, /* flags */ |
0, 0, /* rsize, wsize */ |
0, /* timeo */ |
0, /* retrans */ |
3 * HZ, 60 * HZ, 30 * HZ, 60 * HZ, "\0" |
}; |
|
s.file = nfs_file; |
sock = &((nfs_file->f_inode)->u.socket_i); |
|
/* Extract the other end of the socket into s->toaddr */ |
sock->ops->getname(sock, &(s.toaddr), &dummylen, 1); |
((struct sockaddr_in *) &s.toaddr)->sin_port = server.sin_port; |
((struct sockaddr_in *) &s.toaddr)->sin_family = server.sin_family; |
((struct sockaddr_in *) &s.toaddr)->sin_addr.s_addr = server.sin_addr.s_addr; |
|
s.rsock = rpc_makesock(nfs_file); |
s.flags = nfs_data.flags; |
s.rsize = nfs_data.rsize; |
s.wsize = nfs_data.wsize; |
s.timeo = nfs_data.timeo * HZ / 10; |
s.retrans = nfs_data.retrans; |
strcpy(s.hostname, nfs_data.hostname); |
|
/* |
* First connect the UDP socket to a server port, then send the |
* packet out, and finally check whether the answer is OK. |
*/ |
if (nfs_sock_inode->u.socket_i.ops->connect && |
nfs_sock_inode->u.socket_i.ops->connect(&nfs_sock_inode->u.socket_i, |
(struct sockaddr *) &server, |
sizeof(struct sockaddr_in), |
nfs_file->f_flags) < 0) |
return NULL; |
if (nfs_rpc_call(&s, rpc_packet, end, nfs_data.wsize) < 0) |
return NULL; |
return rpc_verify(rpc_packet); |
} |
|
|
/* |
* Create an RPC packet header |
*/ |
static int *root_nfs_header(int proc, int program, int version) |
{ |
int groups[] = { 0, NOGROUP }; |
|
if (rpc_packet == NULL) { |
if (!(rpc_packet = kmalloc(nfs_data.wsize + 1024, GFP_NFS))) { |
printk(KERN_ERR "Root-NFS: Cannot allocate UDP buffer\n"); |
return NULL; |
} |
} |
return rpc_header(rpc_packet, proc, program, version, 0, 0, groups); |
} |
|
|
/* |
* Query server portmapper for the port of a daemon program |
*/ |
static int root_nfs_get_port(int program, int version) |
{ |
int *p; |
|
/* Prepare header for portmap request */ |
server.sin_port = htons(NFS_PMAP_PORT); |
p = root_nfs_header(NFS_PMAP_PROC, NFS_PMAP_PROGRAM, NFS_PMAP_VERSION); |
if (!p) |
return -1; |
|
/* Set arguments for portmapper */ |
*p++ = htonl(program); |
*p++ = htonl(version); |
*p++ = htonl(IPPROTO_UDP); |
*p++ = 0; |
|
/* Send request to server portmapper */ |
if ((p = root_nfs_call(p)) == NULL) |
return -1; |
|
return ntohl(*p); |
} |
|
|
/* |
* Get portnumbers for mountd and nfsd from server |
*/ |
static int root_nfs_ports(void) |
{ |
int port; |
|
if (nfs_port < 0) { |
if ((port = root_nfs_get_port(NFS_NFS_PROGRAM, NFS_NFS_VERSION)) < 0) { |
printk(KERN_ERR "Root-NFS: Unable to get nfsd port number from server, using default\n"); |
port = NFS_NFS_PORT; |
} |
nfs_port = port; |
#ifdef NFSROOT_DEBUG |
printk(KERN_NOTICE "Root-NFS: Portmapper on server returned %d as nfsd port\n", port); |
#endif |
} |
if ((port = root_nfs_get_port(NFS_MOUNT_PROGRAM, NFS_MOUNT_VERSION)) < 0) { |
printk(KERN_ERR "Root-NFS: Unable to get mountd port number from server, using default\n"); |
port = NFS_MOUNT_PORT; |
} |
server.sin_port = htons(port); |
#ifdef NFSROOT_DEBUG |
printk(KERN_NOTICE "Root-NFS: Portmapper on server returned %d as mountd port\n", port); |
#endif |
|
return 0; |
} |
|
|
/* |
* Get a file handle from the server for the directory which is to be |
* mounted |
*/ |
static int root_nfs_get_handle(void) |
{ |
int len, status, *p; |
|
/* Prepare header for mountd request */ |
p = root_nfs_header(NFS_MOUNT_PROC, NFS_MOUNT_PROGRAM, NFS_MOUNT_VERSION); |
if (!p) { |
root_nfs_close(); |
return -1; |
} |
|
/* Set arguments for mountd */ |
len = strlen(nfs_path); |
*p++ = htonl(len); |
memcpy(p, nfs_path, len); |
len = (len + 3) >> 2; |
p[len] = 0; |
p += len; |
|
/* Send request to server mountd */ |
if ((p = root_nfs_call(p)) == NULL) { |
root_nfs_close(); |
return -1; |
} |
status = ntohl(*p++); |
if (status == 0) { |
nfs_data.root = *((struct nfs_fh *) p); |
printk(KERN_NOTICE "Root-NFS: Got file handle for %s via RPC\n", nfs_path); |
} else { |
printk(KERN_ERR "Root-NFS: Server returned error %d while mounting %s\n", |
status, nfs_path); |
root_nfs_close(); |
return -1; |
} |
|
return 0; |
} |
|
|
/* |
* Now actually mount the given directory |
*/ |
static int root_nfs_do_mount(struct super_block *sb) |
{ |
/* First connect to the nfsd port on the server */ |
server.sin_port = htons(nfs_port); |
nfs_data.addr = server; |
if (nfs_sock_inode->u.socket_i.ops->connect && |
nfs_sock_inode->u.socket_i.ops->connect(&nfs_sock_inode->u.socket_i, |
(struct sockaddr *) &server, |
sizeof(struct sockaddr_in), |
nfs_file->f_flags) < 0) { |
root_nfs_close(); |
return -1; |
} |
|
/* Now (finally ;-)) read the super block for mounting */ |
if (nfs_read_super(sb, &nfs_data, 1) == NULL) { |
root_nfs_close(); |
return -1; |
} |
return 0; |
} |
|
|
/* |
* Get the NFS port numbers and file handle, and then read the super- |
* block for mounting. |
*/ |
int nfs_root_mount(struct super_block *sb) |
{ |
if (root_nfs_open() < 0) |
return -1; |
if (root_nfs_bind() < 0) |
return -1; |
if (root_nfs_ports() < 0) |
return -1; |
if (root_nfs_get_handle() < 0) |
return -1; |
if (root_nfs_do_mount(sb) < 0) |
return -1; |
root_nfs_close(); |
return 0; |
} |
/.depend
0,0 → 1,134
bio.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/kernel.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/stat.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfsiod.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/pagemap.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/system.h |
dir.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/stat.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/kernel.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
file.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/kernel.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/stat.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/pagemap.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/system.h |
inode.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/module.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfsiod.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/kernel.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/stat.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/locks.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/smp.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/smp_lock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/system.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/unistd.h |
nfsiod.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/rpcsock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfsiod.h |
nfsroot.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/config.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/types.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/kernel.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/random.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/param.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/utsname.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/in.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/if.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/inet.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/net.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/netdevice.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/if_arp.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/net/ax25.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/skbuff.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/socket.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/route.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_mount.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/in.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/net/route.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/net/sock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
proc.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/param.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/utsname.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/in.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/pagemap.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
rpcsock.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/types.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/socket.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/in.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/net.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/rpcsock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/udp.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/net/sock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
sock.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/socket.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/fcntl.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/in.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/net.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/rpcsock.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
symlink.o: \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/sched.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/errno.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/nfs_fs.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/stat.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/mm.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/malloc.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/linux/string.h \ |
/home/javier/opencores/or1k/rc203soc/sw/uClinux/include/asm/segment.h |
/Makefile
0,0 → 1,20
# |
# 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... |
|
O_TARGET := nfs.o |
O_OBJS := proc.o sock.o rpcsock.o inode.o file.o bio.o \ |
nfsiod.o dir.o symlink.o |
|
ifdef CONFIG_ROOT_NFS |
O_OBJS += nfsroot.o |
endif |
|
M_OBJS := $(O_TARGET) |
|
include $(TOPDIR)/Rules.make |
/README
0,0 → 1,114
|
|
This is an NFS client for Linux that supports async RPC calls for |
read-ahead (and hopefully soon, write-back) on regular files. |
|
The implementation uses a straightforward nfsiod scheme. After |
trying out a number of different concepts, I finally got back to |
this concept, because everything else either didn't work or gave me |
headaches. It's not flashy, but it works without hacking into any |
other regions of the kernel. |
|
|
HOW TO USE |
|
This stuff compiles as a loadable module (I developed it on 1.3.77). |
Simply type mkmodule, and insmod nfs.o. This will start four nfsiod's |
at the same time (which will show up under the pseudonym of insmod in |
ps-style listings). |
|
Alternatively, you can put it right into the kernel: remove everything |
from fs/nfs, move the Makefile and all *.c to this directory, and |
copy all *.h files to include/linux. |
|
After mounting, you should be able to watch (with tcpdump) several |
RPC READ calls being placed simultaneously. |
|
|
HOW IT WORKS |
|
When a process reads from a file on an NFS volume, the following |
happens: |
|
* nfs_file_read sets file->f_reada if more than 1K is |
read at once. It then calls generic_file_read. |
|
* generic_file_read requests one ore more pages via |
nfs_readpage. |
|
* nfs_readpage allocates a request slot with an nfsiod |
daemon, fills in the READ request, sends out the |
RPC call, kicks the daemon, and returns. |
If there's no free biod, nfs_readpage places the |
call directly, waiting for the reply (sync readpage). |
|
* nfsiod calls nfs_rpc_doio to collect the reply. If the |
call was successful, it sets page->uptodate and |
wakes up all processes waiting on page->wait; |
|
This is the rough outline only. There are a few things to note: |
|
* Async RPC will not be tried when server->rsize < PAGE_SIZE. |
|
* When an error occurs, nfsiod has no way of returning |
the error code to the user process. Therefore, it flags |
page->error and wakes up all processes waiting on that |
page (they usually do so from within generic_readpage). |
|
generic_readpage finds that the page is still not |
uptodate, and calls nfs_readpage again. This time around, |
nfs_readpage notices that page->error is set and |
unconditionally does a synchronous RPC call. |
|
This area needs a lot of improvement, since read errors |
are not that uncommon (e.g. we have to retransmit calls |
if the fsuid is different from the ruid in order to |
cope with root squashing and stuff like this). |
|
Retransmits with fsuid/ruid change should be handled by |
nfsiod, but this doesn't come easily (a more general nfs_call |
routine that does all this may be useful...) |
|
* To save some time on readaheads, we save one data copy |
by frobbing the page into the iovec passed to the |
RPC code so that the networking layer copies the |
data into the page directly. |
|
This needs to be adjustable (different authentication |
flavors; AUTH_NULL versus AUTH_SHORT verifiers). |
|
* Currently, a fixed number of nfsiod's is spawned from |
within init_nfs_fs. This is problematic when running |
as a loadable module, because this will keep insmod's |
memory allocated. As a side-effect, you will see the |
nfsiod processes listed as several insmod's when doing |
a `ps.' |
|
* This NFS client implements server congestion control via |
Van Jacobson slow start as implemented in 44BSD. I haven't |
checked how well this behaves, but since Rick Macklem did |
it this way, it should be okay :-) |
|
|
WISH LIST |
|
After giving this thing some testing, I'd like to add some more |
features: |
|
* Some sort of async write handling. True write-back doesn't |
work with the current kernel (I think), because invalidate_pages |
kills all pages, regardless of whether they're dirty or not. |
Besides, this may require special bdflush treatment because |
write caching on clients is really hairy. |
|
Alternatively, a write-through scheme might be useful where |
the client enqueues the request, but leaves collecting the |
results to nfsiod. Again, we need a way to pass RPC errors |
back to the application. |
|
* Support for different authentication flavors. |
|
* /proc/net/nfsclnt (for nfsstat, etc.). |
|
March 29, 1996 |
Olaf Kirch <okir@monad.swb.de> |