#include <linux/seq_file.h>
#include <linux/pagemap.h>
#include <linux/init.h>
+#include <linux/string.h>
#include <linux/nfs.h>
#include <linux/nfsd_idmap.h>
#include <asm/uaccess.h>
+unsigned int nfsd_versbits = ~0;
+
/*
- * We have a single directory with 8 nodes in it.
+ * We have a single directory with 9 nodes in it.
*/
enum {
NFSD_Root = 1,
NFSD_List,
NFSD_Fh,
NFSD_Threads,
+ NFSD_Versions,
+ /*
+ * The below MUST come last. Otherwise we leave a hole in nfsd_files[]
+ * with !CONFIG_NFSD_V4 and simple_fill_super() goes oops
+ */
+#ifdef CONFIG_NFSD_V4
+ NFSD_Leasetime,
+ NFSD_RecoveryDir,
+#endif
};
/*
static ssize_t write_getfs(struct file *file, char *buf, size_t size);
static ssize_t write_filehandle(struct file *file, char *buf, size_t size);
static ssize_t write_threads(struct file *file, char *buf, size_t size);
+static ssize_t write_versions(struct file *file, char *buf, size_t size);
+#ifdef CONFIG_NFSD_V4
+static ssize_t write_leasetime(struct file *file, char *buf, size_t size);
+static ssize_t write_recoverydir(struct file *file, char *buf, size_t size);
+#endif
static ssize_t (*write_op[])(struct file *, char *, size_t) = {
[NFSD_Svc] = write_svc,
[NFSD_Getfs] = write_getfs,
[NFSD_Fh] = write_filehandle,
[NFSD_Threads] = write_threads,
+ [NFSD_Versions] = write_versions,
+#ifdef CONFIG_NFSD_V4
+ [NFSD_Leasetime] = write_leasetime,
+ [NFSD_RecoveryDir] = write_recoverydir,
+#endif
};
-/* an argresp is stored in an allocated page and holds the
- * size of the argument or response, along with its content
- */
-struct argresp {
- ssize_t size;
- char data[0];
-};
-
-/*
- * transaction based IO methods.
- * The file expects a single write which triggers the transaction, and then
- * possibly a read which collects the result - which is stored in a
- * file-local buffer.
- */
-static ssize_t TA_write(struct file *file, const char *buf, size_t size, loff_t *pos)
+static ssize_t nfsctl_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
ino_t ino = file->f_dentry->d_inode->i_ino;
- struct argresp *ar;
- ssize_t rv = 0;
+ char *data;
+ ssize_t rv;
if (ino >= sizeof(write_op)/sizeof(write_op[0]) || !write_op[ino])
return -EINVAL;
- if (file->private_data)
- return -EINVAL; /* only one write allowed per open */
- if (size > PAGE_SIZE - sizeof(struct argresp))
- return -EFBIG;
- ar = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!ar)
- return -ENOMEM;
- ar->size = 0;
- down(&file->f_dentry->d_inode->i_sem);
- if (file->private_data)
- rv = -EINVAL;
- else
- file->private_data = ar;
- up(&file->f_dentry->d_inode->i_sem);
- if (rv) {
- kfree(ar);
- return rv;
- }
- if (copy_from_user(ar->data, buf, size))
- return -EFAULT;
-
- rv = write_op[ino](file, ar->data, size);
+ data = simple_transaction_get(file, buf, size);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ rv = write_op[ino](file, data, size);
if (rv>0) {
- ar->size = rv;
+ simple_transaction_set(file, rv);
rv = size;
}
return rv;
}
-
-static ssize_t TA_read(struct file *file, char *buf, size_t size, loff_t *pos)
-{
- struct argresp *ar;
- ssize_t rv = 0;
-
- if (file->private_data == NULL)
- rv = TA_write(file, buf, 0, pos);
- if (rv < 0)
- return rv;
-
- ar = file->private_data;
- if (!ar)
- return 0;
- if (*pos >= ar->size)
- return 0;
- if (*pos + size > ar->size)
- size = ar->size - *pos;
- if (copy_to_user(buf, ar->data + *pos, size))
- return -EFAULT;
- *pos += size;
- return size;
-}
-
-static int TA_open(struct inode *inode, struct file *file)
+static ssize_t nfsctl_transaction_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
- file->private_data = NULL;
- return 0;
-}
-
-static int TA_release(struct inode *inode, struct file *file)
-{
- void *p = file->private_data;
- file->private_data = NULL;
- kfree(p);
- return 0;
+ if (! file->private_data) {
+ /* An attempt to read a transaction file without writing
+ * causes a 0-byte write so that the file can return
+ * state information
+ */
+ ssize_t rv = nfsctl_transaction_write(file, buf, 0, pos);
+ if (rv < 0)
+ return rv;
+ }
+ return simple_transaction_read(file, buf, size, pos);
}
static struct file_operations transaction_ops = {
- .write = TA_write,
- .read = TA_read,
- .open = TA_open,
- .release = TA_release,
+ .write = nfsctl_transaction_write,
+ .read = nfsctl_transaction_read,
+ .release = simple_transaction_release,
};
extern struct seq_operations nfs_exports_op;
if (len)
return len;
- mesg = buf; len = PAGE_SIZE-sizeof(struct argresp);
+ mesg = buf; len = SIMPLE_TRANSACTION_LIMIT;
qword_addhex(&mesg, &len, (char*)&fh.fh_base, fh.fh_size);
mesg[-1] = '\n';
return mesg - buf;
return strlen(buf);
}
+static ssize_t write_versions(struct file *file, char *buf, size_t size)
+{
+ /*
+ * Format:
+ * [-/+]vers [-/+]vers ...
+ */
+ char *mesg = buf;
+ char *vers, sign;
+ int len, num;
+ ssize_t tlen = 0;
+ char *sep;
+
+ if (size>0) {
+ if (nfsd_serv)
+ return -EBUSY;
+ if (buf[size-1] != '\n')
+ return -EINVAL;
+ buf[size-1] = 0;
+
+ vers = mesg;
+ len = qword_get(&mesg, vers, size);
+ if (len <= 0) return -EINVAL;
+ do {
+ sign = *vers;
+ if (sign == '+' || sign == '-')
+ num = simple_strtol((vers+1), NULL, 0);
+ else
+ num = simple_strtol(vers, NULL, 0);
+ switch(num) {
+ case 2:
+ case 3:
+ case 4:
+ if (sign != '-')
+ NFSCTL_VERSET(nfsd_versbits, num);
+ else
+ NFSCTL_VERUNSET(nfsd_versbits, num);
+ break;
+ default:
+ return -EINVAL;
+ }
+ vers += len + 1;
+ tlen += len;
+ } while ((len = qword_get(&mesg, vers, size)) > 0);
+ /* If all get turned off, turn them back on, as
+ * having no versions is BAD
+ */
+ if ((nfsd_versbits & NFSCTL_VERALL)==0)
+ nfsd_versbits = NFSCTL_VERALL;
+ }
+ /* Now write current state into reply buffer */
+ len = 0;
+ sep = "";
+ for (num=2 ; num <= 4 ; num++)
+ if (NFSCTL_VERISSET(NFSCTL_VERALL, num)) {
+ len += sprintf(buf+len, "%s%c%d", sep,
+ NFSCTL_VERISSET(nfsd_versbits, num)?'+':'-',
+ num);
+ sep = " ";
+ }
+ len += sprintf(buf+len, "\n");
+ return len;
+}
+
+#ifdef CONFIG_NFSD_V4
+extern time_t nfs4_leasetime(void);
+
+static ssize_t write_leasetime(struct file *file, char *buf, size_t size)
+{
+ /* if size > 10 seconds, call
+ * nfs4_reset_lease() then write out the new lease (seconds) as reply
+ */
+ char *mesg = buf;
+ int rv;
+
+ if (size > 0) {
+ int lease;
+ rv = get_int(&mesg, &lease);
+ if (rv)
+ return rv;
+ if (lease < 10 || lease > 3600)
+ return -EINVAL;
+ nfs4_reset_lease(lease);
+ }
+ sprintf(buf, "%ld\n", nfs4_lease_time());
+ return strlen(buf);
+}
+
+static ssize_t write_recoverydir(struct file *file, char *buf, size_t size)
+{
+ char *mesg = buf;
+ char *recdir;
+ int len, status;
+
+ if (size > PATH_MAX || buf[size-1] != '\n')
+ return -EINVAL;
+ buf[size-1] = 0;
+
+ recdir = mesg;
+ len = qword_get(&mesg, recdir, size);
+ if (len <= 0)
+ return -EINVAL;
+
+ status = nfs4_reset_recoverydir(recdir);
+ return strlen(buf);
+}
+#endif
+
/*----------------------------------------------------------------------------*/
/*
* populating the filesystem.
[NFSD_List] = {"exports", &exports_operations, S_IRUGO},
[NFSD_Fh] = {"filehandle", &transaction_ops, S_IWUSR|S_IRUSR},
[NFSD_Threads] = {"threads", &transaction_ops, S_IWUSR|S_IRUSR},
+ [NFSD_Versions] = {"versions", &transaction_ops, S_IWUSR|S_IRUSR},
+#ifdef CONFIG_NFSD_V4
+ [NFSD_Leasetime] = {"nfsv4leasetime", &transaction_ops, S_IWUSR|S_IRUSR},
+ [NFSD_RecoveryDir] = {"nfsv4recoverydir", &transaction_ops, S_IWUSR|S_IRUSR},
+#endif
/* last one */ {""}
};
return simple_fill_super(sb, 0x6e667364, nfsd_files);
nfsd_cache_init(); /* RPC reply cache */
nfsd_export_init(); /* Exports table */
nfsd_lockd_init(); /* lockd->nfsd callbacks */
-#ifdef CONFIG_NFSD_V4
+ nfs4_state_init(); /* NFSv4 locking state */
nfsd_idmap_init(); /* Name to ID mapping */
-#endif /* CONFIG_NFSD_V4 */
- if (proc_mkdir("fs/nfs", 0)) {
+ if (proc_mkdir("fs/nfs", NULL)) {
struct proc_dir_entry *entry;
entry = create_proc_entry("fs/nfs/exports", 0, NULL);
if (entry)
remove_proc_entry("fs/nfs", NULL);
nfsd_stat_shutdown();
nfsd_lockd_shutdown();
-#ifdef CONFIG_NFSD_V4
nfsd_idmap_shutdown();
-#endif /* CONFIG_NFSD_V4 */
unregister_filesystem(&nfsd_fs_type);
}