u-boot/net/nfs.c
Thomas RIENOESSL e4bd95bba1 net: add NFSv1 support
NFSv1 support added by Christian Gmeiner, Thomas Rienoessl,
September 27, 2018. As of now, NFSv3 is the default choice.
if the server does not support NFSv3, we fall back to
versions 2 or 1.

Signed-off-by: Thomas RIENOESSL <thomas.rienoessl@bachmann.info>
2023-05-05 17:48:44 -04:00

979 lines
24 KiB
C

/*
* NFS support driver - based on etherboot and U-BOOT's tftp.c
*
* Masami Komiya <mkomiya@sonare.it> 2004
*
*/
/* NOTE: the NFS code is heavily inspired by the NetBSD netboot code (read:
* large portions are copied verbatim) as distributed in OSKit 0.97. A few
* changes were necessary to adapt the code to Etherboot and to fix several
* inconsistencies. Also the RPC message preparation is done "by hand" to
* avoid adding netsprintf() which I find hard to understand and use. */
/* NOTE 2: Etherboot does not care about things beyond the kernel image, so
* it loads the kernel image off the boot server (ARP_SERVER) and does not
* access the client root disk (root-path in dhcpd.conf), which would use
* ARP_ROOTSERVER. The root disk is something the operating system we are
* about to load needs to use. This is different from the OSKit 0.97 logic. */
/* NOTE 3: Symlink handling introduced by Anselm M Hoffmeister, 2003-July-14
* If a symlink is encountered, it is followed as far as possible (recursion
* 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. */
/* NOTE 5: NFSv1 support added by Christian Gmeiner, Thomas Rienoessl,
* September 27, 2018. As of now, NFSv3 is the default choice. If the server
* does not support NFSv3, we fall back to versions 2 or 1. */
#include <common.h>
#include <command.h>
#include <display_options.h>
#ifdef CONFIG_SYS_DIRECT_FLASH_NFS
#include <flash.h>
#endif
#include <image.h>
#include <log.h>
#include <net.h>
#include <malloc.h>
#include <mapmem.h>
#include "nfs.h"
#include "bootp.h"
#include <time.h>
#define HASHES_PER_LINE 65 /* Number of "loading" hashes per line */
#define NFS_RETRY_COUNT 30
#define NFS_RPC_ERR 1
#define NFS_RPC_DROP 124
static int fs_mounted;
static unsigned long rpc_id;
static int nfs_offset = -1;
static int nfs_len;
static const ulong nfs_timeout = CONFIG_NFS_TIMEOUT;
static char dirfh[NFS_FHSIZE]; /* NFSv2 / NFSv3 file handle of directory */
static char filefh[NFS3_FHSIZE]; /* NFSv2 / NFSv3 file handle */
static unsigned int filefh3_length; /* (variable) length of filefh when NFSv3 */
static enum net_loop_state nfs_download_state;
static struct in_addr nfs_server_ip;
static int nfs_server_mount_port;
static int nfs_server_port;
static int nfs_our_port;
static int nfs_timeout_count;
static int nfs_state;
#define STATE_PRCLOOKUP_PROG_MOUNT_REQ 1
#define STATE_PRCLOOKUP_PROG_NFS_REQ 2
#define STATE_MOUNT_REQ 3
#define STATE_UMOUNT_REQ 4
#define STATE_LOOKUP_REQ 5
#define STATE_READ_REQ 6
#define STATE_READLINK_REQ 7
static char *nfs_filename;
static char *nfs_path;
static char nfs_path_buff[2048];
enum nfs_version {
NFS_UNKOWN = 0,
NFS_V1 = 1,
NFS_V2 = 2,
NFS_V3 = 3,
};
static enum nfs_version choosen_nfs_version = NFS_V3;
static inline int store_block(uchar *src, unsigned offset, unsigned len)
{
ulong newsize = offset + len;
#ifdef CONFIG_SYS_DIRECT_FLASH_NFS
int i, rc = 0;
for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
/* start address in flash? */
if (image_load_addr + offset >= flash_info[i].start[0]) {
rc = 1;
break;
}
}
if (rc) { /* Flash is destination for this packet */
rc = flash_write((uchar *)src, (ulong)image_load_addr + offset,
len);
if (rc) {
flash_perror(rc);
return -1;
}
} else
#endif /* CONFIG_SYS_DIRECT_FLASH_NFS */
{
void *ptr = map_sysmem(image_load_addr + offset, len);
memcpy(ptr, src, len);
unmap_sysmem(ptr);
}
if (net_boot_file_size < (offset + len))
net_boot_file_size = newsize;
return 0;
}
static char *basename(char *path)
{
char *fname;
fname = path + strlen(path) - 1;
while (fname >= path) {
if (*fname == '/') {
fname++;
break;
}
fname--;
}
return fname;
}
static char *dirname(char *path)
{
char *fname;
fname = basename(path);
--fname;
*fname = '\0';
return path;
}
/**************************************************************************
RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries
**************************************************************************/
static uint32_t *rpc_add_credentials(uint32_t *p)
{
/* Here's the executive summary on authentication requirements of the
* various NFS server implementations: Linux accepts both AUTH_NONE
* and AUTH_UNIX authentication (also accepts an empty hostname field
* in the AUTH_UNIX scheme). *BSD refuses AUTH_NONE, but accepts
* AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX
* scheme). To be safe, use AUTH_UNIX and pass the hostname if we have
* it (if the BOOTP/DHCP reply didn't give one, just use an empty
* hostname). */
/* Provide an AUTH_UNIX credential. */
*p++ = htonl(1); /* AUTH_UNIX */
*p++ = htonl(20); /* auth length */
*p++ = 0; /* stamp */
*p++ = 0; /* hostname string */
*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;
}
/**************************************************************************
RPC_LOOKUP - Lookup RPC Port numbers
**************************************************************************/
static void rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen)
{
struct rpc_t rpc_pkt;
unsigned long id;
uint32_t *p;
int pktlen;
int sport;
id = ++rpc_id;
rpc_pkt.u.call.id = htonl(id);
rpc_pkt.u.call.type = htonl(MSG_CALL);
rpc_pkt.u.call.rpcvers = htonl(2); /* use RPC version 2 */
rpc_pkt.u.call.prog = htonl(rpc_prog);
switch (rpc_prog) {
case PROG_NFS:
switch (choosen_nfs_version) {
case NFS_V1:
case NFS_V2:
rpc_pkt.u.call.vers = htonl(2);
break;
case NFS_V3:
rpc_pkt.u.call.vers = htonl(3);
break;
case NFS_UNKOWN:
/* nothing to do */
break;
}
break;
case PROG_MOUNT:
switch (choosen_nfs_version) {
case NFS_V1:
rpc_pkt.u.call.vers = htonl(1);
break;
case NFS_V2:
rpc_pkt.u.call.vers = htonl(2);
break;
case NFS_V3:
rpc_pkt.u.call.vers = htonl(3);
break;
case NFS_UNKOWN:
/* nothing to do */
break;
}
break;
case PROG_PORTMAP:
default:
rpc_pkt.u.call.vers = htonl(2); /* portmapper is version 2 */
}
rpc_pkt.u.call.proc = htonl(rpc_proc);
p = rpc_pkt.u.call.data;
if (datalen)
memcpy(p, data, datalen * sizeof(uint32_t));
pktlen = (char *)p + datalen * sizeof(uint32_t) - (char *)&rpc_pkt;
memcpy((char *)net_tx_packet + net_eth_hdr_size() + IP_UDP_HDR_SIZE,
&rpc_pkt.u.data[0], pktlen);
if (rpc_prog == PROG_PORTMAP)
sport = SUNRPC_PORT;
else if (rpc_prog == PROG_MOUNT)
sport = nfs_server_mount_port;
else
sport = nfs_server_port;
net_send_udp_packet(net_server_ethaddr, nfs_server_ip, sport,
nfs_our_port, pktlen);
}
/**************************************************************************
RPC_LOOKUP - Lookup RPC Port numbers
**************************************************************************/
static void rpc_lookup_req(int prog, int ver)
{
uint32_t data[16];
data[0] = 0; data[1] = 0; /* auth credential */
data[2] = 0; data[3] = 0; /* auth verifier */
data[4] = htonl(prog);
data[5] = htonl(ver);
data[6] = htonl(17); /* IP_UDP */
data[7] = 0;
rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8);
}
/**************************************************************************
NFS_MOUNT - Mount an NFS Filesystem
**************************************************************************/
static void nfs_mount_req(char *path)
{
uint32_t data[1024];
uint32_t *p;
int len;
int pathlen;
pathlen = strlen(path);
p = &(data[0]);
p = rpc_add_credentials(p);
*p++ = htonl(pathlen);
if (pathlen & 3)
*(p + pathlen / 4) = 0;
memcpy(p, path, pathlen);
p += (pathlen + 3) / 4;
len = (uint32_t *)p - (uint32_t *)&(data[0]);
rpc_req(PROG_MOUNT, MOUNT_ADDENTRY, data, len);
}
/**************************************************************************
NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server
**************************************************************************/
static void nfs_umountall_req(void)
{
uint32_t data[1024];
uint32_t *p;
int len;
if ((nfs_server_mount_port == -1) || (!fs_mounted))
/* Nothing mounted, nothing to umount */
return;
p = &(data[0]);
p = rpc_add_credentials(p);
len = (uint32_t *)p - (uint32_t *)&(data[0]);
rpc_req(PROG_MOUNT, MOUNT_UMOUNTALL, data, len);
}
/***************************************************************************
* NFS_READLINK (AH 2003-07-14)
* This procedure is called when read of the first block fails -
* this probably happens when it's a directory or a symlink
* In case of successful readlink(), the dirname is manipulated,
* so that inside the nfs() function a recursion can be done.
**************************************************************************/
static void nfs_readlink_req(void)
{
uint32_t data[1024];
uint32_t *p;
int len;
p = &(data[0]);
p = rpc_add_credentials(p);
if (choosen_nfs_version != NFS_V3) {
memcpy(p, filefh, NFS_FHSIZE);
p += (NFS_FHSIZE / 4);
} else { /* NFS_V3 */
*p++ = htonl(filefh3_length);
memcpy(p, filefh, filefh3_length);
p += (filefh3_length / 4);
}
len = (uint32_t *)p - (uint32_t *)&(data[0]);
rpc_req(PROG_NFS, NFS_READLINK, data, len);
}
/**************************************************************************
NFS_LOOKUP - Lookup Pathname
**************************************************************************/
static void nfs_lookup_req(char *fname)
{
uint32_t data[1024];
uint32_t *p;
int len;
int fnamelen;
fnamelen = strlen(fname);
p = &(data[0]);
p = rpc_add_credentials(p);
if (choosen_nfs_version != NFS_V3) {
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 { /* NFS_V3 */
*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);
}
}
/**************************************************************************
NFS_READ - Read File on NFS Server
**************************************************************************/
static void nfs_read_req(int offset, int readlen)
{
uint32_t data[1024];
uint32_t *p;
int len;
p = &(data[0]);
p = rpc_add_credentials(p);
if (choosen_nfs_version != NFS_V3) {
memcpy(p, filefh, NFS_FHSIZE);
p += (NFS_FHSIZE / 4);
*p++ = htonl(offset);
*p++ = htonl(readlen);
*p++ = 0;
} else { /* NFS_V3 */
*p++ = htonl(filefh3_length);
memcpy(p, filefh, 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]);
rpc_req(PROG_NFS, NFS_READ, data, len);
}
/**************************************************************************
RPC request dispatcher
**************************************************************************/
static void nfs_send(void)
{
debug("%s\n", __func__);
switch (nfs_state) {
case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
if (choosen_nfs_version != NFS_V3)
rpc_lookup_req(PROG_MOUNT, 1);
else /* NFS_V3 */
rpc_lookup_req(PROG_MOUNT, 3);
break;
case STATE_PRCLOOKUP_PROG_NFS_REQ:
if (choosen_nfs_version != NFS_V3)
rpc_lookup_req(PROG_NFS, 2);
else /* NFS_V3 */
rpc_lookup_req(PROG_NFS, 3);
break;
case STATE_MOUNT_REQ:
nfs_mount_req(nfs_path);
break;
case STATE_UMOUNT_REQ:
nfs_umountall_req();
break;
case STATE_LOOKUP_REQ:
nfs_lookup_req(nfs_filename);
break;
case STATE_READ_REQ:
nfs_read_req(nfs_offset, nfs_len);
break;
case STATE_READLINK_REQ:
nfs_readlink_req();
break;
}
}
/**************************************************************************
Handlers for the reply from server
**************************************************************************/
static int rpc_handle_error(struct rpc_t *rpc_pkt)
{
if (rpc_pkt->u.reply.rstatus ||
rpc_pkt->u.reply.verifier ||
rpc_pkt->u.reply.astatus ||
rpc_pkt->u.reply.data[0]) {
switch (ntohl(rpc_pkt->u.reply.astatus)) {
case NFS_RPC_SUCCESS: /* Not an error */
break;
case NFS_RPC_PROG_MISMATCH: {
/* Remote can't support NFS version */
const int min = ntohl(rpc_pkt->u.reply.data[0]);
const int max = ntohl(rpc_pkt->u.reply.data[1]);
if (max < NFS_V1 || max > NFS_V3 || min > NFS_V3) {
puts("*** ERROR: NFS version not supported");
debug(": Requested: V%d, accepted: min V%d - max V%d\n",
choosen_nfs_version,
ntohl(rpc_pkt->u.reply.data[0]),
ntohl(rpc_pkt->u.reply.data[1]));
puts("\n");
choosen_nfs_version = NFS_UNKOWN;
break;
}
debug("*** Warning: NFS version not supported: Requested: V%d, accepted: min V%d - max V%d\n",
choosen_nfs_version,
ntohl(rpc_pkt->u.reply.data[0]),
ntohl(rpc_pkt->u.reply.data[1]));
debug("Will retry with NFSv%d\n", min);
choosen_nfs_version = min;
return -NFS_RPC_PROG_MISMATCH;
}
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 */
debug("*** ERROR: accept state error (%d)\n",
ntohl(rpc_pkt->u.reply.astatus));
break;
}
return -1;
}
return 0;
}
static int rpc_lookup_reply(int prog, uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
memcpy(&rpc_pkt.u.data[0], pkt, len);
debug("%s\n", __func__);
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
if (rpc_pkt.u.reply.rstatus ||
rpc_pkt.u.reply.verifier ||
rpc_pkt.u.reply.astatus)
return -1;
switch (prog) {
case PROG_MOUNT:
nfs_server_mount_port = ntohl(rpc_pkt.u.reply.data[0]);
break;
case PROG_NFS:
nfs_server_port = ntohl(rpc_pkt.u.reply.data[0]);
break;
}
return 0;
}
static int nfs_mount_reply(uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
int ret;
debug("%s\n", __func__);
memcpy(&rpc_pkt.u.data[0], pkt, len);
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
ret = rpc_handle_error(&rpc_pkt);
if (ret)
return ret;
fs_mounted = 1;
/* NFSv2 and NFSv3 use same structure */
memcpy(dirfh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
return 0;
}
static int nfs_umountall_reply(uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
debug("%s\n", __func__);
memcpy(&rpc_pkt.u.data[0], pkt, len);
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
if (rpc_pkt.u.reply.rstatus ||
rpc_pkt.u.reply.verifier ||
rpc_pkt.u.reply.astatus)
return -1;
fs_mounted = 0;
memset(dirfh, 0, sizeof(dirfh));
return 0;
}
static int nfs_lookup_reply(uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
int ret;
debug("%s\n", __func__);
memcpy(&rpc_pkt.u.data[0], pkt, len);
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
ret = rpc_handle_error(&rpc_pkt);
if (ret)
return ret;
if (choosen_nfs_version != NFS_V3) {
if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + NFS_FHSIZE) > len)
return -NFS_RPC_DROP;
memcpy(filefh, rpc_pkt.u.reply.data + 1, NFS_FHSIZE);
} else { /* NFS_V3 */
filefh3_length = ntohl(rpc_pkt.u.reply.data[1]);
if (filefh3_length > NFS3_FHSIZE)
filefh3_length = NFS3_FHSIZE;
memcpy(filefh, rpc_pkt.u.reply.data + 2, filefh3_length);
}
return 0;
}
static int nfs3_get_attributes_offset(uint32_t *data)
{
if (data[1]) {
/* 'attributes_follow' flag is TRUE,
* so we have attributes on 21 dwords */
/* 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,
*/
return 22;
} else {
/* 'attributes_follow' flag is FALSE,
* so we don't have any attributes */
return 1;
}
}
static int nfs_readlink_reply(uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
int rlen;
int nfsv3_data_offset = 0;
debug("%s\n", __func__);
memcpy((unsigned char *)&rpc_pkt, pkt, len);
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
if (rpc_pkt.u.reply.rstatus ||
rpc_pkt.u.reply.verifier ||
rpc_pkt.u.reply.astatus ||
rpc_pkt.u.reply.data[0])
return -1;
if (choosen_nfs_version == NFS_V3) {
nfsv3_data_offset =
nfs3_get_attributes_offset(rpc_pkt.u.reply.data);
}
/* new path length */
rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len)
return -NFS_RPC_DROP;
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;
}
static int nfs_read_reply(uchar *pkt, unsigned len)
{
struct rpc_t rpc_pkt;
int rlen;
uchar *data_ptr;
debug("%s\n", __func__);
memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply));
if (ntohl(rpc_pkt.u.reply.id) > rpc_id)
return -NFS_RPC_ERR;
else if (ntohl(rpc_pkt.u.reply.id) < rpc_id)
return -NFS_RPC_DROP;
if (rpc_pkt.u.reply.rstatus ||
rpc_pkt.u.reply.verifier ||
rpc_pkt.u.reply.astatus ||
rpc_pkt.u.reply.data[0]) {
if (rpc_pkt.u.reply.rstatus)
return -9999;
if (rpc_pkt.u.reply.astatus)
return -9999;
return -ntohl(rpc_pkt.u.reply.data[0]);
}
if ((nfs_offset != 0) && !((nfs_offset) %
(NFS_READ_SIZE / 2 * 10 * HASHES_PER_LINE)))
puts("\n\t ");
if (!(nfs_offset % ((NFS_READ_SIZE / 2) * 10)))
putc('#');
if (choosen_nfs_version != NFS_V3) {
rlen = ntohl(rpc_pkt.u.reply.data[18]);
data_ptr = (uchar *)&(rpc_pkt.u.reply.data[19]);
} else { /* NFS_V3 */
int nfsv3_data_offset =
nfs3_get_attributes_offset(rpc_pkt.u.reply.data);
/* count value */
rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
/* Skip unused values :
EOF: 32 bits value,
data_size: 32 bits value,
*/
data_ptr = (uchar *)
&(rpc_pkt.u.reply.data[4 + nfsv3_data_offset]);
}
if (((uchar *)&(rpc_pkt.u.reply.data[0]) - (uchar *)(&rpc_pkt) + rlen) > len)
return -9999;
if (store_block(data_ptr, nfs_offset, rlen))
return -9999;
return rlen;
}
/**************************************************************************
Interfaces of U-BOOT
**************************************************************************/
static void nfs_timeout_handler(void)
{
if (++nfs_timeout_count > NFS_RETRY_COUNT) {
puts("\nRetry count exceeded; starting again\n");
net_start_again();
} else {
puts("T ");
net_set_timeout_handler(nfs_timeout +
nfs_timeout * nfs_timeout_count,
nfs_timeout_handler);
nfs_send();
}
}
static void nfs_handler(uchar *pkt, unsigned dest, struct in_addr sip,
unsigned src, unsigned len)
{
int rlen;
int reply;
debug("%s\n", __func__);
if (len > sizeof(struct rpc_t))
return;
if (dest != nfs_our_port)
return;
switch (nfs_state) {
case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
if (rpc_lookup_reply(PROG_MOUNT, pkt, len) == -NFS_RPC_DROP)
break;
nfs_state = STATE_PRCLOOKUP_PROG_NFS_REQ;
nfs_send();
break;
case STATE_PRCLOOKUP_PROG_NFS_REQ:
if (rpc_lookup_reply(PROG_NFS, pkt, len) == -NFS_RPC_DROP)
break;
nfs_state = STATE_MOUNT_REQ;
nfs_send();
break;
case STATE_MOUNT_REQ:
reply = nfs_mount_reply(pkt, len);
if (reply == -NFS_RPC_DROP) {
break;
} else if (reply == -NFS_RPC_ERR) {
puts("*** ERROR: Cannot mount\n");
/* just to be sure... */
nfs_state = STATE_UMOUNT_REQ;
nfs_send();
} else if (reply == -NFS_RPC_PROG_MISMATCH &&
choosen_nfs_version != NFS_UNKOWN) {
nfs_state = STATE_MOUNT_REQ;
nfs_send();
} else {
nfs_state = STATE_LOOKUP_REQ;
nfs_send();
}
break;
case STATE_UMOUNT_REQ:
reply = nfs_umountall_reply(pkt, len);
if (reply == -NFS_RPC_DROP) {
break;
} else if (reply == -NFS_RPC_ERR) {
debug("*** ERROR: Cannot umount\n");
net_set_state(NETLOOP_FAIL);
} else {
puts("\ndone\n");
net_set_state(nfs_download_state);
}
break;
case STATE_LOOKUP_REQ:
reply = nfs_lookup_reply(pkt, len);
if (reply == -NFS_RPC_DROP) {
break;
} else if (reply == -NFS_RPC_ERR) {
puts("*** ERROR: File lookup fail\n");
nfs_state = STATE_UMOUNT_REQ;
nfs_send();
} else if (reply == -NFS_RPC_PROG_MISMATCH &&
choosen_nfs_version != NFS_UNKOWN) {
/* 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;
nfs_len = NFS_READ_SIZE;
nfs_send();
}
break;
case STATE_READLINK_REQ:
reply = nfs_readlink_reply(pkt, len);
if (reply == -NFS_RPC_DROP) {
break;
} else if (reply == -NFS_RPC_ERR) {
puts("*** ERROR: Symlink fail\n");
nfs_state = STATE_UMOUNT_REQ;
nfs_send();
} else {
debug("Symlink --> %s\n", nfs_path);
nfs_filename = basename(nfs_path);
nfs_path = dirname(nfs_path);
nfs_state = STATE_MOUNT_REQ;
nfs_send();
}
break;
case STATE_READ_REQ:
rlen = nfs_read_reply(pkt, len);
if (rlen == -NFS_RPC_DROP)
break;
net_set_timeout_handler(nfs_timeout, nfs_timeout_handler);
if (rlen > 0) {
nfs_offset += rlen;
nfs_send();
} else if ((rlen == -NFSERR_ISDIR) || (rlen == -NFSERR_INVAL)) {
/* symbolic link */
nfs_state = STATE_READLINK_REQ;
nfs_send();
} else {
if (!rlen)
nfs_download_state = NETLOOP_SUCCESS;
if (rlen < 0)
debug("NFS READ error (%d)\n", rlen);
nfs_state = STATE_UMOUNT_REQ;
nfs_send();
}
break;
}
}
void nfs_start(void)
{
debug("%s\n", __func__);
nfs_download_state = NETLOOP_FAIL;
nfs_server_ip = net_server_ip;
nfs_path = (char *)nfs_path_buff;
if (nfs_path == NULL) {
net_set_state(NETLOOP_FAIL);
printf("*** ERROR: Fail allocate memory\n");
return;
}
if (!net_parse_bootfile(&nfs_server_ip, nfs_path,
sizeof(nfs_path_buff))) {
sprintf(nfs_path, "/nfsroot/%02X%02X%02X%02X.img",
net_ip.s_addr & 0xFF,
(net_ip.s_addr >> 8) & 0xFF,
(net_ip.s_addr >> 16) & 0xFF,
(net_ip.s_addr >> 24) & 0xFF);
printf("*** Warning: no boot file name; using '%s'\n",
nfs_path);
}
nfs_filename = basename(nfs_path);
nfs_path = dirname(nfs_path);
printf("Using %s device\n", eth_get_name());
printf("File transfer via NFS from server %pI4; our IP address is %pI4",
&nfs_server_ip, &net_ip);
/* Check if we need to send across this subnet */
if (net_gateway.s_addr && net_netmask.s_addr) {
struct in_addr our_net;
struct in_addr server_net;
our_net.s_addr = net_ip.s_addr & net_netmask.s_addr;
server_net.s_addr = nfs_server_ip.s_addr & net_netmask.s_addr;
if (our_net.s_addr != server_net.s_addr)
printf("; sending through gateway %pI4",
&net_gateway);
}
printf("\nFilename '%s/%s'.", nfs_path, nfs_filename);
if (net_boot_file_expected_size_in_blocks) {
printf(" Size is 0x%x Bytes = ",
net_boot_file_expected_size_in_blocks << 9);
print_size(net_boot_file_expected_size_in_blocks << 9, "");
}
printf("\nLoad address: 0x%lx\nLoading: *\b", image_load_addr);
net_set_timeout_handler(nfs_timeout, nfs_timeout_handler);
net_set_udp_handler(nfs_handler);
nfs_timeout_count = 0;
nfs_state = STATE_PRCLOOKUP_PROG_MOUNT_REQ;
/*nfs_our_port = 4096 + (get_ticks() % 3072);*/
/*FIX ME !!!*/
nfs_our_port = 1000;
/* zero out server ether in case the server ip has changed */
memset(net_server_ethaddr, 0, 6);
nfs_send();
}