net: NFS: Add NFSv3 support
authorGuillaume GARDET <guillaume.gardet@free.fr>
Fri, 29 Jul 2016 09:31:00 +0000 (11:31 +0200)
committerJoe Hershberger <joe.hershberger@ni.com>
Mon, 22 Aug 2016 19:20:20 +0000 (14:20 -0500)
This patch enables NFSv3 support.
If NFSv2 is available use it as usual.
If NFSv2 is not available, but NFSv3 is available, use NFSv3.
If NFSv2 and NFSv3 are not available, print an error message since NFSv4 is not supported.

Tested on iMX6 sabrelite with 4 Linux NFS servers:
  * NFSv2 + NFSv3 + NFSv4 server: use NFSv2 protocol
  * NFSv2 + NFSv3 server: use NFSv2 protocol
  * NFSv3 + NFSv4 server: use NFSv3 protocol
  * NFSv3 server: use NFSv3 protocol

Signed-off-by: Guillaume GARDET <guillaume.gardet@free.fr>
Cc: Tom Rini <trini@konsulko.com>
Cc: joe.hershberger@ni.com
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
net/nfs.c
net/nfs.h

index d7aeef916207d037f579e09b3c5576247c4f51a0..f61b96e00e4052f96192c3da5567ecbd219f4135 100644 (file)
--- a/net/nfs.c
+++ b/net/nfs.c
  * possible, maximum 16 steps). There is no clearing of ".."'s inside the
  * path, so please DON'T DO THAT. thx. */
 
+/* NOTE 4: NFSv3 support added by Guillaume GARDET, 2016-June-20.
+ * NFSv2 is still used by default. But if server does not support NFSv2, then
+ * NFSv3 is used, if available on NFS server. */
+
 #include <common.h>
 #include <command.h>
 #include <net.h>
@@ -47,8 +51,11 @@ static int nfs_offset = -1;
 static int nfs_len;
 static ulong nfs_timeout = NFS_TIMEOUT;
 
-static char dirfh[NFS_FHSIZE]; /* file handle of directory */
-static char filefh[NFS_FHSIZE]; /* file handle of kernel image */
+static char dirfh[NFS_FHSIZE]; /* NFSv2 / NFSv3 file handle of directory */
+static char filefh[NFS_FHSIZE]; /* NFSv2 file handle */
+
+static char filefh3[NFS3_FHSIZE];      /* NFSv3 file handle  */
+static int filefh3_length;     /* (variable) length of filefh3 */
 
 static enum net_loop_state nfs_download_state;
 static struct in_addr nfs_server_ip;
@@ -69,6 +76,10 @@ static char *nfs_filename;
 static char *nfs_path;
 static char nfs_path_buff[2048];
 
+#define NFSV2_FLAG 1
+#define NFSV3_FLAG 1 << 1
+static char supported_nfs_versions = NFSV2_FLAG | NFSV3_FLAG;
+
 static inline int store_block(uchar *src, unsigned offset, unsigned len)
 {
        ulong newsize = offset + len;
@@ -187,7 +198,18 @@ static void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen)
        pkt.u.call.type = htonl(MSG_CALL);
        pkt.u.call.rpcvers = htonl(2);  /* use RPC version 2 */
        pkt.u.call.prog = htonl(rpc_prog);
-       pkt.u.call.vers = htonl(2);     /* portmapper is version 2 */
+       switch (rpc_prog) {
+       case PROG_NFS:
+               if (supported_nfs_versions & NFSV2_FLAG)
+                       pkt.u.call.vers = htonl(2);     /* NFS v2 */
+               else /* NFSV3_FLAG */
+                       pkt.u.call.vers = htonl(3);     /* NFS v3 */
+               break;
+       case PROG_PORTMAP:
+       case PROG_MOUNT:
+       default:
+               pkt.u.call.vers = htonl(2);     /* portmapper is version 2 */
+       }
        pkt.u.call.proc = htonl(rpc_proc);
        p = (uint32_t *)&(pkt.u.call.data);
 
@@ -223,7 +245,6 @@ static void rpc_lookup_req(int prog, int ver)
        data[5] = htonl(ver);
        data[6] = htonl(17);    /* IP_UDP */
        data[7] = 0;
-
        rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8);
 }
 
@@ -290,8 +311,14 @@ static void nfs_readlink_req(void)
        p = &(data[0]);
        p = rpc_add_credentials(p);
 
-       memcpy(p, filefh, NFS_FHSIZE);
-       p += (NFS_FHSIZE / 4);
+       if (supported_nfs_versions & NFSV2_FLAG) {
+               memcpy(p, filefh, NFS_FHSIZE);
+               p += (NFS_FHSIZE / 4);
+       } else { /* NFSV3_FLAG */
+               *p++ = htonl(filefh3_length);
+               memcpy(p, filefh3, filefh3_length);
+               p += (filefh3_length / 4);
+       }
 
        len = (uint32_t *)p - (uint32_t *)&(data[0]);
 
@@ -313,17 +340,32 @@ static void nfs_lookup_req(char *fname)
        p = &(data[0]);
        p = rpc_add_credentials(p);
 
-       memcpy(p, dirfh, NFS_FHSIZE);
-       p += (NFS_FHSIZE / 4);
-       *p++ = htonl(fnamelen);
-       if (fnamelen & 3)
-               *(p + fnamelen / 4) = 0;
-       memcpy(p, fname, fnamelen);
-       p += (fnamelen + 3) / 4;
-
-       len = (uint32_t *)p - (uint32_t *)&(data[0]);
-
-       rpc_req(PROG_NFS, NFS_LOOKUP, data, len);
+       if (supported_nfs_versions & NFSV2_FLAG) {
+               memcpy(p, dirfh, NFS_FHSIZE);
+               p += (NFS_FHSIZE / 4);
+               *p++ = htonl(fnamelen);
+               if (fnamelen & 3)
+                       *(p + fnamelen / 4) = 0;
+               memcpy(p, fname, fnamelen);
+               p += (fnamelen + 3) / 4;
+
+               len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+               rpc_req(PROG_NFS, NFS_LOOKUP, data, len);
+       } else {  /* NFSV3_FLAG */
+               *p++ = htonl(NFS_FHSIZE);       /* Dir handle length */
+               memcpy(p, dirfh, NFS_FHSIZE);
+               p += (NFS_FHSIZE / 4);
+               *p++ = htonl(fnamelen);
+               if (fnamelen & 3)
+                       *(p + fnamelen / 4) = 0;
+               memcpy(p, fname, fnamelen);
+               p += (fnamelen + 3) / 4;
+
+               len = (uint32_t *)p - (uint32_t *)&(data[0]);
+
+               rpc_req(PROG_NFS, NFS3PROC_LOOKUP, data, len);
+       }
 }
 
 /**************************************************************************
@@ -338,11 +380,21 @@ static void nfs_read_req(int offset, int readlen)
        p = &(data[0]);
        p = rpc_add_credentials(p);
 
-       memcpy(p, filefh, NFS_FHSIZE);
-       p += (NFS_FHSIZE / 4);
-       *p++ = htonl(offset);
-       *p++ = htonl(readlen);
-       *p++ = 0;
+       if (supported_nfs_versions & NFSV2_FLAG) {
+               memcpy(p, filefh, NFS_FHSIZE);
+               p += (NFS_FHSIZE / 4);
+               *p++ = htonl(offset);
+               *p++ = htonl(readlen);
+               *p++ = 0;
+       } else { /* NFSV3_FLAG */
+               *p++ = htonl(filefh3_length);
+               memcpy(p, filefh3, filefh3_length);
+               p += (filefh3_length / 4);
+               *p++ = htonl(0); /* offset is 64-bit long, so fill with 0 */
+               *p++ = htonl(offset);
+               *p++ = htonl(readlen);
+               *p++ = 0;
+       }
 
        len = (uint32_t *)p - (uint32_t *)&(data[0]);
 
@@ -358,10 +410,16 @@ static void nfs_send(void)
 
        switch (nfs_state) {
        case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
-               rpc_lookup_req(PROG_MOUNT, 1);
+               if (supported_nfs_versions & NFSV2_FLAG)
+                       rpc_lookup_req(PROG_MOUNT, 1);
+               else  /* NFSV3_FLAG */
+                       rpc_lookup_req(PROG_MOUNT, 3);
                break;
        case STATE_PRCLOOKUP_PROG_NFS_REQ:
-               rpc_lookup_req(PROG_NFS, 2);
+               if (supported_nfs_versions & NFSV2_FLAG)
+                       rpc_lookup_req(PROG_NFS, 2);
+               else  /* NFSV3_FLAG */
+                       rpc_lookup_req(PROG_NFS, 3);
                break;
        case STATE_MOUNT_REQ:
                nfs_mount_req(nfs_path);
@@ -435,6 +493,7 @@ static int nfs_mount_reply(uchar *pkt, unsigned len)
                return -1;
 
        fs_mounted = 1;
+       /*  NFSv2 and NFSv3 use same structure */
        memcpy(dirfh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
 
        return 0;
@@ -482,14 +541,33 @@ static int nfs_lookup_reply(uchar *pkt, unsigned len)
            rpc_pkt.u.reply.astatus  ||
            rpc_pkt.u.reply.data[0]) {
                switch (ntohl(rpc_pkt.u.reply.astatus)) {
-               case 0: /* Not an error */
+               case NFS_RPC_SUCCESS: /* Not an error */
                        break;
-               case 2: /* Remote can't support NFS version */
-                       printf("*** ERROR: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n",
-                              2,
-                              ntohl(rpc_pkt.u.reply.data[0]),
-                              ntohl(rpc_pkt.u.reply.data[1]));
+               case NFS_RPC_PROG_MISMATCH:
+                       /* Remote can't support NFS version */
+                       switch (ntohl(rpc_pkt.u.reply.data[0])) {
+                       /* Minimal supported NFS version */
+                       case 3:
+                               debug("*** Waring: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n",
+                                     (supported_nfs_versions & NFSV2_FLAG) ? 2 : 3,
+                                     ntohl(rpc_pkt.u.reply.data[0]),
+                                     ntohl(rpc_pkt.u.reply.data[1]));
+                               debug("Will retry with NFSv3\n");
+                               /* Clear NFSV2_FLAG from supported versions */
+                               supported_nfs_versions &= ~NFSV2_FLAG;
+                               return -NFS_RPC_PROG_MISMATCH;
+                       case 4:
+                       default:
+                               printf("*** ERROR: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n",
+                                      (supported_nfs_versions & NFSV2_FLAG) ? 2 : 3,
+                                      ntohl(rpc_pkt.u.reply.data[0]),
+                                      ntohl(rpc_pkt.u.reply.data[1]));
+                       }
                        break;
+               case NFS_RPC_PROG_UNAVAIL:
+               case NFS_RPC_PROC_UNAVAIL:
+               case NFS_RPC_GARBAGE_ARGS:
+               case NFS_RPC_SYSTEM_ERR:
                default: /* Unknown error on 'accept state' flag */
                        printf("*** ERROR: accept state error (%d)\n",
                               ntohl(rpc_pkt.u.reply.astatus));
@@ -498,7 +576,14 @@ static int nfs_lookup_reply(uchar *pkt, unsigned len)
                return -1;
        }
 
-       memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
+       if (supported_nfs_versions & NFSV2_FLAG) {
+               memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
+       } else {  /* NFSV3_FLAG */
+               filefh3_length = ntohl(rpc_pkt.u.reply.data[1]);
+               if (filefh3_length > NFS3_FHSIZE)
+                       filefh3_length  = NFS3_FHSIZE;
+               memcpy(filefh3, rpc_pkt.u.reply.data + 2, filefh3_length);
+       }
 
        return 0;
 }
@@ -523,18 +608,68 @@ static int nfs_readlink_reply(uchar *pkt, unsigned len)
            rpc_pkt.u.reply.data[0])
                return -1;
 
-       rlen = ntohl(rpc_pkt.u.reply.data[1]); /* new path length */
+       if (supported_nfs_versions & NFSV2_FLAG) {
 
-       if (*((char *)&(rpc_pkt.u.reply.data[2])) != '/') {
-               int pathlen;
-               strcat(nfs_path, "/");
-               pathlen = strlen(nfs_path);
-               memcpy(nfs_path + pathlen, (uchar *)&(rpc_pkt.u.reply.data[2]),
-                      rlen);
-               nfs_path[pathlen + rlen] = 0;
-       } else {
-               memcpy(nfs_path, (uchar *)&(rpc_pkt.u.reply.data[2]), rlen);
-               nfs_path[rlen] = 0;
+               rlen = ntohl(rpc_pkt.u.reply.data[1]); /* new path length */
+
+               if (*((char *)&(rpc_pkt.u.reply.data[2])) != '/') {
+                       int pathlen;
+                       strcat(nfs_path, "/");
+                       pathlen = strlen(nfs_path);
+                       memcpy(nfs_path + pathlen,
+                              (uchar *)&(rpc_pkt.u.reply.data[2]),
+                              rlen);
+                       nfs_path[pathlen + rlen] = 0;
+               } else {
+                       memcpy(nfs_path,
+                              (uchar *)&(rpc_pkt.u.reply.data[2]),
+                              rlen);
+                       nfs_path[rlen] = 0;
+               }
+       } else {  /* NFSV3_FLAG */
+               int nfsv3_data_offset = 0;
+               if (ntohl(rpc_pkt.u.reply.data[1]) != 0) {
+                       /* 'attributes_follow' flag is TRUE,
+                        * so we have attributes on 21 bytes */
+                       /* Skip unused values :
+                               type;   32 bits value,
+                               mode;   32 bits value,
+                               nlink;  32 bits value,
+                               uid;    32 bits value,
+                               gid;    32 bits value,
+                               size;   64 bits value,
+                               used;   64 bits value,
+                               rdev;   64 bits value,
+                               fsid;   64 bits value,
+                               fileid; 64 bits value,
+                               atime;  64 bits value,
+                               mtime;  64 bits value,
+                               ctime;  64 bits value,
+                       */
+                       nfsv3_data_offset = 22;
+               } else {
+                       /* 'attributes_follow' flag is FALSE,
+                        * so we don't have any attributes */
+                       nfsv3_data_offset = 1;
+               }
+
+               /* new path length */
+               rlen = ntohl(rpc_pkt.u.reply.data[1+nfsv3_data_offset]);
+
+               if (*((char *)&(rpc_pkt.u.reply.data[2+nfsv3_data_offset])) != '/') {
+                       int pathlen;
+                       strcat(nfs_path, "/");
+                       pathlen = strlen(nfs_path);
+                       memcpy(nfs_path + pathlen,
+                              (uchar *)&(rpc_pkt.u.reply.data[2+nfsv3_data_offset]),
+                              rlen);
+                       nfs_path[pathlen + rlen] = 0;
+               } else {
+                       memcpy(nfs_path,
+                              (uchar *)&(rpc_pkt.u.reply.data[2+nfsv3_data_offset]),
+                              rlen);
+                       nfs_path[rlen] = 0;
+               }
        }
        return 0;
 }
@@ -543,6 +678,7 @@ static int nfs_read_reply(uchar *pkt, unsigned len)
 {
        struct rpc_t rpc_pkt;
        int rlen;
+       uchar *data_ptr;
 
        debug("%s\n", __func__);
 
@@ -570,10 +706,47 @@ static int nfs_read_reply(uchar *pkt, unsigned len)
        if (!(nfs_offset % ((NFS_READ_SIZE / 2) * 10)))
                putc('#');
 
-       rlen = ntohl(rpc_pkt.u.reply.data[18]);
-       if (store_block((uchar *)pkt + sizeof(rpc_pkt.u.reply),
-                       nfs_offset, rlen))
-               return -9999;
+       if (supported_nfs_versions & NFSV2_FLAG) {
+               rlen = ntohl(rpc_pkt.u.reply.data[18]);
+               data_ptr = (uchar *)&(rpc_pkt.u.reply.data[19]);
+       } else {  /* NFSV3_FLAG */
+               if (ntohl(rpc_pkt.u.reply.data[1]) != 0) {
+                       /* 'attributes_follow' is TRUE,
+                        * so we have attributes on 21 bytes */
+                       /* Skip unused values :
+                               type;   32 bits value,
+                               mode;   32 bits value,
+                               nlink;  32 bits value,
+                               uid;    32 bits value,
+                               gid;    32 bits value,
+                               size;   64 bits value,
+                               used;   64 bits value,
+                               rdev;   64 bits value,
+                               fsid;   64 bits value,
+                               fileid; 64 bits value,
+                               atime;  64 bits value,
+                               mtime;  64 bits value,
+                               ctime;  64 bits value,
+                       */
+                       rlen = ntohl(rpc_pkt.u.reply.data[23]); /* count value */
+                       /* Skip unused values :
+                               EOF:            32 bits value,
+                               data_size:      32 bits value,
+                       */
+                       data_ptr = (uchar *)&(rpc_pkt.u.reply.data[26]);
+               } else {
+                       /* attributes_follow is FALSE, so we don't have any attributes */
+                       rlen = ntohl(rpc_pkt.u.reply.data[2]); /* count value */
+                       /* Skip unused values :
+                               EOF:            32 bits value,
+                               data_size:      32 bits value,
+                       */
+                       data_ptr = (uchar *)&(rpc_pkt.u.reply.data[5]);
+               }
+       }
+
+       if (store_block(data_ptr, nfs_offset, rlen))
+                       return -9999;
 
        return rlen;
 }
@@ -657,6 +830,13 @@ static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip,
                        puts("*** ERROR: File lookup fail\n");
                        nfs_state = STATE_UMOUNT_REQ;
                        nfs_send();
+               } else if (reply == -NFS_RPC_PROG_MISMATCH && supported_nfs_versions != 0) {
+                       /* umount */
+                       nfs_state = STATE_UMOUNT_REQ;
+                       nfs_send();
+                       /* And retry with another supported version */
+                       nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ;
+                       nfs_send();
                } else {
                        nfs_state = STATE_READ_REQ;
                        nfs_offset = 0;
@@ -696,6 +876,8 @@ static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip,
                } else {
                        if (!rlen)
                                nfs_download_state = NETLOOP_SUCCESS;
+                       if (rlen < 0)
+                               printf("NFS READ error (%d)\n", rlen);
                        nfs_state = STATE_UMOUNT_REQ;
                        nfs_send();
                }
index 2a1f4db9b43accbee0486423fcfec34475018561..45da246aa1e1bfa6ad8d7b267ab0a41fde8e231d 100644 (file)
--- a/net/nfs.h
+++ b/net/nfs.h
 #define NFS_READLINK    5
 #define NFS_READ        6
 
+#define NFS3PROC_LOOKUP 3
+
 #define NFS_FHSIZE      32
+#define NFS3_FHSIZE     64
 
 #define NFSERR_PERM     1
 #define NFSERR_NOENT    2
 #define NFS_READ_SIZE 1024 /* biggest power of two that fits Ether frame */
 #endif
 
+/* Values for Accept State flag on RPC answers (See: rfc1831) */
+enum rpc_accept_stat {
+       NFS_RPC_SUCCESS = 0,    /* RPC executed successfully */
+       NFS_RPC_PROG_UNAVAIL = 1,       /* remote hasn't exported program */
+       NFS_RPC_PROG_MISMATCH = 2,      /* remote can't support version # */
+       NFS_RPC_PROC_UNAVAIL = 3,       /* program can't support procedure */
+       NFS_RPC_GARBAGE_ARGS = 4,       /* procedure can't decode params */
+       NFS_RPC_SYSTEM_ERR = 5  /* errors like memory allocation failure */
+};
+
 struct rpc_t {
        union {
                uint8_t data[2048];
@@ -63,7 +76,7 @@ struct rpc_t {
                        uint32_t verifier;
                        uint32_t v2;
                        uint32_t astatus;
-                       uint32_t data[19];
+                       uint32_t data[NFS_READ_SIZE];
                } reply;
        } u;
 };