summaryrefslogtreecommitdiffstats
path: root/fs/nfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/nfs.c')
-rw-r--r--fs/nfs.c265
1 files changed, 191 insertions, 74 deletions
diff --git a/fs/nfs.c b/fs/nfs.c
index 0ad07aa3f2..1a0b28442d 100644
--- a/fs/nfs.c
+++ b/fs/nfs.c
@@ -8,9 +8,6 @@
* Based on U-Boot NFS code which is based on NetBSD code with
* major changes to support nfs3.
*
- * See file CREDITS for list of people who contributed to this
- * project.
- *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
@@ -29,7 +26,6 @@
#include <errno.h>
#include <libgen.h>
#include <fcntl.h>
-#include <fs.h>
#include <init.h>
#include <linux/stat.h>
#include <linux/err.h>
@@ -122,8 +118,8 @@ struct rpc_reply {
uint32_t data[0];
};
-#define NFS_TIMEOUT (2 * SECOND)
-#define NFS_MAX_RESEND 5
+#define NFS_TIMEOUT (100 * MSECOND)
+#define NFS_MAX_RESEND 100
struct nfs_fh {
unsigned short size;
@@ -131,6 +127,7 @@ struct nfs_fh {
};
struct packet {
+ struct list_head list;
int len;
char data[];
};
@@ -145,7 +142,7 @@ struct nfs_priv {
unsigned manual_nfs_port:1;
uint32_t rpc_id;
struct nfs_fh rootfh;
- struct packet *nfs_packet;
+ struct list_head packets;
};
struct file_priv {
@@ -175,10 +172,6 @@ static void nfs_set_fh(struct inode *inode, struct nfs_fh *fh)
static uint64_t nfs_timer_start;
-static int nfs_state;
-#define STATE_DONE 1
-#define STATE_START 2
-
/*
* common types used in more than one request:
*
@@ -269,6 +262,74 @@ struct nfs_dir {
struct nfs_fh fh;
};
+struct nfserror {
+ int ne;
+ int e;
+ const char *name;
+};
+
+static struct nfserror nfserrors[] = {
+ { .ne = NFS3ERR_PERM, .e = EPERM },
+ { .ne = NFS3ERR_NOENT, .e = ENOENT },
+ { .ne = NFS3ERR_IO, .e = EIO },
+ { .ne = NFS3ERR_NXIO, .e = ENXIO },
+ { .ne = NFS3ERR_ACCES, .e = EACCES },
+ { .ne = NFS3ERR_EXIST, .e = EEXIST },
+ { .ne = NFS3ERR_XDEV, .e = EXDEV },
+ { .ne = NFS3ERR_NODEV, .e = ENODEV },
+ { .ne = NFS3ERR_NOTDIR, .e = ENOTDIR },
+ { .ne = NFS3ERR_ISDIR, .e = EISDIR },
+ { .ne = NFS3ERR_INVAL, .e = EINVAL },
+ { .ne = NFS3ERR_FBIG, .e = EFBIG },
+ { .ne = NFS3ERR_NOSPC, .e = ENOSPC },
+ { .ne = NFS3ERR_ROFS, .e = EROFS },
+ { .ne = NFS3ERR_MLINK, .e = EMLINK },
+ { .ne = NFS3ERR_NAMETOOLONG, .e = ENAMETOOLONG },
+ { .ne = NFS3ERR_NOTEMPTY, .e = ENOTEMPTY },
+ { .ne = NFS3ERR_DQUOT, .e = EDQUOT },
+ { .ne = NFS3ERR_STALE, .e = ESTALE },
+ { .ne = NFS3ERR_REMOTE, .e = EREMOTE },
+ { .ne = NFS3ERR_NOTSUPP, .e = EOPNOTSUPP },
+ { .ne = NFS3ERR_BADHANDLE, .e = EINVAL, .name = "BADHANDLE"},
+ { .ne = NFS3ERR_NOT_SYNC, .e = EINVAL, .name = "NOT_SYNC" },
+ { .ne = NFS3ERR_BAD_COOKIE, .e = EINVAL, .name = "BAD_COOKIE" },
+ { .ne = NFS3ERR_TOOSMALL, .e = EINVAL, .name = "TOOSMALL" },
+ { .ne = NFS3ERR_SERVERFAULT, .e = EINVAL, .name = "SERVERFAULT" },
+ { .ne = NFS3ERR_BADTYPE, .e = EINVAL, .name = "BADTYPE" },
+ { .ne = NFS3ERR_JUKEBOX, .e = EINVAL, .name = "JUKEBOX" },
+};
+
+static const char *nfserrstr(u32 nfserror, int *errcode)
+{
+ static char str[32];
+ int i;
+
+ /*
+ * Most NFS errors have a corresponding POSIX error code. But not all of
+ * them have one, so some must be mapped to a different code here.
+ */
+ for (i = 0; i < ARRAY_SIZE(nfserrors); i++) {
+ struct nfserror *err = &nfserrors[i];
+
+ if (nfserror == err->ne) {
+ if (errcode)
+ *errcode = -err->e;
+
+ if (err->name) {
+ snprintf(str, sizeof(str), "NFS3ERR_%s", err->name);
+ return str;
+ } else
+ return strerror(err->e);
+ }
+ }
+
+ if (errcode)
+ *errcode = -EINVAL;
+
+ snprintf(str, sizeof(str), "Unknown NFS error %d", nfserror);
+ return str;
+}
+
static void xdr_init(struct xdr_stream *stream, void *buf, int len)
{
stream->p = stream->buf = buf;
@@ -366,18 +427,10 @@ static int rpc_check_reply(struct packet *pkt, int rpc_prog,
*nfserr = 0;
- if (!pkt)
- return -EAGAIN;
-
memcpy(&rpc, pkt->data, sizeof(rpc));
- if (ntoh32(rpc.id) != rpc_id) {
- if (rpc_id - ntoh32(rpc.id) == 1)
- /* stale packet, wait a bit longer */
- return 0;
-
- return -EINVAL;
- }
+ if (ntoh32(rpc.id) != rpc_id)
+ return -EAGAIN;
if (rpc.rstatus ||
rpc.verifier ||
@@ -392,11 +445,17 @@ static int rpc_check_reply(struct packet *pkt, int rpc_prog,
*nfserr = ntoh32(net_read_uint32(data));
*nfserr = -*nfserr;
- debug("%s: state: %d, err %d\n", __func__, nfs_state, *nfserr);
+ debug("%s: err %d\n", __func__, *nfserr);
return 0;
}
+static void nfs_free_packet(struct packet *packet)
+{
+ list_del(&packet->list);
+ free(packet);
+}
+
/*
* rpc_req - synchronous RPC request
*/
@@ -409,6 +468,7 @@ static struct packet *rpc_req(struct nfs_priv *npriv, int rpc_prog,
unsigned char *payload = net_udp_get_payload(npriv->con);
int nfserr;
int tries = 0;
+ struct packet *packet;
npriv->rpc_id++;
@@ -453,12 +513,7 @@ again:
nfs_timer_start = get_time_ns();
- nfs_state = STATE_START;
-
- while (nfs_state != STATE_DONE) {
- if (ctrlc())
- return ERR_PTR(-EINTR);
-
+ while (1) {
net_poll();
if (is_timeout(nfs_timer_start, NFS_TIMEOUT)) {
@@ -468,19 +523,28 @@ again:
goto again;
}
- ret = rpc_check_reply(npriv->nfs_packet, rpc_prog,
+ if (list_empty(&npriv->packets))
+ continue;
+
+ packet = list_first_entry(&npriv->packets, struct packet, list);
+
+ ret = rpc_check_reply(packet, rpc_prog,
npriv->rpc_id, &nfserr);
- if (!ret) {
+ if (ret == -EAGAIN) {
+ nfs_free_packet(packet);
+ continue;
+ } else if (ret) {
+ nfs_free_packet(packet);
+ return ERR_PTR(ret);
+ } else {
if (rpc_prog == PROG_NFS && nfserr) {
- free(npriv->nfs_packet);
+ nfs_free_packet(packet);
return ERR_PTR(nfserr);
} else {
- return npriv->nfs_packet;
+ return packet;
}
}
}
-
- return npriv->nfs_packet;
}
/*
@@ -505,7 +569,13 @@ static int rpc_lookup_req(struct nfs_priv *npriv, uint32_t prog, uint32_t ver)
port = ntoh32(net_read_uint32(nfs_packet->data + sizeof(struct rpc_reply)));
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
+
+ if (port == 0) {
+ pr_warn("No UDP port for RPC program %i! "
+ "Is your NFS server TCP only?\n", prog);
+ return -ENOENT;
+ }
return port;
}
@@ -645,7 +715,7 @@ static uint32_t *nfs_read_post_op_attr(uint32_t *p, struct inode *inode)
static int nfs_mount_req(struct nfs_priv *npriv)
{
uint32_t data[1024];
- uint32_t *p;
+ uint32_t *p, status;
int len;
int pathlen;
struct packet *nfs_packet;
@@ -670,18 +740,29 @@ static int nfs_mount_req(struct nfs_priv *npriv)
if (IS_ERR(nfs_packet))
return PTR_ERR(nfs_packet);
- p = (void *)nfs_packet->data + sizeof(struct rpc_reply) + 4;
+ p = (void *)nfs_packet->data + sizeof(struct rpc_reply);
+
+ /*
+ * Theoretically the error status is one of MNT3ERR_..., but the NFS
+ * constants are identical.
+ */
+ status = ntoh32(net_read_uint32(p++));
+ if (status != NFS3_OK) {
+ int ret;
+ pr_err("Mounting failed: %s\n", nfserrstr(status, &ret));
+ return ret;
+ }
npriv->rootfh.size = ntoh32(net_read_uint32(p++));
if (npriv->rootfh.size > NFS3_FHSIZE) {
printf("%s: file handle too big: %lu\n",
__func__, (unsigned long)npriv->rootfh.size);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return -EIO;
}
memcpy(npriv->rootfh.data, p, npriv->rootfh.size);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return 0;
}
@@ -709,7 +790,7 @@ static void nfs_umount_req(struct nfs_priv *npriv)
nfs_packet = rpc_req(npriv, PROG_MOUNT, MOUNT_UMOUNT, data, len);
if (!IS_ERR(nfs_packet))
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
}
/*
@@ -722,7 +803,7 @@ static int nfs_lookup_req(struct nfs_priv *npriv, struct nfs_fh *fh,
{
struct nfs_inode *ninode = nfsi(inode);
uint32_t data[1024];
- uint32_t *p;
+ uint32_t *p, status;
int len;
struct packet *nfs_packet;
@@ -764,10 +845,17 @@ static int nfs_lookup_req(struct nfs_priv *npriv, struct nfs_fh *fh,
if (IS_ERR(nfs_packet))
return PTR_ERR(nfs_packet);
- p = (void *)nfs_packet->data + sizeof(struct rpc_reply) + 4;
+ p = (void *)nfs_packet->data + sizeof(struct rpc_reply);
+ status = ntoh32(net_read_uint32(p++));
+ if (status != NFS3_OK) {
+ int ret;
+ pr_err("Lookup failed: %s\n", nfserrstr(status, &ret));
+ return ret;
+ }
ninode->fh.size = ntoh32(net_read_uint32(p++));
if (ninode->fh.size > NFS3_FHSIZE) {
+ nfs_free_packet(nfs_packet);
debug("%s: file handle too big: %u\n", __func__,
ninode->fh.size);
return -EIO;
@@ -777,7 +865,7 @@ static int nfs_lookup_req(struct nfs_priv *npriv, struct nfs_fh *fh,
nfs_read_post_op_attr(p, inode);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return 0;
}
@@ -789,7 +877,7 @@ static int nfs_lookup_req(struct nfs_priv *npriv, struct nfs_fh *fh,
static void *nfs_readdirattr_req(struct nfs_priv *npriv, struct nfs_dir *dir)
{
uint32_t data[1024];
- uint32_t *p;
+ uint32_t *p, status;
int len;
struct packet *nfs_packet;
void *buf;
@@ -847,7 +935,13 @@ static void *nfs_readdirattr_req(struct nfs_priv *npriv, struct nfs_dir *dir)
if (IS_ERR(nfs_packet))
return NULL;
- p = (void *)nfs_packet->data + sizeof(struct rpc_reply) + 4;
+ p = (void *)nfs_packet->data + sizeof(struct rpc_reply);
+ status = ntoh32(net_read_uint32(p++));
+ if (status != NFS3_OK) {
+ pr_err("Readdir failed: %s\n", nfserrstr(status, NULL));
+ return NULL;
+ }
+
p = nfs_read_post_op_attr(p, NULL);
/* update cookieverf */
@@ -857,7 +951,7 @@ static void *nfs_readdirattr_req(struct nfs_priv *npriv, struct nfs_dir *dir)
len = (void *)nfs_packet->data + nfs_packet->len - (void *)p;
if (!len) {
printf("%s: huh, no payload left\n", __func__);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return NULL;
}
@@ -865,7 +959,7 @@ static void *nfs_readdirattr_req(struct nfs_priv *npriv, struct nfs_dir *dir)
memcpy(buf, p, len);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
xdr_init(&dir->stream, buf, len);
@@ -881,8 +975,8 @@ static int nfs_read_req(struct file_priv *priv, uint64_t offset,
uint32_t readlen)
{
uint32_t data[1024];
- uint32_t *p;
- int len;
+ uint32_t *p, status;
+ int len, ret;
struct packet *nfs_packet;
uint32_t rlen, eof;
@@ -924,7 +1018,12 @@ static int nfs_read_req(struct file_priv *priv, uint64_t offset,
if (IS_ERR(nfs_packet))
return PTR_ERR(nfs_packet);
- p = (void *)nfs_packet->data + sizeof(struct rpc_reply) + 4;
+ p = (void *)nfs_packet->data + sizeof(struct rpc_reply);
+ status = ntoh32(net_read_uint32(p++));
+ if (status != NFS3_OK) {
+ pr_err("Read failed: %s\n", nfserrstr(status, &ret));
+ return ret;
+ }
p = nfs_read_post_op_attr(p, NULL);
@@ -942,30 +1041,31 @@ static int nfs_read_req(struct file_priv *priv, uint64_t offset,
p += 2;
if (readlen && !rlen && !eof) {
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return -EIO;
}
kfifo_put(priv->fifo, (char *)p, rlen);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return 0;
}
-static void nfs_handler(void *ctx, char *packet, unsigned len)
+static void nfs_handler(void *ctx, char *p, unsigned len)
{
- char *pkt = net_eth_to_udp_payload(packet);
+ char *pkt = net_eth_to_udp_payload(p);
struct nfs_priv *npriv = ctx;
+ struct packet *packet;
- nfs_state = STATE_DONE;
+ packet = xmalloc(sizeof(*packet) + len);
+ memcpy(packet->data, pkt, len);
+ packet->len = len;
- npriv->nfs_packet = xmalloc(sizeof(*npriv->nfs_packet) + len);
- memcpy(npriv->nfs_packet->data, pkt, len);
- npriv->nfs_packet->len = len;
+ list_add_tail(&packet->list, &npriv->packets);
}
-static int nfs_truncate(struct device_d *dev, FILE *f, loff_t size)
+static int nfs_truncate(struct device *dev, FILE *f, loff_t size)
{
return -ENOSYS;
}
@@ -982,7 +1082,7 @@ static int nfs_readlink_req(struct nfs_priv *npriv, struct nfs_fh *fh,
char **target)
{
uint32_t data[1024];
- uint32_t *p;
+ uint32_t *p, status;
uint32_t len;
struct packet *nfs_packet;
@@ -1018,13 +1118,19 @@ static int nfs_readlink_req(struct nfs_priv *npriv, struct nfs_fh *fh,
if (IS_ERR(nfs_packet))
return PTR_ERR(nfs_packet);
- p = (void *)nfs_packet->data + sizeof(struct rpc_reply) + 4;
+ p = (void *)nfs_packet->data + sizeof(struct rpc_reply);
+ status = ntoh32(net_read_uint32(p++));
+ if (status != NFS3_OK) {
+ int ret;
+ pr_err("Readlink failed: %s\n", nfserrstr(status, &ret));
+ return ret;
+ }
p = nfs_read_post_op_attr(p, NULL);
len = ntoh32(net_read_uint32(p)); /* new path length */
- len = max_t(unsigned int, len,
+ len = min_t(unsigned int, len,
nfs_packet->len - sizeof(struct rpc_reply) - sizeof(uint32_t));
p++;
@@ -1032,7 +1138,7 @@ static int nfs_readlink_req(struct nfs_priv *npriv, struct nfs_fh *fh,
*target = xzalloc(len + 1);
memcpy(*target, p, len);
- free(nfs_packet);
+ nfs_free_packet(nfs_packet);
return 0;
}
@@ -1050,7 +1156,7 @@ static const char *nfs_get_link(struct dentry *dentry, struct inode *inode)
return inode->i_link;
}
-static int nfs_open(struct device_d *dev, FILE *file, const char *filename)
+static int nfs_open(struct device *dev, FILE *file, const char *filename)
{
struct inode *inode = file->f_inode;
struct nfs_inode *ninode = nfsi(inode);
@@ -1072,7 +1178,7 @@ static int nfs_open(struct device_d *dev, FILE *file, const char *filename)
return 0;
}
-static int nfs_close(struct device_d *dev, FILE *file)
+static int nfs_close(struct device *dev, FILE *file)
{
struct file_priv *priv = file->priv;
@@ -1081,13 +1187,13 @@ static int nfs_close(struct device_d *dev, FILE *file)
return 0;
}
-static int nfs_write(struct device_d *_dev, FILE *file, const void *inbuf,
- size_t insize)
+static int nfs_write(struct device *_dev, FILE *file, const void *inbuf,
+ size_t insize)
{
return -ENOSYS;
}
-static int nfs_read(struct device_d *dev, FILE *file, void *buf, size_t insize)
+static int nfs_read(struct device *dev, FILE *file, void *buf, size_t insize)
{
struct file_priv *priv = file->priv;
@@ -1103,7 +1209,7 @@ static int nfs_read(struct device_d *dev, FILE *file, void *buf, size_t insize)
return kfifo_get(priv->fifo, buf, insize);
}
-static int nfs_lseek(struct device_d *dev, FILE *file, loff_t pos)
+static int nfs_lseek(struct device *dev, FILE *file, loff_t pos)
{
struct file_priv *priv = file->priv;
@@ -1203,6 +1309,13 @@ static struct inode *nfs_alloc_inode(struct super_block *sb)
return &node->inode;
}
+static void nfs_destroy_inode(struct inode *inode)
+{
+ struct nfs_inode *node = nfsi(inode);
+
+ free(node);
+}
+
static const struct inode_operations nfs_file_inode_operations;
static const struct file_operations nfs_dir_operations;
static const struct inode_operations nfs_dir_inode_operations;
@@ -1274,11 +1387,12 @@ static const struct inode_operations nfs_dir_inode_operations =
static const struct super_operations nfs_ops = {
.alloc_inode = nfs_alloc_inode,
+ .destroy_inode = nfs_destroy_inode,
};
static char *rootnfsopts;
-static void nfs_set_rootarg(struct nfs_priv *npriv, struct fs_device_d *fsdev)
+static void nfs_set_rootarg(struct nfs_priv *npriv, struct fs_device *fsdev)
{
char *str, *tmp;
const char *bootargs;
@@ -1311,9 +1425,9 @@ static void nfs_set_rootarg(struct nfs_priv *npriv, struct fs_device_d *fsdev)
free(str);
}
-static int nfs_probe(struct device_d *dev)
+static int nfs_probe(struct device *dev)
{
- struct fs_device_d *fsdev = dev_to_fs_device(dev);
+ struct fs_device *fsdev = dev_to_fs_device(dev);
struct nfs_priv *npriv = xzalloc(sizeof(struct nfs_priv));
struct super_block *sb = &fsdev->sb;
char *tmp = xstrdup(fsdev->backingstore);
@@ -1323,6 +1437,8 @@ static int nfs_probe(struct device_d *dev)
dev->priv = npriv;
+ INIT_LIST_HEAD(&npriv->packets);
+
debug("nfs: mount: %s\n", fsdev->backingstore);
path = strchr(tmp, ':');
@@ -1389,6 +1505,7 @@ static int nfs_probe(struct device_d *dev)
free(tmp);
sb->s_op = &nfs_ops;
+ sb->s_d_op = &no_revalidate_d_ops;
inode = new_inode(sb);
nfs_set_fh(inode, &npriv->rootfh);
@@ -1408,7 +1525,7 @@ err:
return ret;
}
-static void nfs_remove(struct device_d *dev)
+static void nfs_remove(struct device *dev)
{
struct nfs_priv *npriv = dev->priv;
@@ -1419,7 +1536,7 @@ static void nfs_remove(struct device_d *dev)
free(npriv);
}
-static struct fs_driver_d nfs_driver = {
+static struct fs_driver nfs_driver = {
.open = nfs_open,
.close = nfs_close,
.read = nfs_read,