| #if 0 |
| |
| #include <gpxe/init.h> |
| #include <gpxe/in.h> |
| |
| /* 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. */ |
| |
| #define START_OPORT 700 /* mountd usually insists on secure ports */ |
| #define OPORT_SWEEP 200 /* make sure we don't leave secure range */ |
| |
| static int oport = START_OPORT; |
| static struct sockaddr_in mount_server; |
| static struct sockaddr_in nfs_server; |
| static unsigned long rpc_id; |
| |
| /************************************************************************** |
| RPC_INIT - set up the ID counter to something fairly random |
| **************************************************************************/ |
| void rpc_init(void) |
| { |
| unsigned long t; |
| |
| t = currticks(); |
| rpc_id = t ^ (t << 8) ^ (t << 16); |
| } |
| |
| /************************************************************************** |
| RPC_PRINTERROR - Print a low level RPC error message |
| **************************************************************************/ |
| static void rpc_printerror(struct rpc_t *rpc) |
| { |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus) { |
| /* rpc_printerror() is called for any RPC related error, |
| * suppress output if no low level RPC error happened. */ |
| DBG("RPC error: (%ld,%ld,%ld)\n", ntohl(rpc->u.reply.rstatus), |
| ntohl(rpc->u.reply.verifier), |
| ntohl(rpc->u.reply.astatus)); |
| } |
| } |
| |
| /************************************************************************** |
| AWAIT_RPC - Wait for an rpc packet |
| **************************************************************************/ |
| static int await_rpc(int ival, void *ptr, |
| unsigned short ptype __unused, struct iphdr *ip, |
| struct udphdr *udp, struct tcphdr *tcp __unused) |
| { |
| struct rpc_t *rpc; |
| if (!udp) |
| return 0; |
| if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) |
| return 0; |
| if (ntohs(udp->dest) != ival) |
| return 0; |
| if (nic.packetlen < ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr) + 8) |
| return 0; |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (*(unsigned long *)ptr != ntohl(rpc->u.reply.id)) |
| return 0; |
| if (MSG_REPLY != ntohl(rpc->u.reply.type)) |
| return 0; |
| return 1; |
| } |
| |
| /************************************************************************** |
| RPC_LOOKUP - Lookup RPC Port numbers |
| **************************************************************************/ |
| static int rpc_lookup(struct sockaddr_in *addr, int prog, int ver, int sport) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| int retries; |
| long *p; |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_PORTMAP); |
| buf.u.call.vers = htonl(2); /* portmapper is version 2 */ |
| buf.u.call.proc = htonl(PORTMAP_GETPORT); |
| p = (long *)buf.u.call.data; |
| *p++ = 0; *p++ = 0; /* auth credential */ |
| *p++ = 0; *p++ = 0; /* auth verifier */ |
| *p++ = htonl(prog); |
| *p++ = htonl(ver); |
| *p++ = htonl(IP_UDP); |
| *p++ = 0; |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout; |
| udp_transmit(addr->sin_addr.s_addr, sport, addr->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| if (await_reply(await_rpc, sport, &id, timeout)) { |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus) { |
| rpc_printerror(rpc); |
| return 0; |
| } else { |
| return ntohl(rpc->u.reply.data[0]); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| /************************************************************************** |
| RPC_ADD_CREDENTIALS - Add RPC authentication/verifier entries |
| **************************************************************************/ |
| static long *rpc_add_credentials(long *p) |
| { |
| int hl; |
| |
| /* 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). */ |
| |
| hl = (hostnamelen + 3) & ~3; |
| |
| /* Provide an AUTH_UNIX credential. */ |
| *p++ = htonl(1); /* AUTH_UNIX */ |
| *p++ = htonl(hl+20); /* auth length */ |
| *p++ = htonl(0); /* stamp */ |
| *p++ = htonl(hostnamelen); /* hostname string */ |
| if (hostnamelen & 3) { |
| *(p + hostnamelen / 4) = 0; /* add zero padding */ |
| } |
| memcpy(p, hostname, hostnamelen); |
| p += hl / 4; |
| *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; |
| } |
| |
| /************************************************************************** |
| NFS_PRINTERROR - Print a NFS error message |
| **************************************************************************/ |
| static void nfs_printerror(int err) |
| { |
| switch (-err) { |
| case NFSERR_PERM: |
| printf("Not owner\n"); |
| break; |
| case NFSERR_NOENT: |
| printf("No such file or directory\n"); |
| break; |
| case NFSERR_ACCES: |
| printf("Permission denied\n"); |
| break; |
| case NFSERR_ISDIR: |
| printf("Directory given where filename expected\n"); |
| break; |
| case NFSERR_INVAL: |
| printf("Invalid filehandle\n"); |
| break; // INVAL is not defined in NFSv2, some NFS-servers |
| // seem to use it in answers to v2 nevertheless. |
| case 9998: |
| printf("low-level RPC failure (parameter decoding problem?)\n"); |
| break; |
| case 9999: |
| printf("low-level RPC failure (authentication problem?)\n"); |
| break; |
| default: |
| printf("Unknown NFS error %d\n", -err); |
| } |
| } |
| |
| /************************************************************************** |
| NFS_MOUNT - Mount an NFS Filesystem |
| **************************************************************************/ |
| static int nfs_mount(struct sockaddr_in *server, char *path, char *fh, int sport) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| int retries; |
| long *p; |
| int pathlen = strlen(path); |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_MOUNT); |
| buf.u.call.vers = htonl(1); /* mountd is version 1 */ |
| buf.u.call.proc = htonl(MOUNT_ADDENTRY); |
| p = rpc_add_credentials((long *)buf.u.call.data); |
| *p++ = htonl(pathlen); |
| if (pathlen & 3) { |
| *(p + pathlen / 4) = 0; /* add zero padding */ |
| } |
| memcpy(p, path, pathlen); |
| p += (pathlen + 3) / 4; |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout; |
| udp_transmit(server->sin_addr.s_addr, sport, server->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| if (await_reply(await_rpc, sport, &id, timeout)) { |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus || rpc->u.reply.data[0]) { |
| rpc_printerror(rpc); |
| if (rpc->u.reply.rstatus) { |
| /* RPC failed, no verifier, data[0] */ |
| return -9999; |
| } |
| if (rpc->u.reply.astatus) { |
| /* RPC couldn't decode parameters */ |
| return -9998; |
| } |
| return -ntohl(rpc->u.reply.data[0]); |
| } else { |
| memcpy(fh, rpc->u.reply.data + 1, NFS_FHSIZE); |
| return 0; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /************************************************************************** |
| NFS_UMOUNTALL - Unmount all our NFS Filesystems on the Server |
| **************************************************************************/ |
| static void nfs_umountall(struct sockaddr_in *server) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| int retries; |
| long *p; |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_MOUNT); |
| buf.u.call.vers = htonl(1); /* mountd is version 1 */ |
| buf.u.call.proc = htonl(MOUNT_UMOUNTALL); |
| p = rpc_add_credentials((long *)buf.u.call.data); |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| udp_transmit(server->sin_addr.s_addr, oport, server->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| if (await_reply(await_rpc, oport, &id, timeout)) { |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus) { |
| rpc_printerror(rpc); |
| } |
| break; |
| } |
| } |
| } |
| |
| /************************************************************************** |
| NFS_RESET - Reset the NFS subsystem |
| **************************************************************************/ |
| static void nfs_reset ( void ) { |
| /* If we have a mount server, call nfs_umountall() */ |
| if ( mount_server.sin_addr.s_addr ) { |
| nfs_umountall ( &mount_server ); |
| } |
| /* Zero the data structures */ |
| memset ( &mount_server, 0, sizeof ( mount_server ) ); |
| memset ( &nfs_server, 0, sizeof ( nfs_server ) ); |
| } |
| |
| /*************************************************************************** |
| * 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 int nfs_readlink(struct sockaddr_in *server, char *fh __unused, |
| char *path, char *nfh, int sport) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| long *p; |
| int retries; |
| int pathlen = strlen(path); |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_NFS); |
| buf.u.call.vers = htonl(2); /* nfsd is version 2 */ |
| buf.u.call.proc = htonl(NFS_READLINK); |
| p = rpc_add_credentials((long *)buf.u.call.data); |
| memcpy(p, nfh, NFS_FHSIZE); |
| p += (NFS_FHSIZE / 4); |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| udp_transmit(server->sin_addr.s_addr, sport, server->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| if (await_reply(await_rpc, sport, &id, timeout)) { |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus || rpc->u.reply.data[0]) { |
| rpc_printerror(rpc); |
| if (rpc->u.reply.rstatus) { |
| /* RPC failed, no verifier, data[0] */ |
| return -9999; |
| } |
| if (rpc->u.reply.astatus) { |
| /* RPC couldn't decode parameters */ |
| return -9998; |
| } |
| return -ntohl(rpc->u.reply.data[0]); |
| } else { |
| // It *is* a link. |
| // If it's a relative link, append everything to dirname, filename TOO! |
| retries = strlen ( (char *)(&(rpc->u.reply.data[2]) )); |
| if ( *((char *)(&(rpc->u.reply.data[2]))) != '/' ) { |
| path[pathlen++] = '/'; |
| while ( ( retries + pathlen ) > 298 ) { |
| retries--; |
| } |
| if ( retries > 0 ) { |
| memcpy(path + pathlen, &(rpc->u.reply.data[2]), retries + 1); |
| } else { retries = 0; } |
| path[pathlen + retries] = 0; |
| } else { |
| // Else make it the only path. |
| if ( retries > 298 ) { retries = 298; } |
| memcpy ( path, &(rpc->u.reply.data[2]), retries + 1 ); |
| path[retries] = 0; |
| } |
| return 0; |
| } |
| } |
| } |
| return -1; |
| } |
| /************************************************************************** |
| NFS_LOOKUP - Lookup Pathname |
| **************************************************************************/ |
| static int nfs_lookup(struct sockaddr_in *server, char *fh, char *path, char *nfh, |
| int sport) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| long *p; |
| int retries; |
| int pathlen = strlen(path); |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_NFS); |
| buf.u.call.vers = htonl(2); /* nfsd is version 2 */ |
| buf.u.call.proc = htonl(NFS_LOOKUP); |
| p = rpc_add_credentials((long *)buf.u.call.data); |
| memcpy(p, fh, NFS_FHSIZE); |
| p += (NFS_FHSIZE / 4); |
| *p++ = htonl(pathlen); |
| if (pathlen & 3) { |
| *(p + pathlen / 4) = 0; /* add zero padding */ |
| } |
| memcpy(p, path, pathlen); |
| p += (pathlen + 3) / 4; |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| udp_transmit(server->sin_addr.s_addr, sport, server->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| if (await_reply(await_rpc, sport, &id, timeout)) { |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus || rpc->u.reply.data[0]) { |
| rpc_printerror(rpc); |
| if (rpc->u.reply.rstatus) { |
| /* RPC failed, no verifier, data[0] */ |
| return -9999; |
| } |
| if (rpc->u.reply.astatus) { |
| /* RPC couldn't decode parameters */ |
| return -9998; |
| } |
| return -ntohl(rpc->u.reply.data[0]); |
| } else { |
| memcpy(nfh, rpc->u.reply.data + 1, NFS_FHSIZE); |
| return 0; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /************************************************************************** |
| NFS_READ - Read File on NFS Server |
| **************************************************************************/ |
| static int nfs_read(struct sockaddr_in *server, char *fh, int offset, int len, |
| int sport) |
| { |
| struct rpc_t buf, *rpc; |
| unsigned long id; |
| int retries; |
| long *p; |
| |
| static int tokens=0; |
| /* |
| * Try to implement something similar to a window protocol in |
| * terms of response to losses. On successful receive, increment |
| * the number of tokens by 1 (cap at 256). On failure, halve it. |
| * When the number of tokens is >= 2, use a very short timeout. |
| */ |
| |
| id = rpc_id++; |
| buf.u.call.id = htonl(id); |
| buf.u.call.type = htonl(MSG_CALL); |
| buf.u.call.rpcvers = htonl(2); /* use RPC version 2 */ |
| buf.u.call.prog = htonl(PROG_NFS); |
| buf.u.call.vers = htonl(2); /* nfsd is version 2 */ |
| buf.u.call.proc = htonl(NFS_READ); |
| p = rpc_add_credentials((long *)buf.u.call.data); |
| memcpy(p, fh, NFS_FHSIZE); |
| p += NFS_FHSIZE / 4; |
| *p++ = htonl(offset); |
| *p++ = htonl(len); |
| *p++ = 0; /* unused parameter */ |
| for (retries = 0; retries < MAX_RPC_RETRIES; retries++) { |
| long timeout = rfc2131_sleep_interval(TIMEOUT, retries); |
| if (tokens >= 2) |
| timeout = TICKS_PER_SEC/2; |
| |
| udp_transmit(server->sin_addr.s_addr, sport, server->sin_port, |
| (char *)p - (char *)&buf, &buf); |
| if (await_reply(await_rpc, sport, &id, timeout)) { |
| if (tokens < 256) |
| tokens++; |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| if (rpc->u.reply.rstatus || rpc->u.reply.verifier || |
| rpc->u.reply.astatus || rpc->u.reply.data[0]) { |
| rpc_printerror(rpc); |
| if (rpc->u.reply.rstatus) { |
| /* RPC failed, no verifier, data[0] */ |
| return -9999; |
| } |
| if (rpc->u.reply.astatus) { |
| /* RPC couldn't decode parameters */ |
| return -9998; |
| } |
| return -ntohl(rpc->u.reply.data[0]); |
| } else { |
| return 0; |
| } |
| } else |
| tokens >>= 1; |
| } |
| return -1; |
| } |
| |
| /************************************************************************** |
| NFS - Download extended BOOTP data, or kernel image from NFS server |
| **************************************************************************/ |
| static int nfs ( char *url __unused, struct sockaddr_in *server, |
| char *name, struct buffer *buffer ) { |
| static int recursion = 0; |
| int sport; |
| int err, namelen = strlen(name); |
| char dirname[300], *fname; |
| char dirfh[NFS_FHSIZE]; /* file handle of directory */ |
| char filefh[NFS_FHSIZE]; /* file handle of kernel image */ |
| int rlen, size, offs, len; |
| struct rpc_t *rpc; |
| |
| sport = oport++; |
| if (oport > START_OPORT+OPORT_SWEEP) { |
| oport = START_OPORT; |
| } |
| |
| mount_server.sin_addr = nfs_server.sin_addr = server->sin_addr; |
| mount_server.sin_port = rpc_lookup(server, PROG_MOUNT, 1, sport); |
| if ( ! mount_server.sin_port ) { |
| DBG ( "Cannot get mount port from %s:%d\n", |
| inet_ntoa ( server->sin_addr ), server->sin_port ); |
| return 0; |
| } |
| nfs_server.sin_port = rpc_lookup(server, PROG_NFS, 2, sport); |
| if ( ! mount_server.sin_port ) { |
| DBG ( "Cannot get nfs port from %s:%d\n", |
| inet_ntoa ( server->sin_addr ), server->sin_port ); |
| return 0; |
| } |
| |
| if ( name != dirname ) { |
| memcpy(dirname, name, namelen + 1); |
| } |
| recursion = 0; |
| nfssymlink: |
| if ( recursion > NFS_MAXLINKDEPTH ) { |
| DBG ( "\nRecursion: More than %d symlinks followed. Abort.\n", |
| NFS_MAXLINKDEPTH ); |
| return 0; |
| } |
| recursion++; |
| fname = dirname + (namelen - 1); |
| while (fname >= dirname) { |
| if (*fname == '/') { |
| *fname = '\0'; |
| fname++; |
| break; |
| } |
| fname--; |
| } |
| if (fname < dirname) { |
| DBG("can't parse file name %s\n", name); |
| return 0; |
| } |
| |
| err = nfs_mount(&mount_server, dirname, dirfh, sport); |
| if (err) { |
| DBG("mounting %s: ", dirname); |
| nfs_printerror(err); |
| /* just to be sure... */ |
| nfs_reset(); |
| return 0; |
| } |
| |
| err = nfs_lookup(&nfs_server, dirfh, fname, filefh, sport); |
| if (err) { |
| DBG("looking up %s: ", fname); |
| nfs_printerror(err); |
| nfs_reset(); |
| return 0; |
| } |
| |
| offs = 0; |
| size = -1; /* will be set properly with the first reply */ |
| len = NFS_READ_SIZE; /* first request is always full size */ |
| do { |
| err = nfs_read(&nfs_server, filefh, offs, len, sport); |
| if ((err <= -NFSERR_ISDIR)&&(err >= -NFSERR_INVAL) && (offs == 0)) { |
| // An error occured. NFS servers tend to sending |
| // errors 21 / 22 when symlink instead of real file |
| // is requested. So check if it's a symlink! |
| if ( nfs_readlink(&nfs_server, dirfh, dirname, |
| filefh, sport) == 0 ) { |
| printf("\nLoading symlink:%s ..",dirname); |
| goto nfssymlink; |
| } |
| nfs_printerror(err); |
| nfs_reset(); |
| return 0; |
| } |
| if (err) { |
| printf("\nError reading at offset %d: ", offs); |
| nfs_printerror(err); |
| nfs_reset(); |
| return 0; |
| } |
| |
| rpc = (struct rpc_t *)&nic.packet[ETH_HLEN]; |
| |
| /* size must be found out early to allow EOF detection */ |
| if (size == -1) { |
| size = ntohl(rpc->u.reply.data[6]); |
| } |
| rlen = ntohl(rpc->u.reply.data[18]); |
| if (rlen > len) { |
| rlen = len; /* shouldn't happen... */ |
| } |
| |
| if ( ! fill_buffer ( buffer, &rpc->u.reply.data[19], |
| offs, rlen ) ) { |
| nfs_reset(); |
| return 0; |
| } |
| |
| offs += rlen; |
| /* last request is done with matching requested read size */ |
| if (size-offs < NFS_READ_SIZE) { |
| len = size-offs; |
| } |
| } while (len != 0); |
| /* len == 0 means that all the file has been read */ |
| return 1; |
| } |
| |
| struct protocol nfs_protocol __protocol = { |
| .name = "nfs", |
| .default_port = SUNRPC_PORT, |
| .load = nfs, |
| }; |
| |
| #endif |