9
0
Fork 0
barebox/fs/nfs.c

1427 lines
27 KiB
C

/*
* nfs.c - barebox NFS driver
*
* Copyright (c) 2014 Uwe Kleine-König, Pengutronix
* Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
* Copyright (c) Masami Komiya <mkomiya@sonare.it> 2004
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <common.h>
#include <net.h>
#include <driver.h>
#include <fs.h>
#include <errno.h>
#include <libgen.h>
#include <fcntl.h>
#include <fs.h>
#include <init.h>
#include <linux/stat.h>
#include <linux/err.h>
#include <kfifo.h>
#include <sizes.h>
#include <byteorder.h>
#include "parseopt.h"
#define SUNRPC_PORT 111
#define PROG_PORTMAP 100000
#define PROG_NFS 100003
#define PROG_MOUNT 100005
#define MSG_CALL 0
#define MSG_REPLY 1
#define PORTMAP_GETPORT 3
#define MOUNT_ADDENTRY 1
#define MOUNT_UMOUNT 3
#define NFSPROC3_GETATTR 1
#define NFSPROC3_LOOKUP 3
#define NFSPROC3_READLINK 5
#define NFSPROC3_READ 6
#define NFSPROC3_READDIR 16
#define NFS3_FHSIZE 64
#define NFS3_COOKIEVERFSIZE 8
/* values of enum ftype3 */
#define NF3REG 1
#define NF3DIR 2
#define NF3BLK 3
#define NF3CHR 4
#define NF3LNK 5
#define NF3SOCK 6
#define NF3FIFO 7
/* values for enum nfsstat3 */
#define NFS3_OK 0
#define NFS3ERR_PERM 1
#define NFS3ERR_NOENT 2
#define NFS3ERR_IO 5
#define NFS3ERR_NXIO 6
#define NFS3ERR_ACCES 13
#define NFS3ERR_EXIST 17
#define NFS3ERR_XDEV 18
#define NFS3ERR_NODEV 19
#define NFS3ERR_NOTDIR 20
#define NFS3ERR_ISDIR 21
#define NFS3ERR_INVAL 22
#define NFS3ERR_FBIG 27
#define NFS3ERR_NOSPC 28
#define NFS3ERR_ROFS 30
#define NFS3ERR_MLINK 31
#define NFS3ERR_NAMETOOLONG 63
#define NFS3ERR_NOTEMPTY 66
#define NFS3ERR_DQUOT 69
#define NFS3ERR_STALE 70
#define NFS3ERR_REMOTE 71
#define NFS3ERR_BADHANDLE 10001
#define NFS3ERR_NOT_SYNC 10002
#define NFS3ERR_BAD_COOKIE 10003
#define NFS3ERR_NOTSUPP 10004
#define NFS3ERR_TOOSMALL 10005
#define NFS3ERR_SERVERFAULT 10006
#define NFS3ERR_BADTYPE 10007
#define NFS3ERR_JUKEBOX 10008
static void *nfs_packet;
static int nfs_len;
struct rpc_call {
uint32_t id;
uint32_t type;
uint32_t rpcvers;
uint32_t prog;
uint32_t vers;
uint32_t proc;
uint32_t data[0];
};
struct rpc_reply {
uint32_t id;
uint32_t type;
uint32_t rstatus;
uint32_t verifier;
uint32_t v2;
uint32_t astatus;
uint32_t data[0];
};
#define NFS_TIMEOUT (2 * SECOND)
#define NFS_MAX_RESEND 5
struct nfs_priv {
struct net_connection *con;
IPaddr_t server;
char *path;
unsigned short mount_port;
unsigned short nfs_port;
uint32_t rpc_id;
uint32_t rootfh_len;
char rootfh[NFS3_FHSIZE];
};
struct file_priv {
struct kfifo *fifo;
void *buf;
uint32_t filefh_len;
char filefh[NFS3_FHSIZE];
struct nfs_priv *npriv;
};
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:
*
* typedef uint32 count3;
* typedef uint32 gid3;
* typedef uint32 mode3;
* typedef uint32 uid3;
*
* typedef uint64 cookie3;
* typedef uint64 fileid3;
* typedef uint64 size3;
*
* typedef string filename3<>;
* typedef string nfspath3<>;
*
* typedef opaque cookieverf3[NFS3_COOKIEVERFSIZE];
*
* enum ftype3 {
* NF3REG = 1,
* NF3DIR = 2,
* NF3BLK = 3,
* NF3CHR = 4,
* NF3LNK = 5,
* NF3SOCK = 6,
* NF3FIFO = 7
* };
*
* struct specdata3 {
* uint32 specdata1;
* uint32 specdata2;
* };
*
* struct nfs_fh3 {
* opaque data<NFS3_FHSIZE>;
* }
*
* struct nfstime3 {
* uint32 seconds;
* uint32 nseconds;
* };
*
* struct fattr3 {
* ftype3 type;
* mode3 mode;
* uint32_t nlink;
* uid3 uid;
* gid3 gid;
* size3 size;
* size3 used;
* specdata3 rdev;
* uint64_t fsid;
* fileid3 fileid;
* nfstime3 atime;
* nfstime3 mtime;
* nfstime3 ctime;
* };
*
* struct diropargs3 {
* nfs_fh3 dir;
* filename3 name;
* }
*
* union post_op_attr switch (bool attributes_follow) {
* case TRUE:
* fattr3 attributes;
* case FALSE:
* void;
* };
*/
struct xdr_stream {
__be32 *p;
void *buf;
__be32 *end;
};
#define xdr_zero 0
#define XDR_QUADLEN(l) (((l) + 3) >> 2)
struct nfs_dir {
DIR dir;
/*
* stream points to the next entry3 in the reply member of READDIR3res
* (if any, to the end indicator otherwise).
*/
struct xdr_stream stream;
struct dirent ent;
struct file_priv *priv;
uint64_t cookie;
char cookieverf[NFS3_COOKIEVERFSIZE];
};
static void xdr_init(struct xdr_stream *stream, void *buf, int len)
{
stream->p = stream->buf = buf;
stream->end = stream->buf + len;
}
static __be32 *__xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
{
__be32 *p = xdr->p;
__be32 *q = p + XDR_QUADLEN(nbytes);
if (q > xdr->end || q < p)
return NULL;
xdr->p = q;
return p;
}
static __be32 *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
{
__be32 *p;
if (nbytes == 0)
return xdr->p;
if (xdr->p == xdr->end)
return NULL;
p = __xdr_inline_decode(xdr, nbytes);
return p;
}
/*
* name is expected to point to a buffer with a size of at least 256 bytes.
*/
static int decode_filename(struct xdr_stream *xdr, char *name, u32 *length)
{
__be32 *p;
u32 count;
p = xdr_inline_decode(xdr, 4);
if (!p)
goto out_overflow;
count = ntoh32(net_read_uint32(p));
if (count > 255)
goto out_nametoolong;
p = xdr_inline_decode(xdr, count);
if (!p)
goto out_overflow;
memcpy(name, p, count);
name[count] = 0;
*length = count;
return 0;
out_nametoolong:
printf("%s: returned a too long filename: %u\n", __func__, count);
return -ENAMETOOLONG;
out_overflow:
printf("%s: premature end of packet\n", __func__);
return -EIO;
}
/*
* rpc_add_credentials - Add RPC authentication/verifier entries
*/
static uint32_t *rpc_add_credentials(uint32_t *p)
{
/*
* *BSD refuses AUTH_NONE, so use AUTH_UNIX. An empty hostname is OK for
* both Linux and *BSD.
*/
/* Provide an AUTH_UNIX credential. */
*p++ = hton32(1); /* AUTH_UNIX */
*p++ = hton32(20); /* auth length: 20 + strlen(hostname) */
*p++ = hton32(0); /* stamp */
*p++ = hton32(0); /* hostname string length */
/* memcpy(p, "", 0); p += 0; <- empty host name */
*p++ = 0; /* uid */
*p++ = 0; /* gid */
*p++ = 0; /* auxiliary gid list */
/* Provide an AUTH_NONE verifier. */
*p++ = 0; /* AUTH_NONE */
*p++ = 0; /* auth length */
return p;
}
static int rpc_check_reply(unsigned char *pkt,
int rpc_prog, uint32_t rpc_id, int *nfserr)
{
uint32_t *data;
struct rpc_reply rpc;
*nfserr = 0;
if (!pkt)
return -EAGAIN;
memcpy(&rpc, pkt, 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 (rpc.rstatus ||
rpc.verifier ||
rpc.astatus ) {
return -EINVAL;
}
if (rpc_prog == PROG_PORTMAP)
return 0;
data = (uint32_t *)(pkt + sizeof(struct rpc_reply));
*nfserr = ntoh32(net_read_uint32(data));
*nfserr = -*nfserr;
debug("%s: state: %d, err %d\n", __func__, nfs_state, *nfserr);
return 0;
}
/*
* rpc_req - synchronous RPC request
*/
static int rpc_req(struct nfs_priv *npriv, int rpc_prog, int rpc_proc,
uint32_t *data, int datalen)
{
struct rpc_call pkt;
unsigned short dport;
int ret;
unsigned char *payload = net_udp_get_payload(npriv->con);
int nfserr;
int tries = 0;
npriv->rpc_id++;
pkt.id = hton32(npriv->rpc_id);
pkt.type = hton32(MSG_CALL);
pkt.rpcvers = hton32(2); /* use RPC version 2 */
pkt.prog = hton32(rpc_prog);
pkt.proc = hton32(rpc_proc);
debug("%s: prog: %d, proc: %d\n", __func__, rpc_prog, rpc_proc);
if (rpc_prog == PROG_PORTMAP) {
dport = SUNRPC_PORT;
pkt.vers = hton32(2);
} else if (rpc_prog == PROG_MOUNT) {
dport = npriv->mount_port;
pkt.vers = hton32(3);
} else {
dport = npriv->nfs_port;
pkt.vers = hton32(3);
}
memcpy(payload, &pkt, sizeof(pkt));
memcpy(payload + sizeof(pkt), data, datalen * sizeof(uint32_t));
npriv->con->udp->uh_dport = hton16(dport);
again:
ret = net_udp_send(npriv->con,
sizeof(pkt) + datalen * sizeof(uint32_t));
nfs_timer_start = get_time_ns();
nfs_state = STATE_START;
nfs_packet = NULL;
while (nfs_state != STATE_DONE) {
if (ctrlc()) {
ret = -EINTR;
break;
}
net_poll();
if (is_timeout(nfs_timer_start, NFS_TIMEOUT)) {
tries++;
if (tries == NFS_MAX_RESEND)
return -ETIMEDOUT;
goto again;
}
ret = rpc_check_reply(nfs_packet, rpc_prog,
npriv->rpc_id, &nfserr);
if (!ret) {
ret = nfserr;
break;
}
}
return ret;
}
/*
* rpc_lookup_req - Lookup RPC Port numbers
*/
static int rpc_lookup_req(struct nfs_priv *npriv, uint32_t prog, uint32_t ver)
{
uint32_t data[16];
int ret;
uint32_t port;
data[0] = 0; data[1] = 0; /* auth credential */
data[2] = 0; data[3] = 0; /* auth verifier */
data[4] = hton32(prog);
data[5] = hton32(ver);
data[6] = hton32(17); /* IP_UDP */
data[7] = 0;
ret = rpc_req(npriv, PROG_PORTMAP, PORTMAP_GETPORT, data, 8);
if (ret)
return ret;
port = ntoh32(net_read_uint32(nfs_packet + sizeof(struct rpc_reply)));
return port;
}
static uint32_t *nfs_add_uint32(uint32_t *p, uint32_t val)
{
*p++ = hton32(val);
return p;
}
static uint32_t *nfs_add_uint64(uint32_t *p, uint64_t val)
{
uint64_t nval = hton64(val);
memcpy(p, &nval, 8);
return p + 2;
}
static uint32_t *nfs_add_fh3(uint32_t *p, unsigned fh_len, const char *fh)
{
*p++ = hton32(fh_len);
/* zero padding */
if (fh_len & 3)
p[fh_len / 4] = 0;
memcpy(p, fh, fh_len);
p += DIV_ROUND_UP(fh_len, 4);
return p;
}
static uint32_t *nfs_add_filename(uint32_t *p,
uint32_t filename_len, const char *filename)
{
*p++ = hton32(filename_len);
/* zero padding */
if (filename_len & 3)
p[filename_len / 4] = 0;
memcpy(p, filename, filename_len);
p += DIV_ROUND_UP(filename_len, 4);
return p;
}
/* This is a 1:1 mapping for Linux, the compiler optimizes it out */
static const struct {
uint32_t nfsmode;
unsigned short statmode;
} nfs3_mode_bits[] = {
{ 0x00001, S_IXOTH },
{ 0x00002, S_IWOTH },
{ 0x00004, S_IROTH },
{ 0x00008, S_IXGRP },
{ 0x00010, S_IWGRP },
{ 0x00020, S_IRGRP },
{ 0x00040, S_IXUSR },
{ 0x00080, S_IWUSR },
{ 0x00100, S_IRUSR },
{ 0x00200, S_ISVTX },
{ 0x00400, S_ISGID },
{ 0x00800, S_ISUID },
};
static int nfs_fattr3_to_stat(uint32_t *p, struct stat *s)
{
uint32_t mode;
size_t i;
/* offsetof(struct fattr3, type) = 0 */
switch (ntoh32(net_read_uint32(p + 0))) {
case NF3REG:
s->st_mode = S_IFREG;
break;
case NF3DIR:
s->st_mode = S_IFDIR;
break;
case NF3BLK:
s->st_mode = S_IFBLK;
break;
case NF3CHR:
s->st_mode = S_IFCHR;
break;
case NF3LNK:
s->st_mode = S_IFLNK;
break;
case NF3SOCK:
s->st_mode = S_IFSOCK;
break;
case NF3FIFO:
s->st_mode = S_IFIFO;
break;
default:
printf("%s: invalid mode %x\n",
__func__, ntoh32(net_read_uint32(p + 0)));
return -EIO;
}
/* offsetof(struct fattr3, mode) = 4 */
mode = ntoh32(net_read_uint32(p + 1));
for (i = 0; i < ARRAY_SIZE(nfs3_mode_bits); ++i) {
if (mode & nfs3_mode_bits[i].nfsmode)
s->st_mode |= nfs3_mode_bits[i].statmode;
}
/* offsetof(struct fattr3, size) = 20 */
s->st_size = ntoh64(net_read_uint64(p + 5));
return 0;
}
static uint32_t *nfs_read_post_op_attr(uint32_t *p, struct stat **s)
{
struct stat dummy;
/*
* union post_op_attr switch (bool attributes_follow) {
* case TRUE:
* fattr3 attributes;
* case FALSE:
* void;
* };
*/
if (ntoh32(net_read_uint32(p++))) {
nfs_fattr3_to_stat(p, s ? *s : &dummy);
p += 21;
} else if (s) {
/* no attributes available */
*s = NULL;
}
return p;
}
/*
* nfs_mount_req - Mount an NFS Filesystem
*/
static int nfs_mount_req(struct nfs_priv *npriv)
{
uint32_t data[1024];
uint32_t *p;
int len;
int pathlen;
int ret;
pathlen = strlen(npriv->path);
debug("%s: %s\n", __func__, npriv->path);
p = &(data[0]);
p = rpc_add_credentials(p);
*p++ = hton32(pathlen);
if (pathlen & 3)
*(p + pathlen / 4) = 0;
memcpy (p, npriv->path, pathlen);
p += (pathlen + 3) / 4;
len = p - &(data[0]);
ret = rpc_req(npriv, PROG_MOUNT, MOUNT_ADDENTRY, data, len);
if (ret)
return ret;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
npriv->rootfh_len = ntoh32(net_read_uint32(p++));
if (npriv->rootfh_len > NFS3_FHSIZE) {
printf("%s: file handle too big: %lu\n", __func__,
(unsigned long)npriv->rootfh_len);
return -EIO;
}
memcpy(npriv->rootfh, p, npriv->rootfh_len);
p += DIV_ROUND_UP(npriv->rootfh_len, 4);
return 0;
}
/*
* nfs_umountall_req - Unmount all our NFS Filesystems on the Server
*/
static void nfs_umount_req(struct nfs_priv *npriv)
{
uint32_t data[1024];
uint32_t *p;
int len;
int pathlen;
pathlen = strlen(npriv->path);
p = &(data[0]);
p = rpc_add_credentials(p);
p = nfs_add_filename(p, pathlen, npriv->path);
len = p - &(data[0]);
rpc_req(npriv, PROG_MOUNT, MOUNT_UMOUNT, data, len);
}
/*
* nfs_lookup_req - Lookup Pathname
*
* *s is set to NULL if LOOKUP3resok doesn't contain obj_attributes.
*/
static int nfs_lookup_req(struct file_priv *priv,
uint32_t filename_len, const char *filename, struct stat **s)
{
uint32_t data[1024];
uint32_t *p;
int len;
int ret;
/*
* struct LOOKUP3args {
* diropargs3 what;
* };
*
* struct LOOKUP3resok {
* nfs_fh3 object;
* post_op_attr obj_attributes;
* post_op_attr dir_attributes;
* };
*
* struct LOOKUP3resfail {
* post_op_attr dir_attributes;
* };
*
* union LOOKUP3res switch (nfsstat3 status) {
* case NFS3_OK:
* LOOKUP3resok resok;
* default:
* LOOKUP3resfail resfail;
* };
*/
p = &(data[0]);
p = rpc_add_credentials(p);
/* what.dir */
p = nfs_add_fh3(p, priv->filefh_len, priv->filefh);
/* what.name */
p = nfs_add_filename(p, filename_len, filename);
len = p - &(data[0]);
ret = rpc_req(priv->npriv, PROG_NFS, NFSPROC3_LOOKUP, data, len);
if (ret)
return ret;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
priv->filefh_len = ntoh32(net_read_uint32(p++));
if (priv->filefh_len > NFS3_FHSIZE) {
debug("%s: file handle too big: %lu\n", __func__,
(unsigned long)priv->filefh_len);
return -EIO;
}
memcpy(priv->filefh, p, priv->filefh_len);
p += DIV_ROUND_UP(priv->filefh_len, 4);
if (s)
nfs_read_post_op_attr(p, s);
return 0;
}
static int nfs_attr_req(struct file_priv *priv, struct stat *s)
{
uint32_t data[1024];
uint32_t *p;
int len;
int ret;
/*
* struct GETATTR3args {
* nfs_fh3 object;
* }
*
* struct GETATTR3resok {
* fattr3 obj_attributes;
* };
*
* union GETATTR3res switch (nfsstat3 status) {
* case NFS3_OK:
* GETATTR3resok resok;
* default:
* void;
* }
*/
p = &(data[0]);
p = rpc_add_credentials(p);
/* object */
p = nfs_add_fh3(p, priv->filefh_len, priv->filefh);
len = p - &(data[0]);
ret = rpc_req(priv->npriv, PROG_NFS, NFSPROC3_GETATTR, data, len);
if (ret)
return ret;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
nfs_fattr3_to_stat(p, s);
return 0;
}
/*
* returns with dir->stream pointing to the first entry
* of dirlist3 res.resok.reply
*/
static void *nfs_readdirattr_req(struct file_priv *priv, struct nfs_dir *dir)
{
uint32_t data[1024];
uint32_t *p;
int len;
int ret;
void *buf;
/*
* struct READDIR3args {
* nfs_fh3 dir;
* cookie3 cookie;
* cookieverf3 cookieverf;
* count3 count;
* };
*
* struct entry3 {
* fileid3 fileid;
* filename3 name;
* cookie3 cookie;
* entry3 *nextentry;
* };
*
* struct dirlist3 {
* entry3 *entries;
* bool eof;
* };
*
* struct READDIR3resok {
* post_op_attr dir_attributes;
* cookieverf3 cookieverf;
* dirlist3 reply;
* };
*
* struct READDIR3resfail {
* post_op_attr dir_attributes;
* };
*
* union READDIR3res switch (nfsstat3 status) {
* case NFS3_OK:
* READDIR3resok resok;
* default:
* READDIR3resfail resfail;
* };
*/
p = &(data[0]);
p = rpc_add_credentials(p);
p = nfs_add_fh3(p, priv->filefh_len, priv->filefh);
p = nfs_add_uint64(p, dir->cookie);
memcpy(p, dir->cookieverf, NFS3_COOKIEVERFSIZE);
p += NFS3_COOKIEVERFSIZE / 4;
p = nfs_add_uint32(p, 1024); /* count */
ret = rpc_req(priv->npriv, PROG_NFS, NFSPROC3_READDIR, data, p - data);
if (ret)
return NULL;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
p = nfs_read_post_op_attr(p, NULL);
/* update cookieverf */
memcpy(dir->cookieverf, p, NFS3_COOKIEVERFSIZE);
p += NFS3_COOKIEVERFSIZE / 4;
len = nfs_packet + nfs_len - (void *)p;
if (!len) {
printf("%s: huh, no payload left\n", __func__);
return NULL;
}
buf = xzalloc(len);
memcpy(buf, p, len);
xdr_init(&dir->stream, buf, len);
/* now xdr points to dirlist3 res.resok.reply */
return buf;
}
/*
* nfs_read_req - Read File on NFS Server
*/
static int nfs_read_req(struct file_priv *priv, uint64_t offset,
uint32_t readlen)
{
uint32_t data[1024];
uint32_t *p;
int len;
int ret;
uint32_t rlen, eof;
/*
* struct READ3args {
* nfs_fh3 file;
* offset3 offset;
* count3 count;
* };
*
* struct READ3resok {
* post_op_attr file_attributes;
* count3 count;
* bool eof;
* opaque data<>;
* };
*
* struct READ3resfail {
* post_op_attr file_attributes;
* };
*
* union READ3res switch (nfsstat3 status) {
* case NFS3_OK:
* READ3resok resok;
* default:
* READ3resfail resfail;
* };
*/
p = &(data[0]);
p = rpc_add_credentials(p);
p = nfs_add_fh3(p, priv->filefh_len, priv->filefh);
p = nfs_add_uint64(p, offset);
p = nfs_add_uint32(p, readlen);
len = p - &(data[0]);
ret = rpc_req(priv->npriv, PROG_NFS, NFSPROC3_READ, data, len);
if (ret)
return ret;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
p = nfs_read_post_op_attr(p, NULL);
rlen = ntoh32(net_read_uint32(p));
/* skip over count */
p += 1;
eof = ntoh32(net_read_uint32(p));
/*
* skip over eof and count embedded in the representation of data
* assuming it equals rlen above.
*/
p += 2;
if (readlen && !rlen && !eof)
return -EIO;
kfifo_put(priv->fifo, (char *)p, rlen);
return 0;
}
static void nfs_handler(void *ctx, char *packet, unsigned len)
{
char *pkt = net_eth_to_udp_payload(packet);
nfs_state = STATE_DONE;
nfs_packet = pkt;
nfs_len = len;
}
static int nfs_create(struct device_d *dev, const char *pathname, mode_t mode)
{
return -ENOSYS;
}
static int nfs_unlink(struct device_d *dev, const char *pathname)
{
return -ENOSYS;
}
static int nfs_mkdir(struct device_d *dev, const char *pathname)
{
return -ENOSYS;
}
static int nfs_rmdir(struct device_d *dev, const char *pathname)
{
return -ENOSYS;
}
static int nfs_truncate(struct device_d *dev, FILE *f, ulong size)
{
return -ENOSYS;
}
static struct file_priv *nfs_do_open(struct device_d *dev,
const char *filename, struct stat **s)
{
struct file_priv *priv;
struct nfs_priv *npriv = dev->priv;
int ret;
const char *tok;
debug("%s: filename = %s\n", __func__, filename);
priv = xzalloc(sizeof(*priv));
priv->npriv = npriv;
if (!*filename) {
priv->filefh_len = npriv->rootfh_len;
memcpy(priv->filefh, npriv->rootfh, npriv->rootfh_len);
return priv;
}
filename++;
priv->filefh_len = npriv->rootfh_len;
memcpy(priv->filefh, npriv->rootfh, NFS3_FHSIZE);
while (*filename) {
size_t flen;
tok = strchr(filename, '/');
if (tok)
flen = tok - filename;
else
flen = strlen(filename);
ret = nfs_lookup_req(priv, flen, filename, s);
if (ret)
goto out;
if (tok)
filename += flen + 1;
else
break;
}
return priv;
out:
free(priv);
return ERR_PTR(ret);
}
static void nfs_do_close(struct file_priv *priv)
{
if (priv->fifo)
kfifo_free(priv->fifo);
free(priv);
}
static struct file_priv *nfs_do_stat(struct device_d *dev,
const char *filename, struct stat *s)
{
struct file_priv *priv;
int ret;
struct stat **sptr = &s;
debug("%s: filename = %s\n", __func__, filename);
priv = nfs_do_open(dev, filename, sptr);
if (IS_ERR(priv))
return priv;
if (!*sptr) {
/*
* The nfs server didn't provide obj_attributes in the lookup
* reply, so ask for them explicitly.
*/
ret = nfs_attr_req(priv, s);
if (ret) {
nfs_do_close(priv);
return ERR_PTR(ret);
}
}
return priv;
}
static int nfs_readlink_req(struct file_priv *priv, char* buf, size_t size)
{
uint32_t data[1024];
uint32_t *p;
uint32_t len;
int ret;
/*
* struct READLINK3args {
* nfs_fh3 symlink;
* };
*
* struct READLINK3resok {
* post_op_attr symlink_attributes;
* nfspath3 data;
* };
*
* struct READLINK3resfail {
* post_op_attr symlink_attributes;
* }
*
* union READLINK3res switch (nfsstat3 status) {
* case NFS3_OK:
* READLINK3resok resok;
* default:
* READLINK3resfail resfail;
* };
*/
p = &(data[0]);
p = rpc_add_credentials(p);
p = nfs_add_fh3(p, priv->filefh_len, priv->filefh);
len = p - &(data[0]);
ret = rpc_req(priv->npriv, PROG_NFS, NFSPROC3_READLINK, data, len);
if (ret)
return ret;
p = nfs_packet + sizeof(struct rpc_reply) + 4;
p = nfs_read_post_op_attr(p, NULL);
len = ntoh32(net_read_uint32(p)); /* new path length */
p++;
if (len > size)
return -ENOMEM;
memcpy(buf, p, len);
return 0;
}
static int nfs_readlink(struct device_d *dev, const char *filename,
char *realname, size_t size)
{
struct file_priv *priv;
int ret;
priv = nfs_do_open(dev, filename, NULL);
if (IS_ERR(priv))
return PTR_ERR(priv);
ret = nfs_readlink_req(priv, realname, size);
if (ret) {
nfs_do_close(priv);
return ret;
}
return 0;
}
static int nfs_open(struct device_d *dev, FILE *file, const char *filename)
{
struct file_priv *priv;
struct stat s;
priv = nfs_do_stat(dev, filename, &s);
if (IS_ERR(priv))
return PTR_ERR(priv);
file->inode = priv;
file->size = s.st_size;
priv->fifo = kfifo_alloc(1024);
if (!priv->fifo) {
free(priv);
return -ENOMEM;
}
return 0;
}
static int nfs_close(struct device_d *dev, FILE *file)
{
struct file_priv *priv = file->inode;
nfs_do_close(priv);
return 0;
}
static int nfs_write(struct device_d *_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)
{
struct file_priv *priv = file->inode;
if (insize > 1024)
insize = 1024;
if (insize && !kfifo_len(priv->fifo)) {
int ret = nfs_read_req(priv, file->pos, insize);
if (ret)
return ret;
}
return kfifo_get(priv->fifo, buf, insize);
}
static loff_t nfs_lseek(struct device_d *dev, FILE *file, loff_t pos)
{
struct file_priv *priv = file->inode;
file->pos = pos;
kfifo_reset(priv->fifo);
return file->pos;
}
static DIR *nfs_opendir(struct device_d *dev, const char *pathname)
{
struct file_priv *priv;
void *buf = NULL;
struct nfs_dir *dir;
priv = nfs_do_open(dev, pathname, NULL);
if (IS_ERR(priv))
return NULL;
dir = xzalloc(sizeof(*dir));
dir->priv = priv;
/* cookie == 0 and cookieverf == 0 means start of dir */
buf = nfs_readdirattr_req(priv, dir);
if (!buf) {
debug("%s: nfs_readdirattr_req failed\n", __func__);
goto err;
}
return &dir->dir;
err:
free(buf);
free(dir);
nfs_do_close(priv);
return NULL;
}
static struct dirent *nfs_readdir(struct device_d *dev, DIR *dir)
{
struct nfs_dir *ndir = container_of(dir, struct nfs_dir, dir);
uint32_t *p;
int ret;
int len;
struct xdr_stream *xdr = &ndir->stream;
again:
p = xdr_inline_decode(xdr, 4);
if (!p) {
printf("%s: premature end of packet\n", __func__);
return NULL;
}
if (!net_read_uint32(p)) {
/* eof? */
p = xdr_inline_decode(xdr, 4);
if (!p) {
printf("%s: premature end of packet\n", __func__);
return NULL;
}
if (net_read_uint32(p))
return NULL;
if (!nfs_readdirattr_req(ndir->priv, ndir)) {
printf("%s: nfs_readdirattr_req failed\n", __func__);
return NULL;
}
goto again;
}
/* there is another entry available in the last reply */
/* skip over fileid */
p = xdr_inline_decode(xdr, 8);
if (!p) {
printf("%s: premature end of packet\n", __func__);
return NULL;
}
ret = decode_filename(xdr, ndir->ent.d_name, &len);
if (ret)
return NULL;
p = xdr_inline_decode(xdr, 8);
if (!p) {
printf("%s: premature end of packet\n", __func__);
return NULL;
}
ndir->cookie = ntoh64(net_read_uint64(p));
return &ndir->ent;
}
static int nfs_closedir(struct device_d *dev, DIR *dir)
{
struct nfs_dir *ndir = (void *)dir;
nfs_do_close(ndir->priv);
free(ndir->stream.buf);
free(ndir);
return 0;
}
static int nfs_stat(struct device_d *dev, const char *filename, struct stat *s)
{
struct file_priv *priv;
priv = nfs_do_stat(dev, filename, s);
if (IS_ERR(priv)) {
return PTR_ERR(priv);
} else {
nfs_do_close(priv);
return 0;
}
}
static int nfs_probe(struct device_d *dev)
{
struct fs_device_d *fsdev = dev_to_fs_device(dev);
struct nfs_priv *npriv = xzalloc(sizeof(struct nfs_priv));
char *tmp = xstrdup(fsdev->backingstore);
char *path;
int ret;
dev->priv = npriv;
debug("nfs: mount: %s\n", fsdev->backingstore);
path = strchr(tmp, ':');
if (!path) {
ret = -EINVAL;
goto err;
}
*path = 0;
npriv->path = xstrdup(path + 1);
npriv->server = resolv(tmp);
debug("nfs: server: %s path: %s\n", tmp, npriv->path);
npriv->con = net_udp_new(npriv->server, 0, nfs_handler, npriv);
if (IS_ERR(npriv->con)) {
ret = PTR_ERR(npriv->con);
goto err1;
}
/* Need a priviliged source port */
net_udp_bind(npriv->con, 1000);
parseopt_hu(fsdev->options, "mountport", &npriv->mount_port);
if (!npriv->mount_port) {
ret = rpc_lookup_req(npriv, PROG_MOUNT, 3);
if (ret < 0) {
printf("lookup mount port failed with %d\n", ret);
goto err2;
}
npriv->mount_port = ret;
}
debug("mount port: %hu\n", npriv->mount_port);
parseopt_hu(fsdev->options, "port", &npriv->nfs_port);
if (!npriv->nfs_port) {
ret = rpc_lookup_req(npriv, PROG_NFS, 3);
if (ret < 0) {
printf("lookup nfs port failed with %d\n", ret);
goto err2;
}
npriv->nfs_port = ret;
}
debug("nfs port: %d\n", npriv->nfs_port);
ret = nfs_mount_req(npriv);
if (ret) {
printf("mounting failed with %d\n", ret);
goto err2;
}
free(tmp);
return 0;
err2:
net_unregister(npriv->con);
err1:
free(npriv->path);
err:
free(tmp);
free(npriv);
return ret;
}
static void nfs_remove(struct device_d *dev)
{
struct nfs_priv *npriv = dev->priv;
nfs_umount_req(npriv);
net_unregister(npriv->con);
free(npriv->path);
free(npriv);
}
static struct fs_driver_d nfs_driver = {
.open = nfs_open,
.close = nfs_close,
.read = nfs_read,
.lseek = nfs_lseek,
.opendir = nfs_opendir,
.readdir = nfs_readdir,
.closedir = nfs_closedir,
.stat = nfs_stat,
.create = nfs_create,
.unlink = nfs_unlink,
.mkdir = nfs_mkdir,
.rmdir = nfs_rmdir,
.write = nfs_write,
.truncate = nfs_truncate,
.readlink = nfs_readlink,
.flags = 0,
.drv = {
.probe = nfs_probe,
.remove = nfs_remove,
.name = "nfs",
}
};
static int nfs_init(void)
{
return register_fs_driver(&nfs_driver);
}
coredevice_initcall(nfs_init);