summaryrefslogtreecommitdiff
path: root/klibc/klibc/tests/nfs_no_rpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'klibc/klibc/tests/nfs_no_rpc.c')
-rw-r--r--klibc/klibc/tests/nfs_no_rpc.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/klibc/klibc/tests/nfs_no_rpc.c b/klibc/klibc/tests/nfs_no_rpc.c
new file mode 100644
index 0000000000..11b9f61fea
--- /dev/null
+++ b/klibc/klibc/tests/nfs_no_rpc.c
@@ -0,0 +1,538 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/mount.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Default path we try to mount. "%s" gets replaced by our IP address */
+#define NFS_ROOT "/tftpboot/%s"
+#define NFS_DEF_FILE_IO_BUFFER_SIZE 4096
+#define NFS_MAXPATHLEN 1024
+#define NFS_MNT_PROGRAM 100005
+#define NFS_MNT_PORT 627
+#define NFS_PROGRAM 100003
+#define NFS_PORT 2049
+#define NFS2_VERSION 2
+#define NFS3_VERSION 3
+#define NFS_MNT_PROGRAM 100005
+#define NFS_MNT_VERSION 1
+#define NFS_MNT3_VERSION 3
+#define MNTPROC_MNT 1
+#define MOUNTPROC3_MNT 1
+#define RPC_PMAP_PROGRAM 100000
+#define RPC_PMAP_VERSION 2
+#define RPC_PMAP_PORT 111
+
+#define NFS2_FHSIZE 32
+#define NFS3_FHSIZE 64
+
+#define RPC_VERSION 2
+
+enum rpc_msg_type {
+ RPC_CALL = 0,
+ RPC_REPLY = 1
+};
+
+enum rpc_auth_flavor {
+ RPC_AUTH_NULL = 0,
+ RPC_AUTH_UNIX = 1,
+ RPC_AUTH_SHORT = 2,
+ RPC_AUTH_DES = 3,
+ RPC_AUTH_KRB = 4,
+};
+
+enum rpc_reply_stat {
+ RPC_MSG_ACCEPTED = 0,
+ RPC_MSG_DENIED = 1
+};
+
+#define NFS_MAXFHSIZE 64
+struct nfs_fh {
+ unsigned short size;
+ unsigned char data[NFS_MAXFHSIZE];
+};
+
+struct nfs2_fh {
+ char data[NFS2_FHSIZE];
+};
+
+#define NFS_MOUNT_VERSION 4
+
+struct nfs_mount_data {
+ int version;
+ int fd;
+ struct nfs2_fh old_root;
+ int flags;
+ int rsize;
+ int wsize;
+ int timeo;
+ int retrans;
+ int acregmin;
+ int acregmax;
+ int acdirmin;
+ int acdirmax;
+ struct sockaddr_in addr;
+ char hostname[256];
+ int namlen;
+ unsigned int bsize;
+ struct nfs_fh root;
+};
+
+#define NFS_MOUNT_SOFT 0x0001 /* 1 */
+#define NFS_MOUNT_INTR 0x0002 /* 1 */
+#define NFS_MOUNT_SECURE 0x0004 /* 1 */
+#define NFS_MOUNT_POSIX 0x0008 /* 1 */
+#define NFS_MOUNT_NOCTO 0x0010 /* 1 */
+#define NFS_MOUNT_NOAC 0x0020 /* 1 */
+#define NFS_MOUNT_TCP 0x0040 /* 2 */
+#define NFS_MOUNT_VER3 0x0080 /* 3 */
+#define NFS_MOUNT_KERBEROS 0x0100 /* 3 */
+#define NFS_MOUNT_NONLM 0x0200 /* 3 */
+#define NFS_MOUNT_BROKEN_SUID 0x0400 /* 4 */
+#define NFS_MOUNT_FLAGMASK 0xFFFF
+
+static char nfs_root_name[256];
+static u_int32_t root_server_addr;
+static char root_server_path[256];
+
+/* Address of NFS server */
+static u_int32_t servaddr;
+
+/* Name of directory to mount */
+static char nfs_path[NFS_MAXPATHLEN];
+
+/* NFS-related data */
+static struct nfs_mount_data nfs_data = {
+ .version = NFS_MOUNT_VERSION,
+ .flags = NFS_MOUNT_NONLM, /* No lockd in nfs root yet */
+ .rsize = NFS_DEF_FILE_IO_BUFFER_SIZE,
+ .wsize = NFS_DEF_FILE_IO_BUFFER_SIZE,
+ .bsize = 0,
+ .timeo = 7,
+ .retrans = 3,
+ .acregmin = 3,
+ .acregmax = 60,
+ .acdirmin = 30,
+ .acdirmax = 60,
+};
+static int nfs_port = -1;
+static int mount_port;
+
+/***************************************************************************
+
+ Parsing of options
+
+ ***************************************************************************/
+
+/*
+ * The following integer options are recognized
+ */
+static struct nfs_int_opts {
+ const char *name;
+ int *val;
+} root_int_opts[] = {
+ { "port", &nfs_port },
+ { "rsize", &nfs_data.rsize },
+ { "wsize", &nfs_data.wsize },
+ { "timeo", &nfs_data.timeo },
+ { "retrans", &nfs_data.retrans },
+ { "acregmin", &nfs_data.acregmin },
+ { "acregmax", &nfs_data.acregmax },
+ { "acdirmin", &nfs_data.acdirmin },
+ { "acdirmax", &nfs_data.acdirmax },
+ { NULL, NULL }
+};
+
+/*
+ * And now the flag options
+ */
+static struct nfs_bool_opts {
+ const char *name;
+ int and_mask;
+ int or_mask;
+} root_bool_opts[] = {
+ { "soft", ~NFS_MOUNT_SOFT, NFS_MOUNT_SOFT },
+ { "hard", ~NFS_MOUNT_SOFT, 0 },
+ { "intr", ~NFS_MOUNT_INTR, NFS_MOUNT_INTR },
+ { "nointr", ~NFS_MOUNT_INTR, 0 },
+ { "posix", ~NFS_MOUNT_POSIX, NFS_MOUNT_POSIX },
+ { "noposix", ~NFS_MOUNT_POSIX, 0 },
+ { "cto", ~NFS_MOUNT_NOCTO, 0 },
+ { "nocto", ~NFS_MOUNT_NOCTO, NFS_MOUNT_NOCTO },
+ { "ac", ~NFS_MOUNT_NOAC, 0 },
+ { "noac", ~NFS_MOUNT_NOAC, NFS_MOUNT_NOAC },
+ { "lock", ~NFS_MOUNT_NONLM, 0 },
+ { "nolock", ~NFS_MOUNT_NONLM, NFS_MOUNT_NONLM },
+#ifdef CONFIG_NFS_V3
+ { "v2", ~NFS_MOUNT_VER3, 0 },
+ { "v3", ~NFS_MOUNT_VER3, NFS_MOUNT_VER3 },
+#endif
+ { "udp", ~NFS_MOUNT_TCP, 0 },
+ { "tcp", ~NFS_MOUNT_TCP, NFS_MOUNT_TCP },
+ { "broken_suid",~NFS_MOUNT_BROKEN_SUID, NFS_MOUNT_BROKEN_SUID },
+ { NULL, 0, 0 }
+};
+/*
+ * Parse option string.
+ */
+static void root_nfs_parse(char *name, char *buf)
+{
+ char *options, *val, *cp;
+
+ if ((options = strchr(name, ','))) {
+ *options++ = 0;
+ cp = strtok(options, ",");
+ while (cp) {
+ if ((val = strchr(cp, '='))) {
+ struct nfs_int_opts *opts = root_int_opts;
+ *val++ = '\0';
+ while (opts->name && strcmp(opts->name, cp))
+ opts++;
+ if (opts->name)
+ *(opts->val) = (int) strtoul(val, NULL, 10);
+ } else {
+ struct nfs_bool_opts *opts = root_bool_opts;
+ while (opts->name && strcmp(opts->name, cp))
+ opts++;
+ if (opts->name) {
+ nfs_data.flags &= opts->and_mask;
+ nfs_data.flags |= opts->or_mask;
+ }
+ }
+ cp = strtok(NULL, ",");
+ }
+ }
+ if (name[0] && strcmp(name, "default")) {
+ strncpy(buf, name, NFS_MAXPATHLEN-1);
+ buf[NFS_MAXPATHLEN-1] = 0;
+ }
+}
+
+/*
+ * Prepare the NFS data structure and parse all options.
+ */
+static int root_nfs_name(char *name)
+{
+ char buf[NFS_MAXPATHLEN];
+ struct utsname uname_buf;
+
+ /* Set some default values */
+ strcpy(buf, NFS_ROOT);
+
+ /* Process options received from the remote server */
+ root_nfs_parse(root_server_path, buf);
+
+ /* Override them by options set on kernel command-line */
+ root_nfs_parse(name, buf);
+
+ uname(&uname_buf);
+ if (strlen(buf) + strlen(uname_buf.nodename) > NFS_MAXPATHLEN) {
+ printf("nfsroot: Pathname for remote directory too long.\n");
+ return -1;
+ }
+ sprintf(nfs_path, buf, uname_buf.nodename);
+
+ return 1;
+}
+
+/***************************************************************************
+
+ Routines to actually mount the root directory
+
+ ***************************************************************************/
+
+/*
+ * Construct sockaddr_in from address and port number.
+ */
+static inline void
+set_sockaddr(struct sockaddr_in *sin, u_int32_t addr, u_int16_t port)
+{
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = addr;
+ sin->sin_port = port;
+}
+
+/*
+ * Extremely crude RPC-over-UDP call. We get an already encoded request
+ * to pass, we do that and put the reply into buffer. That (and callers
+ * below - getport, getfh2 and getfh3) should be replaced with proper
+ * librpc use. Now, if we only had one that wasn't bloated as a dead
+ * gnu that had lied for a while under the sun...
+ */
+
+static u_int32_t XID;
+static int flag;
+static void timeout(int n)
+{
+ (void)n;
+ flag = 1;
+}
+static int do_call(struct sockaddr_in *sin, u_int32_t msg[], u_int32_t rmsg[],
+ u_int32_t len, u_int32_t rlen)
+{
+ struct sockaddr_in from;
+ int slen = sizeof(struct sockaddr_in);
+ struct timeval tv = {1, 0};
+ int n;
+ int fd;
+
+ signal(SIGALRM, timeout);
+ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0)
+ goto Esocket;
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&tv, sizeof(tv));
+ len *= 4;
+ if (sendto(fd, msg, len, 0, (struct sockaddr *)sin, slen)!=(int)len)
+ goto Esend;
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&tv, sizeof(tv));
+ alarm(0);
+ flag = 0;
+ alarm(5);
+ rlen *= 4;
+ do {
+ slen = sizeof(from);
+ n = recvfrom(fd, rmsg, rlen, 0, (struct sockaddr*)&from, &slen);
+ if (flag || n < 0)
+ goto Erecv;
+ } while (memcmp(&from, sin, sizeof(from)) || rmsg[0] != msg[0]);
+
+ if (n < 6*4 || n % 4 || ntohl(rmsg[1]) != 1 || rmsg[2] ||
+ rmsg[3] || rmsg[4] || rmsg[5])
+ goto Einval;
+ alarm(0);
+ close(fd);
+ return n / 4 - 6;
+
+Esend: printf("rpc: write failed\n");
+ goto out;
+Erecv: printf("rpc: read failed\n");
+ goto out;
+Einval: printf("rpc: invalid response\n");
+ goto out;
+Esocket:printf("rpc: can't create socket\n");
+ return -1;
+out:
+ alarm(0);
+ close(fd);
+ return -1;
+}
+
+enum {
+ PMAP_GETPORT = 3
+};
+
+static void do_header(u_int32_t msg[], u_int32_t prog, u_int32_t vers, u_int32_t proc)
+{
+ msg[0] = XID++;
+ msg[1] = htonl(RPC_CALL);
+ msg[2] = htonl(RPC_VERSION);
+ msg[3] = htonl(prog);
+ msg[4] = htonl(vers);
+ msg[5] = htonl(proc);
+ msg[6] = htonl(RPC_AUTH_NULL);
+ msg[7] = htonl(0);
+ msg[8] = htonl(RPC_AUTH_NULL);
+ msg[9] = htonl(0);
+}
+
+static int getport(u_int32_t prog, u_int32_t vers, u_int32_t prot)
+{
+ struct sockaddr_in sin;
+ unsigned msg[14];
+ unsigned rmsg[7];
+ int n;
+ set_sockaddr(&sin, servaddr, htons(RPC_PMAP_PORT));
+ do_header(msg, RPC_PMAP_PROGRAM, RPC_PMAP_VERSION, PMAP_GETPORT);
+ msg[10] = htonl(prog);
+ msg[11] = htonl(vers);
+ msg[12] = htonl(prot);
+ msg[13] = htonl(0);
+ n = do_call(&sin, msg, rmsg, 14, 7);
+ if (n <= 0)
+ return -1;
+ else
+ return ntohl(rmsg[6]);
+}
+
+static int getfh2(void)
+{
+ struct sockaddr_in sin;
+ unsigned msg[10+1+256/4];
+ unsigned rmsg[6 + 1 + NFS2_FHSIZE/4];
+ int n;
+ int len = strlen(nfs_path);
+ set_sockaddr(&sin, servaddr, mount_port);
+
+ if (len > 255) {
+ printf("nfsroot: pathname is too long");
+ return -1;
+ }
+ memset(msg, 0, sizeof(msg));
+ do_header(msg, NFS_MNT_PROGRAM, NFS_MNT_VERSION, MNTPROC_MNT);
+ msg[10] = htonl(len);
+ strcpy((char*)&msg[11], nfs_path);
+ n = do_call(&sin, msg, rmsg, 11 + (len + 3)/4, 7 + NFS2_FHSIZE/4);
+ if (n < 0)
+ return -1;
+ if (n != NFS2_FHSIZE/4 + 1)
+ goto Esize;
+ if (rmsg[6]) {
+ printf("nfsroot: mountd returned an error (%d)",htonl(rmsg[6]));
+ return -1;
+ }
+ nfs_data.root.size = NFS2_FHSIZE;
+ memcpy(nfs_data.root.data, &rmsg[7], NFS2_FHSIZE);
+ return 0;
+Esize:
+ printf("nfsroot: bad fhandle size");
+ return -1;
+}
+
+static int getfh3(void)
+{
+ struct sockaddr_in sin;
+ unsigned msg[10+1+256/4];
+ unsigned rmsg[6 + 1 + 1 + NFS3_FHSIZE/4];
+ int n;
+ int len = strlen(nfs_path);
+ int size;
+ set_sockaddr(&sin, servaddr, mount_port);
+
+ if (len > 255) {
+ printf("nfsroot: pathname is too long");
+ return -1;
+ }
+ memset(msg, 0, sizeof(msg));
+ do_header(msg, NFS_MNT_PROGRAM, NFS_MNT3_VERSION, MOUNTPROC3_MNT);
+ msg[10] = htonl(len);
+ strcpy((char*)&msg[11], nfs_path);
+ n = do_call(&sin, msg, rmsg, 11 + (len + 3)/4, 8 + NFS3_FHSIZE/4);
+ if (n < 0)
+ return -1;
+ if (n <= 2)
+ goto Esize;
+ if (rmsg[6]) {
+ printf("nfsroot: mountd returned an error (%d)",htonl(rmsg[6]));
+ return -1;
+ }
+ size = ntohl(rmsg[7]);
+ if (size > NFS3_FHSIZE || n != 2 + size/4)
+ goto Esize;
+ nfs_data.root.size = size;
+ memcpy(nfs_data.root.data, &rmsg[8], size);
+ return 0;
+Esize:
+ printf("nfsroot: bad fhandle size");
+ return -1;
+}
+
+/*
+ * Use portmapper to find mountd and nfsd port numbers if not overriden
+ * by the user. Use defaults if portmapper is not available.
+ * XXX: Is there any nfs server with no portmapper?
+ */
+static int root_nfs_ports(void)
+{
+ int port;
+ int nfsd_ver, mountd_ver;
+ int proto;
+
+ if (nfs_data.flags & NFS_MOUNT_VER3) {
+ nfsd_ver = NFS3_VERSION;
+ mountd_ver = NFS_MNT3_VERSION;
+ } else {
+ nfsd_ver = NFS2_VERSION;
+ mountd_ver = NFS_MNT_VERSION;
+ }
+
+ proto = (nfs_data.flags & NFS_MOUNT_TCP) ? IPPROTO_TCP : IPPROTO_UDP;
+
+ if (nfs_port < 0) {
+ if ((port = getport(NFS_PROGRAM, nfsd_ver, proto)) < 0) {
+ printf("nfsroot: Unable to get nfsd port "
+ "number from server, using default\n");
+ port = NFS_PORT;
+ }
+ nfs_port = htons(port);
+ printf("nfsroot: Portmapper on server returned %d "
+ "as nfsd port\n", port);
+ }
+
+ if ((port = getport(NFS_MNT_PROGRAM, mountd_ver, proto)) < 0) {
+ printf("nfsroot: Unable to get mountd port "
+ "number from server, using default\n");
+ port = NFS_MNT_PORT;
+ }
+ mount_port = htons(port);
+ printf("nfsroot: mountd port is %d\n", port);
+
+ return 0;
+}
+
+int main(void)
+{
+ unsigned char *p;
+ struct timeval tv;
+ char *s;
+
+ /* FIX: use getopt() instead of this */
+
+ s = getenv("root_server_addr");
+ if (s)
+ root_server_addr = strtoul(s, NULL, 10);
+ s = getenv("root_server_path");
+ if (s)
+ strncpy(root_server_path, s, 255);
+ s = getenv("nfs_root_name");
+ if (s)
+ strncpy(nfs_root_name, s, 255);
+
+ /*
+ * Decode the root directory path name and NFS options from
+ * the kernel command line. This has to go here in order to
+ * be able to use the client IP address for the remote root
+ * directory (necessary for pure RARP booting).
+ */
+ if (root_nfs_name(nfs_root_name) < 0)
+ return 0;
+ if ((servaddr = root_server_addr) == INADDR_NONE) {
+ printf("nfsroot: No NFS server available, giving up.\n");
+ return 0;
+ }
+
+ p = (char *) &servaddr;
+ sprintf(nfs_data.hostname, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
+
+#ifdef NFSROOT_DEBUG
+ printf("nfsroot: Mounting %s on server %s as root\n",
+ nfs_path, nfs_data.hostname);
+ printf("nfsroot: rsize = %d, wsize = %d, timeo = %d, retrans = %d\n",
+ nfs_data.rsize, nfs_data.wsize, nfs_data.timeo, nfs_data.retrans);
+ printf("nfsroot: acreg (min,max) = (%d,%d), acdir (min,max) = (%d,%d)\n",
+ nfs_data.acregmin, nfs_data.acregmax,
+ nfs_data.acdirmin, nfs_data.acdirmax);
+ printf("nfsroot: nfsd port = %d, mountd port = %d, flags = %08x\n",
+ nfs_port, mount_port, nfs_data.flags);
+#endif
+
+ gettimeofday(&tv, NULL);
+ XID = (tv.tv_sec << 15) ^ tv.tv_usec;
+
+ if (root_nfs_ports() < 0)
+ return 0;
+ if (nfs_data.flags & NFS_MOUNT_VER3) {
+ if (getfh3())
+ return 0;
+ } else {
+ if (getfh2())
+ return 0;
+ }
+ set_sockaddr((struct sockaddr_in *) &nfs_data.addr, servaddr, nfs_port);
+ return mount("/dev/root", "/mnt", "nfs", 0, &nfs_data) == 0;
+}