summaryrefslogtreecommitdiffstats
path: root/usr/kinit/nfsmount
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:06:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:06:04 +0000
commit2f0649f6fe411d7e07c8d56cf8ea56db53536da8 (patch)
tree778611fb52176dce1ad06c68e87b2cb348ca0f7b /usr/kinit/nfsmount
parentInitial commit. (diff)
downloadklibc-upstream.tar.xz
klibc-upstream.zip
Adding upstream version 2.0.13.upstream/2.0.13upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'usr/kinit/nfsmount')
-rw-r--r--usr/kinit/nfsmount/Kbuild31
-rw-r--r--usr/kinit/nfsmount/README.locking26
-rw-r--r--usr/kinit/nfsmount/dummypmap.c281
-rw-r--r--usr/kinit/nfsmount/dummypmap.h11
-rw-r--r--usr/kinit/nfsmount/dummypmap_test.c2
-rw-r--r--usr/kinit/nfsmount/main.c288
-rw-r--r--usr/kinit/nfsmount/mount.c347
-rw-r--r--usr/kinit/nfsmount/nfsmount.h34
-rw-r--r--usr/kinit/nfsmount/portmap.c73
-rw-r--r--usr/kinit/nfsmount/sunrpc.c252
-rw-r--r--usr/kinit/nfsmount/sunrpc.h110
11 files changed, 1455 insertions, 0 deletions
diff --git a/usr/kinit/nfsmount/Kbuild b/usr/kinit/nfsmount/Kbuild
new file mode 100644
index 0000000..5f34950
--- /dev/null
+++ b/usr/kinit/nfsmount/Kbuild
@@ -0,0 +1,31 @@
+#
+# kbuild file for nfsmount
+#
+
+static-y := static/nfsmount
+#FIXME - build is broken static-y := dummypmap
+shared-y := shared/nfsmount
+
+objs := main.o mount.o portmap.o dummypmap.o sunrpc.o
+
+# Create built-in.o with all .o files (used by kinit)
+lib-y := $(objs)
+
+# .o files used for executables
+static/nfsmount-y := $(objs)
+shared/nfsmount-y := $(objs)
+
+# dummypmap uses a single .o file (rename src file?)
+dummypmap-y := dummypmap_test.o
+
+# TODO - do we want a stripped version
+# TODO - do we want the static.g + shared.g directories?
+
+clean-dirs := static shared
+
+# Install binary
+ifdef KLIBCSHAREDFLAGS
+install-y := $(shared-y)
+else
+install-y := $(static-y)
+endif
diff --git a/usr/kinit/nfsmount/README.locking b/usr/kinit/nfsmount/README.locking
new file mode 100644
index 0000000..bf2e8e7
--- /dev/null
+++ b/usr/kinit/nfsmount/README.locking
@@ -0,0 +1,26 @@
+I have implemented portmap spoofing in klibc nfsmount (released as
+klibc-0.144) This is basically a vestigial portmap daemon which gets
+launched before the mount() call and then just records any
+transactions it gets to a file and sends back an affirmative reply.
+
+There are two ways to use it (this belongs in a README file, but it's
+too late at night right now):
+
+a) Set a fixed portnumber in /proc/sys/nfs/nlm_tcpport and
+/proc/sys/nfs/nlm_udpport before calling nfsmount; once the portmapper
+starts feed that fixed portnumber to pmap_set(8). In this case the
+pmap_file can be /dev/null.
+
+b) Allow the kernel to bind to any port and use the file produced by
+nfsroot to feed to pmap_set (it should be directly compatible); this
+means the file needs to be transferred to a place where the "real
+root" can find it before run-init.
+
+In either case, it is imperative that the real portmapper is launched
+before any program actually tries to create locks!
+
+To use it:
+
+ # We need the loopback device to be up before we do this!
+ ipconfig 127.0.0.1:::::lo:none
+ nfsroot -p pmap_file -o lock server:/pathname /realpath
diff --git a/usr/kinit/nfsmount/dummypmap.c b/usr/kinit/nfsmount/dummypmap.c
new file mode 100644
index 0000000..07210c5
--- /dev/null
+++ b/usr/kinit/nfsmount/dummypmap.c
@@ -0,0 +1,281 @@
+/*
+ * Enough portmapper functionality that mount doesn't hang trying
+ * to start lockd. Enables nfsroot with locking functionality.
+ *
+ * Note: the kernel will only speak to the local portmapper
+ * using RPC over UDP.
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "dummypmap.h"
+#include "sunrpc.h"
+
+extern const char *progname;
+
+struct portmap_args {
+ uint32_t program;
+ uint32_t version;
+ uint32_t proto;
+ uint32_t port;
+};
+
+struct portmap_call {
+ struct rpc_call rpc;
+ struct portmap_args args;
+};
+
+struct portmap_reply {
+ struct rpc_reply rpc;
+ uint32_t port;
+};
+
+static int bind_portmap(void)
+{
+ int sock = socket(PF_INET, SOCK_DGRAM, 0);
+ struct sockaddr_in sin;
+
+ if (sock < 0)
+ return -1;
+
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
+ sin.sin_port = htons(RPC_PMAP_PORT);
+ if (bind(sock, (struct sockaddr *)&sin, sizeof sin) < 0) {
+ int err = errno;
+ close(sock);
+ errno = err;
+ return -1;
+ }
+
+ return sock;
+}
+
+static const char *protoname(uint32_t proto)
+{
+ switch (ntohl(proto)) {
+ case IPPROTO_TCP:
+ return "tcp";
+ case IPPROTO_UDP:
+ return "udp";
+ default:
+ return NULL;
+ }
+}
+
+static void *get_auth(struct rpc_auth *auth)
+{
+ switch (ntohl(auth->flavor)) {
+ case AUTH_NULL:
+ /* Fallthrough */
+ case AUTH_UNIX:
+ return (char *)&auth->body + ntohl(auth->len);
+ default:
+ return NULL;
+ }
+}
+
+static int check_unix_cred(struct rpc_auth *cred)
+{
+ uint32_t len;
+ int quad_len;
+ uint32_t node_name_len;
+ int quad_name_len;
+ uint32_t *base;
+ uint32_t *pos;
+ int ret = -1;
+
+ len = ntohl(cred->len);
+ quad_len = (len + 3) >> 2;
+ if (quad_len < 6)
+ /* Malformed creds */
+ goto out;
+
+ base = pos = cred->body;
+
+ /* Skip timestamp */
+ pos++;
+
+ /* Skip node name: only localhost can succeed. */
+ node_name_len = ntohl(*pos++);
+ quad_name_len = (node_name_len + 3) >> 2;
+ if (pos + quad_name_len + 3 > base + quad_len)
+ /* Malformed creds */
+ goto out;
+ pos += quad_name_len;
+
+ /* uid must be 0 */
+ if (*pos++ != 0)
+ goto out;
+
+ /* gid must be 0 */
+ if (*pos++ != 0)
+ goto out;
+
+ /* Skip remaining gids */
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int check_cred(struct rpc_auth *cred)
+{
+ switch (ntohl(cred->flavor)) {
+ case AUTH_NULL:
+ return 0;
+ case AUTH_UNIX:
+ return check_unix_cred(cred);
+ default:
+ return -1;
+ }
+}
+
+static int check_vrf(struct rpc_auth *vrf)
+{
+ return (vrf->flavor == htonl(AUTH_NULL)) ? 0 : -1;
+}
+
+#define MAX_UDP_PACKET 65536
+
+static int dummy_portmap(int sock, FILE *portmap_file)
+{
+ enum { PAYLOAD_SIZE = MAX_UDP_PACKET + offsetof(struct rpc_header, udp) };
+ struct sockaddr_in sin;
+ int pktlen, addrlen;
+ union {
+ struct rpc_call rpc;
+ /* Max UDP packet size + unused TCP fragment size */
+ char payload[PAYLOAD_SIZE];
+ } pkt;
+ struct rpc_call *rpc = &pkt.rpc;
+ struct rpc_auth *cred;
+ struct rpc_auth *vrf;
+ struct portmap_args *args;
+ struct portmap_reply rply;
+
+ for (;;) {
+ addrlen = sizeof sin;
+ pktlen = recvfrom(sock, &rpc->hdr.udp, MAX_UDP_PACKET,
+ 0, (struct sockaddr *)&sin, &addrlen);
+
+ if (pktlen < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -1;
+ }
+
+ /* +4 to skip the TCP fragment header */
+ if (pktlen + 4 < sizeof(struct portmap_call))
+ continue; /* Bad packet */
+
+ if (rpc->hdr.udp.msg_type != htonl(RPC_CALL))
+ continue; /* Bad packet */
+
+ memset(&rply, 0, sizeof rply);
+
+ rply.rpc.hdr.udp.xid = rpc->hdr.udp.xid;
+ rply.rpc.hdr.udp.msg_type = htonl(RPC_REPLY);
+
+ cred = (struct rpc_auth *) &rpc->cred_flavor;
+ if (rpc->rpc_vers != htonl(2)) {
+ rply.rpc.reply_state = htonl(REPLY_DENIED);
+ /* state <- RPC_MISMATCH == 0 */
+ } else if (rpc->program != htonl(PORTMAP_PROGRAM)) {
+ rply.rpc.reply_state = htonl(PROG_UNAVAIL);
+ } else if (rpc->prog_vers != htonl(2)) {
+ rply.rpc.reply_state = htonl(PROG_MISMATCH);
+ } else if (!(vrf = get_auth(cred)) ||
+ (char *)vrf > ((char *)&rpc->hdr.udp + pktlen - 8 -
+ sizeof(*args)) ||
+ !(args = get_auth(vrf)) ||
+ (char *)args > ((char *)&rpc->hdr.udp + pktlen -
+ sizeof(*args)) ||
+ check_cred(cred) || check_vrf(vrf)) {
+ /* Can't deal with credentials data; the kernel
+ won't send them */
+ rply.rpc.reply_state = htonl(SYSTEM_ERR);
+ } else {
+ switch (ntohl(rpc->proc)) {
+ case PMAP_PROC_NULL:
+ break;
+ case PMAP_PROC_SET:
+ if (args->proto == htonl(IPPROTO_TCP) ||
+ args->proto == htonl(IPPROTO_UDP)) {
+ if (portmap_file)
+ fprintf(portmap_file,
+ "%u %u %s %u\n",
+ ntohl(args->program),
+ ntohl(args->version),
+ protoname(args->proto),
+ ntohl(args->port));
+ rply.port = htonl(1); /* TRUE = success */
+ }
+ break;
+ case PMAP_PROC_UNSET:
+ rply.port = htonl(1); /* TRUE = success */
+ break;
+ case PMAP_PROC_GETPORT:
+ break;
+ case PMAP_PROC_DUMP:
+ break;
+ default:
+ rply.rpc.reply_state = htonl(PROC_UNAVAIL);
+ break;
+ }
+ }
+
+ sendto(sock, &rply.rpc.hdr.udp, sizeof rply - 4, 0,
+ (struct sockaddr *)&sin, addrlen);
+ }
+}
+
+pid_t start_dummy_portmap(const char *file)
+{
+ FILE *portmap_filep;
+ int sock;
+ pid_t spoof_portmap;
+
+ portmap_filep = fopen(file, "w");
+ if (!portmap_filep) {
+ fprintf(stderr, "%s: cannot write portmap file: %s\n",
+ progname, file);
+ return -1;
+ }
+
+ sock = bind_portmap();
+ if (sock == -1) {
+ if (errno == EINVAL || errno == EADDRINUSE)
+ return 0; /* Assume not needed */
+ else {
+ fclose(portmap_filep);
+ fprintf(stderr, "%s: portmap spoofing failed\n",
+ progname);
+ return -1;
+ }
+ }
+
+ spoof_portmap = fork();
+ if (spoof_portmap == -1) {
+ fclose(portmap_filep);
+ fprintf(stderr, "%s: cannot fork\n", progname);
+ return -1;
+ } else if (spoof_portmap == 0) {
+ /* Child process */
+ dummy_portmap(sock, portmap_filep);
+ _exit(255); /* Error */
+ } else {
+ /* Parent process */
+ close(sock);
+ return spoof_portmap;
+ }
+}
diff --git a/usr/kinit/nfsmount/dummypmap.h b/usr/kinit/nfsmount/dummypmap.h
new file mode 100644
index 0000000..37650bf
--- /dev/null
+++ b/usr/kinit/nfsmount/dummypmap.h
@@ -0,0 +1,11 @@
+/*
+ * Functions for the portmap spoofer
+ */
+
+#ifndef NFSMOUNT_DUMMYPORTMAP_H
+#define NFSMOUNT_DUMMYPORTMAP_H
+
+#include <unistd.h>
+pid_t start_dummy_portmap(const char *file);
+
+#endif /* NFSMOUNT_DUMMYPORTMAP_H */
diff --git a/usr/kinit/nfsmount/dummypmap_test.c b/usr/kinit/nfsmount/dummypmap_test.c
new file mode 100644
index 0000000..d81a141
--- /dev/null
+++ b/usr/kinit/nfsmount/dummypmap_test.c
@@ -0,0 +1,2 @@
+#define TEST
+#include "dummypmap.c"
diff --git a/usr/kinit/nfsmount/main.c b/usr/kinit/nfsmount/main.c
new file mode 100644
index 0000000..66969f4
--- /dev/null
+++ b/usr/kinit/nfsmount/main.c
@@ -0,0 +1,288 @@
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <klibc/sysconfig.h> /* For _KLIBC_NO_MMU */
+
+#include <linux/nfs_mount.h>
+
+#include "nfsmount.h"
+#include "sunrpc.h"
+#include "dummypmap.h"
+
+const char *progname;
+static jmp_buf abort_buf;
+
+static struct nfs_mount_data mount_data = {
+ .version = NFS_MOUNT_VERSION,
+ .flags = NFS_MOUNT_NONLM | NFS_MOUNT_VER3 | NFS_MOUNT_TCP,
+ .rsize = 0, /* Server's choice */
+ .wsize = 0, /* Server's choice */
+ .timeo = 0, /* Kernel client's default */
+ .retrans = 3,
+ .acregmin = 3,
+ .acregmax = 60,
+ .acdirmin = 30,
+ .acdirmax = 60,
+ .namlen = NAME_MAX,
+};
+
+int nfs_port;
+int nfs_version;
+
+static struct int_opts {
+ char *name;
+ int *val;
+} int_opts[] = {
+ {"port", &nfs_port},
+ {"nfsvers", &nfs_version},
+ {"vers", &nfs_version},
+ {"rsize", &mount_data.rsize},
+ {"wsize", &mount_data.wsize},
+ {"timeo", &mount_data.timeo},
+ {"retrans", &mount_data.retrans},
+ {"acregmin", &mount_data.acregmin},
+ {"acregmax", &mount_data.acregmax},
+ {"acdirmin", &mount_data.acdirmin},
+ {"acdirmax", &mount_data.acdirmax},
+ {NULL, NULL}
+};
+
+static struct bool_opts {
+ char *name;
+ int and_mask;
+ int or_mask;
+} 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},
+ {"acl", ~NFS_MOUNT_NOACL, 0},
+ {"noacl", ~NFS_MOUNT_NOACL, NFS_MOUNT_NOACL},
+ {"v2", ~NFS_MOUNT_VER3, 0},
+ {"v3", ~NFS_MOUNT_VER3, NFS_MOUNT_VER3},
+ {"udp", ~NFS_MOUNT_TCP, 0},
+ {"tcp", ~NFS_MOUNT_TCP, NFS_MOUNT_TCP},
+ {"broken_suid", ~NFS_MOUNT_BROKEN_SUID, NFS_MOUNT_BROKEN_SUID},
+ {"ro", ~NFS_MOUNT_KLIBC_RONLY, NFS_MOUNT_KLIBC_RONLY},
+ {"rw", ~NFS_MOUNT_KLIBC_RONLY, 0},
+ {NULL, 0, 0}
+};
+
+static int parse_int(const char *val, const char *ctx)
+{
+ char *end;
+ int ret;
+
+ ret = (int)strtoul(val, &end, 0);
+ if (*val == '\0' || *end != '\0') {
+ fprintf(stderr, "%s: invalid value for %s\n", val, ctx);
+ longjmp(abort_buf, 1);
+ }
+ return ret;
+}
+
+static void parse_opts(char *opts)
+{
+ char *cp, *val;
+
+ while ((cp = strsep(&opts, ",")) != NULL) {
+ if (*cp == '\0')
+ continue;
+ val = strchr(cp, '=');
+ if (val != NULL) {
+ struct int_opts *opts = int_opts;
+ *val++ = '\0';
+ while (opts->name && strcmp(opts->name, cp) != 0)
+ opts++;
+ if (opts->name)
+ *(opts->val) = parse_int(val, opts->name);
+ else {
+ fprintf(stderr, "%s: bad option '%s'\n",
+ progname, cp);
+ longjmp(abort_buf, 1);
+ }
+ } else {
+ struct bool_opts *opts = bool_opts;
+ while (opts->name && strcmp(opts->name, cp) != 0)
+ opts++;
+ if (opts->name) {
+ mount_data.flags &= opts->and_mask;
+ mount_data.flags |= opts->or_mask;
+ } else {
+ fprintf(stderr, "%s: bad option '%s'\n",
+ progname, cp);
+ longjmp(abort_buf, 1);
+ }
+ }
+ }
+ /* If new-style options "nfsvers=" or "vers=" are passed, override
+ old "v2" and "v3" options */
+ if (nfs_version != 0) {
+ switch (nfs_version) {
+ case 2:
+ mount_data.flags &= ~NFS_MOUNT_VER3;
+ break;
+ case 3:
+ mount_data.flags |= NFS_MOUNT_VER3;
+ break;
+ default:
+ fprintf(stderr, "%s: bad NFS version '%d'\n",
+ progname, nfs_version);
+ longjmp(abort_buf, 1);
+ }
+ }
+}
+
+static uint32_t parse_addr(const char *ip)
+{
+ struct in_addr in;
+ if (inet_aton(ip, &in) == 0) {
+ fprintf(stderr, "%s: can't parse IP address '%s'\n",
+ progname, ip);
+ longjmp(abort_buf, 1);
+ }
+ return in.s_addr;
+}
+
+static void check_path(const char *path)
+{
+ struct stat st;
+
+ if (stat(path, &st) == -1) {
+ perror("stat");
+ longjmp(abort_buf, 1);
+ } else if (!S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "%s: '%s' not a directory\n", progname, path);
+ longjmp(abort_buf, 1);
+ }
+}
+
+int main(int argc, char *argv[])
+ __attribute__ ((weak, alias("nfsmount_main")));
+
+int nfsmount_main(int argc, char *argv[])
+{
+ uint32_t server;
+ char *rem_name;
+ char *rem_path;
+ char *hostname;
+ char *path;
+ int c;
+ const char *portmap_file;
+ pid_t spoof_portmap;
+ int err, ret;
+
+ if ((err = setjmp(abort_buf)))
+ return err;
+
+ /* Set these here to avoid longjmp warning */
+ portmap_file = NULL;
+ spoof_portmap = 0;
+ server = 0;
+
+ /* If progname is set we're invoked from another program */
+ if (!progname) {
+ struct timeval now;
+ progname = argv[0];
+ gettimeofday(&now, NULL);
+ srand48(now.tv_usec ^ (now.tv_sec << 24));
+ }
+
+ while ((c = getopt(argc, argv, "o:p:")) != EOF) {
+ switch (c) {
+ case 'o':
+ parse_opts(optarg);
+ break;
+ case 'p':
+ portmap_file = optarg;
+ break;
+ case '?':
+ fprintf(stderr, "%s: invalid option -%c\n",
+ progname, optopt);
+ return 1;
+ }
+ }
+
+ if (optind == argc) {
+ fprintf(stderr, "%s: need a path\n", progname);
+ return 1;
+ }
+
+ hostname = rem_path = argv[optind];
+
+ rem_name = strdup(rem_path);
+ if (rem_name == NULL) {
+ perror("strdup");
+ return 1;
+ }
+
+ rem_path = strchr(rem_path, ':');
+ if (rem_path == NULL) {
+ fprintf(stderr, "%s: need a server\n", progname);
+ free(rem_name);
+ return 1;
+ }
+
+ *rem_path++ = '\0';
+
+ if (*rem_path != '/') {
+ fprintf(stderr, "%s: need a path\n", progname);
+ free(rem_name);
+ return 1;
+ }
+
+ server = parse_addr(hostname);
+
+ if (optind <= argc - 2)
+ path = argv[optind + 1];
+ else
+ path = "/nfs_root";
+
+ check_path(path);
+
+#if !_KLIBC_NO_MMU
+ /* Note: uClinux can't fork(), so the spoof portmapper is not
+ available on uClinux. */
+ if (portmap_file)
+ spoof_portmap = start_dummy_portmap(portmap_file);
+
+ if (spoof_portmap == -1) {
+ free(rem_name);
+ return 1;
+ }
+#endif
+
+ ret = 0;
+ if (nfs_mount(rem_name, hostname, server, rem_path, path,
+ &mount_data) != 0)
+ ret = 1;
+
+ /* If we set up the spoofer, tear it down now */
+ if (spoof_portmap) {
+ kill(spoof_portmap, SIGTERM);
+ while (waitpid(spoof_portmap, NULL, 0) == -1
+ && errno == EINTR)
+ ;
+ }
+
+ free(rem_name);
+
+ return ret;
+}
diff --git a/usr/kinit/nfsmount/mount.c b/usr/kinit/nfsmount/mount.c
new file mode 100644
index 0000000..ae48354
--- /dev/null
+++ b/usr/kinit/nfsmount/mount.c
@@ -0,0 +1,347 @@
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <linux/nfs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "nfsmount.h"
+#include "sunrpc.h"
+
+static uint32_t mount_port;
+
+struct mount_call {
+ struct rpc_call rpc;
+ uint32_t path_len;
+ char path[0];
+};
+
+/*
+ * The following structure is the NFS v3 on-the-wire file handle,
+ * as defined in rfc1813.
+ * This differs from the structure used by the kernel,
+ * defined in <linux/nfh3.h>: rfc has a long in network order,
+ * kernel has a short in native order.
+ * Both kernel and rfc use the name nfs_fh; kernel name is
+ * visible to user apps in some versions of libc.
+ * Use different name to avoid clashes.
+ */
+#define NFS_MAXFHSIZE_WIRE 64
+struct nfs_fh_wire {
+ uint32_t size;
+ char data[NFS_MAXFHSIZE_WIRE];
+} __attribute__ ((packed, aligned(4)));
+
+struct mount_reply {
+ struct rpc_reply reply;
+ uint32_t status;
+ struct nfs_fh_wire fh;
+} __attribute__ ((packed, aligned(4)));
+
+#define MNT_REPLY_MINSIZE (sizeof(struct rpc_reply) + sizeof(uint32_t))
+
+static int get_ports(uint32_t server, const struct nfs_mount_data *data)
+{
+ uint32_t nfs_ver, mount_ver;
+ uint32_t proto;
+
+ if (data->flags & NFS_MOUNT_VER3) {
+ nfs_ver = NFS3_VERSION;
+ mount_ver = NFS_MNT3_VERSION;
+ } else {
+ nfs_ver = NFS2_VERSION;
+ mount_ver = NFS_MNT_VERSION;
+ }
+
+ proto = (data->flags & NFS_MOUNT_TCP) ? IPPROTO_TCP : IPPROTO_UDP;
+
+ if (nfs_port == 0) {
+ nfs_port = portmap(server, NFS_PROGRAM, nfs_ver, proto);
+ if (nfs_port == 0) {
+ if (proto == IPPROTO_TCP) {
+ struct in_addr addr = { server };
+ fprintf(stderr, "NFS over TCP not "
+ "available from %s\n", inet_ntoa(addr));
+ return -1;
+ }
+ nfs_port = NFS_PORT;
+ }
+ }
+
+ if (mount_port == 0) {
+ mount_port = portmap(server, NFS_MNT_PROGRAM, mount_ver, proto);
+ if (mount_port == 0)
+ mount_port = MOUNT_PORT;
+ }
+ return 0;
+}
+
+static inline int pad_len(int len)
+{
+ return (len + 3) & ~3;
+}
+
+static inline void dump_params(uint32_t server,
+ const char *path,
+ const struct nfs_mount_data *data)
+{
+ (void)server;
+ (void)path;
+ (void)data;
+
+#ifdef DEBUG
+ struct in_addr addr = { server };
+
+ printf("NFS params:\n");
+ printf(" server = %s, path = \"%s\", ", inet_ntoa(addr), path);
+ printf("version = %d, proto = %s\n",
+ data->flags & NFS_MOUNT_VER3 ? 3 : 2,
+ (data->flags & NFS_MOUNT_TCP) ? "tcp" : "udp");
+ printf(" mount_port = %d, nfs_port = %d, flags = %08x\n",
+ mount_port, nfs_port, data->flags);
+ printf(" rsize = %d, wsize = %d, timeo = %d, retrans = %d\n",
+ data->rsize, data->wsize, data->timeo, data->retrans);
+ printf(" acreg (min, max) = (%d, %d), acdir (min, max) = (%d, %d)\n",
+ data->acregmin, data->acregmax, data->acdirmin, data->acdirmax);
+ printf(" soft = %d, intr = %d, posix = %d, nocto = %d, noac = %d\n",
+ (data->flags & NFS_MOUNT_SOFT) != 0,
+ (data->flags & NFS_MOUNT_INTR) != 0,
+ (data->flags & NFS_MOUNT_POSIX) != 0,
+ (data->flags & NFS_MOUNT_NOCTO) != 0,
+ (data->flags & NFS_MOUNT_NOAC) != 0);
+#endif
+}
+
+static inline void dump_fh(const char *data, int len)
+{
+ (void)data;
+ (void)len;
+
+#ifdef DEBUG
+ int i = 0;
+ int max = len - (len % 8);
+
+ printf("Root file handle: %d bytes\n", NFS2_FHSIZE);
+
+ while (i < max) {
+ int j;
+
+ printf(" %4d: ", i);
+ for (j = 0; j < 4; j++) {
+ printf("%02x %02x %02x %02x ",
+ data[i] & 0xff, data[i + 1] & 0xff,
+ data[i + 2] & 0xff, data[i + 3] & 0xff);
+ }
+ i += j;
+ printf("\n");
+ }
+#endif
+}
+
+static struct mount_reply mnt_reply;
+
+static int mount_call(uint32_t proc, uint32_t version,
+ const char *path, struct client *clnt)
+{
+ struct mount_call *mnt_call = NULL;
+ size_t path_len, call_len;
+ struct rpc rpc;
+ int ret = 0;
+
+ path_len = strlen(path);
+ call_len = sizeof(*mnt_call) + pad_len(path_len);
+
+ mnt_call = malloc(call_len);
+ if (mnt_call == NULL) {
+ perror("malloc");
+ goto bail;
+ }
+
+ memset(mnt_call, 0, sizeof(*mnt_call));
+
+ mnt_call->rpc.program = htonl(NFS_MNT_PROGRAM);
+ mnt_call->rpc.prog_vers = htonl(version);
+ mnt_call->rpc.proc = htonl(proc);
+ mnt_call->path_len = htonl(path_len);
+ memcpy(mnt_call->path, path, path_len);
+
+ rpc.call = (struct rpc_call *)mnt_call;
+ rpc.call_len = call_len;
+ rpc.reply = (struct rpc_reply *)&mnt_reply;
+ rpc.reply_len = sizeof(mnt_reply);
+
+ if (rpc_call(clnt, &rpc) < 0)
+ goto bail;
+
+ if (proc != MNTPROC_MNT)
+ goto done;
+
+ if (rpc.reply_len < MNT_REPLY_MINSIZE) {
+ fprintf(stderr, "incomplete reply: %zu < %zu\n",
+ rpc.reply_len, MNT_REPLY_MINSIZE);
+ goto bail;
+ }
+
+ if (mnt_reply.status != 0) {
+ fprintf(stderr, "mount call failed - server replied: %s.\n",
+ strerror(ntohl(mnt_reply.status)));
+ goto bail;
+ }
+
+ goto done;
+
+bail:
+ ret = -1;
+
+done:
+ if (mnt_call)
+ free(mnt_call);
+
+ return ret;
+}
+
+static int mount_v2(const char *path,
+ struct nfs_mount_data *data, struct client *clnt)
+{
+ int ret = mount_call(MNTPROC_MNT, NFS_MNT_VERSION, path, clnt);
+
+ if (ret == 0) {
+ dump_fh((const char *)&mnt_reply.fh, NFS2_FHSIZE);
+
+ data->root.size = NFS_FHSIZE;
+ memcpy(data->root.data, &mnt_reply.fh, NFS_FHSIZE);
+ memcpy(data->old_root.data, &mnt_reply.fh, NFS_FHSIZE);
+ }
+
+ return ret;
+}
+
+static inline int umount_v2(const char *path, struct client *clnt)
+{
+ return mount_call(MNTPROC_UMNT, NFS_MNT_VERSION, path, clnt);
+}
+
+static int mount_v3(const char *path,
+ struct nfs_mount_data *data, struct client *clnt)
+{
+ int ret = mount_call(MNTPROC_MNT, NFS_MNT3_VERSION, path, clnt);
+
+ if (ret == 0) {
+ size_t fhsize = ntohl(mnt_reply.fh.size);
+
+ dump_fh((const char *)&mnt_reply.fh.data, fhsize);
+
+ memset(data->old_root.data, 0, NFS_FHSIZE);
+ memset(&data->root, 0, sizeof(data->root));
+ data->root.size = fhsize;
+ memcpy(&data->root.data, mnt_reply.fh.data, fhsize);
+ data->flags |= NFS_MOUNT_VER3;
+ }
+
+ return ret;
+}
+
+static inline int umount_v3(const char *path, struct client *clnt)
+{
+ return mount_call(MNTPROC_UMNT, NFS_MNT3_VERSION, path, clnt);
+}
+
+int nfs_mount(const char *pathname, const char *hostname,
+ uint32_t server, const char *rem_path, const char *path,
+ struct nfs_mount_data *data)
+{
+ struct client *clnt = NULL;
+ struct sockaddr_in addr;
+ char mounted = 0;
+ int sock = -1;
+ int ret = 0;
+ int mountflags;
+
+ if (get_ports(server, data) != 0)
+ goto bail;
+
+ dump_params(server, rem_path, data);
+
+ if (data->flags & NFS_MOUNT_TCP)
+ clnt = tcp_client(server, mount_port, CLI_RESVPORT);
+ else
+ clnt = udp_client(server, mount_port, CLI_RESVPORT);
+
+ if (clnt == NULL)
+ goto bail;
+
+ if (data->flags & NFS_MOUNT_VER3)
+ ret = mount_v3(rem_path, data, clnt);
+ else
+ ret = mount_v2(rem_path, data, clnt);
+
+ if (ret == -1)
+ goto bail;
+ mounted = 1;
+
+ if (data->flags & NFS_MOUNT_TCP)
+ sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ else
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (sock == -1) {
+ perror("socket");
+ goto bail;
+ }
+
+ if (bindresvport(sock, 0) == -1) {
+ perror("bindresvport");
+ goto bail;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = server;
+ addr.sin_port = htons(nfs_port);
+ memcpy(&data->addr, &addr, sizeof(data->addr));
+
+ strncpy(data->hostname, hostname, sizeof(data->hostname));
+
+ data->fd = sock;
+
+ mountflags = (data->flags & NFS_MOUNT_KLIBC_RONLY) ? MS_RDONLY : 0;
+ data->flags = data->flags & NFS_MOUNT_FLAGMASK;
+ ret = mount(pathname, path, "nfs", mountflags, data);
+
+ if (ret == -1) {
+ if (errno == ENODEV) {
+ fprintf(stderr, "mount: the kernel lacks NFS v%d "
+ "support\n",
+ (data->flags & NFS_MOUNT_VER3) ? 3 : 2);
+ } else {
+ perror("mount");
+ }
+ goto bail;
+ }
+
+ dprintf("Mounted %s on %s\n", pathname, path);
+
+ goto done;
+
+bail:
+ if (mounted) {
+ if (data->flags & NFS_MOUNT_VER3)
+ umount_v3(rem_path, clnt);
+ else
+ umount_v2(rem_path, clnt);
+ }
+
+ ret = -1;
+
+done:
+ if (clnt)
+ client_free(clnt);
+
+ if (sock != -1)
+ close(sock);
+
+ return ret;
+}
diff --git a/usr/kinit/nfsmount/nfsmount.h b/usr/kinit/nfsmount/nfsmount.h
new file mode 100644
index 0000000..7b28ded
--- /dev/null
+++ b/usr/kinit/nfsmount/nfsmount.h
@@ -0,0 +1,34 @@
+#ifndef NFSMOUNT_NFSMOUNT_H
+#define NFSMOUNT_NFSMOUNT_H
+
+#include <linux/nfs_mount.h>
+
+extern int nfs_port;
+
+extern int nfsmount_main(int argc, char *argv[]);
+int nfs_mount(const char *rem_name, const char *hostname,
+ uint32_t server, const char *rem_path,
+ const char *path, struct nfs_mount_data *data);
+
+enum nfs_proto {
+ v2 = 2,
+ v3,
+};
+
+/* masked with NFS_MOUNT_FLAGMASK before mount() call */
+#define NFS_MOUNT_KLIBC_RONLY 0x00010000U
+
+#ifdef DEBUG
+# define dprintf printf
+#else
+# define dprintf(...) ((void)0)
+#endif
+
+#ifndef MNTPROC_MNT
+#define MNTPROC_MNT 1
+#endif
+#ifndef MNTPROC_UMNT
+#define MNTPROC_UMNT 3
+#endif
+
+#endif /* NFSMOUNT_NFSMOUNT_H */
diff --git a/usr/kinit/nfsmount/portmap.c b/usr/kinit/nfsmount/portmap.c
new file mode 100644
index 0000000..0a3e2d0
--- /dev/null
+++ b/usr/kinit/nfsmount/portmap.c
@@ -0,0 +1,73 @@
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <asm/byteorder.h> /* __constant_hton* */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "nfsmount.h"
+#include "sunrpc.h"
+
+struct portmap_call {
+ struct rpc_call rpc;
+ uint32_t program;
+ uint32_t version;
+ uint32_t proto;
+ uint32_t port;
+};
+
+struct portmap_reply {
+ struct rpc_reply rpc;
+ uint32_t port;
+};
+
+static struct portmap_call call = {
+ .rpc = {
+ .program = __constant_htonl(RPC_PMAP_PROGRAM),
+ .prog_vers = __constant_htonl(RPC_PMAP_VERSION),
+ .proc = __constant_htonl(PMAP_PROC_GETPORT),
+ }
+};
+
+uint32_t portmap(uint32_t server, uint32_t program, uint32_t version, uint32_t proto)
+{
+ struct portmap_reply reply;
+ struct client *clnt;
+ struct rpc rpc;
+ uint32_t port = 0;
+
+ clnt = tcp_client(server, RPC_PMAP_PORT, 0);
+ if (clnt == NULL) {
+ clnt = udp_client(server, RPC_PMAP_PORT, 0);
+ if (clnt == NULL)
+ goto bail;
+ }
+
+ call.program = htonl(program);
+ call.version = htonl(version);
+ call.proto = htonl(proto);
+
+ rpc.call = (struct rpc_call *)&call;
+ rpc.call_len = sizeof(call);
+ rpc.reply = (struct rpc_reply *)&reply;
+ rpc.reply_len = sizeof(reply);
+
+ if (rpc_call(clnt, &rpc) < 0)
+ goto bail;
+
+ if (rpc.reply_len < sizeof(reply)) {
+ fprintf(stderr, "incomplete reply: %zu < %zu\n",
+ rpc.reply_len, sizeof(reply));
+ goto bail;
+ }
+
+ port = ntohl(reply.port);
+
+bail:
+ dprintf("Port for %d/%d[%s]: %d\n", program, version,
+ proto == IPPROTO_TCP ? "tcp" : "udp", port);
+
+ if (clnt)
+ client_free(clnt);
+
+ return port;
+}
diff --git a/usr/kinit/nfsmount/sunrpc.c b/usr/kinit/nfsmount/sunrpc.c
new file mode 100644
index 0000000..0a7fcf5
--- /dev/null
+++ b/usr/kinit/nfsmount/sunrpc.c
@@ -0,0 +1,252 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nfsmount.h"
+#include "sunrpc.h"
+
+/*
+ * The magic offset is needed here because RPC over TCP includes a
+ * field that RPC over UDP doesn't. Luvverly.
+ */
+static int rpc_do_reply(struct client *clnt, struct rpc *rpc, size_t off)
+{
+ int ret;
+
+ if ((ret = read(clnt->sock,
+ ((char *)rpc->reply) + off,
+ rpc->reply_len - off)) == -1) {
+ perror("read");
+ goto bail;
+ } else if (ret < sizeof(struct rpc_reply) - off) {
+ fprintf(stderr, "short read: %d < %zu\n", ret,
+ sizeof(struct rpc_reply) - off);
+ goto bail;
+ }
+ rpc->reply_len = ret + off;
+
+ if ((!off && !(ntohl(rpc->reply->hdr.frag_hdr) & LAST_FRAG)) ||
+ rpc->reply->hdr.udp.xid != rpc->call->hdr.udp.xid ||
+ rpc->reply->hdr.udp.msg_type != htonl(RPC_REPLY)) {
+ fprintf(stderr, "bad reply\n");
+ goto bail;
+ }
+
+ if (ntohl(rpc->reply->state) != REPLY_OK) {
+ fprintf(stderr, "rpc failed: %d\n", ntohl(rpc->reply->state));
+ goto bail;
+ }
+
+ ret = 0;
+ goto done;
+
+bail:
+ ret = -1;
+done:
+ return ret;
+}
+
+static void rpc_header(struct client *clnt, struct rpc *rpc)
+{
+ (void)clnt;
+
+ rpc->call->hdr.frag_hdr = htonl(LAST_FRAG | (rpc->call_len - 4));
+ rpc->call->hdr.udp.xid = lrand48();
+ rpc->call->hdr.udp.msg_type = htonl(RPC_CALL);
+ rpc->call->rpc_vers = htonl(2);
+}
+
+static int rpc_call_tcp(struct client *clnt, struct rpc *rpc)
+{
+ int ret;
+
+ rpc_header(clnt, rpc);
+
+ if ((ret = write(clnt->sock, rpc->call, rpc->call_len)) == -1) {
+ perror("write");
+ goto bail;
+ } else if (ret < rpc->call_len) {
+ fprintf(stderr, "short write: %d < %zu\n", ret, rpc->call_len);
+ goto bail;
+ }
+
+ ret = rpc_do_reply(clnt, rpc, 0);
+ goto done;
+
+ bail:
+ ret = -1;
+
+ done:
+ return ret;
+}
+
+static int rpc_call_udp(struct client *clnt, struct rpc *rpc)
+{
+#define NR_FDS 1
+#define TIMEOUT_MS 3000
+#define MAX_TRIES 100
+#define UDP_HDR_OFF (sizeof(struct rpc_header) - sizeof(struct rpc_udp_header))
+ struct pollfd fds[NR_FDS];
+ int ret = -1;
+ int i;
+
+ rpc_header(clnt, rpc);
+
+ fds[0].fd = clnt->sock;
+ fds[0].events = POLLRDNORM;
+
+ rpc->call_len -= UDP_HDR_OFF;
+
+ for (i = 0; i < MAX_TRIES; i++) {
+ int timeout_ms = TIMEOUT_MS + (lrand48() % (TIMEOUT_MS / 2));
+ if ((ret = write(clnt->sock,
+ ((char *)rpc->call) + UDP_HDR_OFF,
+ rpc->call_len)) == -1) {
+ perror("write");
+ goto bail;
+ } else if (ret < rpc->call_len) {
+ fprintf(stderr, "short write: %d < %zu\n", ret,
+ rpc->call_len);
+ goto bail;
+ }
+ for (; i < MAX_TRIES; i++) {
+ if ((ret = poll(fds, NR_FDS, timeout_ms)) == -1) {
+ perror("poll");
+ goto bail;
+ }
+ if (ret == 0) {
+ dprintf("Timeout #%d\n", i + 1);
+ break;
+ }
+ if ((ret = rpc_do_reply(clnt, rpc, UDP_HDR_OFF)) == 0) {
+ goto done;
+ } else {
+ dprintf("Failed on try #%d - retrying\n",
+ i + 1);
+ }
+ }
+ }
+
+ bail:
+ ret = -1;
+
+ done:
+ return ret;
+}
+
+struct client *tcp_client(uint32_t server, uint16_t port, uint32_t flags)
+{
+ struct client *clnt = malloc(sizeof(*clnt));
+ struct sockaddr_in addr;
+ int sock;
+
+ if (clnt == NULL) {
+ perror("malloc");
+ goto bail;
+ }
+
+ memset(clnt, 0, sizeof(*clnt));
+
+ if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ perror("socket");
+ goto bail;
+ }
+
+ if ((flags & CLI_RESVPORT) && bindresvport(sock, 0) == -1) {
+ perror("bindresvport");
+ goto bail;
+ }
+
+ clnt->sock = sock;
+ clnt->call_stub = rpc_call_tcp;
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = server;
+
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ perror("connect");
+ goto bail;
+ }
+
+ goto done;
+ bail:
+ if (clnt) {
+ free(clnt);
+ clnt = NULL;
+ }
+ done:
+ return clnt;
+}
+
+struct client *udp_client(uint32_t server, uint16_t port, uint32_t flags)
+{
+ struct client *clnt = malloc(sizeof(*clnt));
+ struct sockaddr_in addr;
+ int sock;
+
+ if (clnt == NULL) {
+ perror("malloc");
+ goto bail;
+ }
+
+ memset(clnt, 0, sizeof(*clnt));
+
+ if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+ perror("socket");
+ goto bail;
+ }
+
+ if ((flags & CLI_RESVPORT) && bindresvport(sock, 0) == -1) {
+ perror("bindresvport");
+ goto bail;
+ } else {
+ struct sockaddr_in me;
+
+ me.sin_family = AF_INET;
+ me.sin_port = 0;
+ me.sin_addr.s_addr = INADDR_ANY;
+
+ if (0 && bind(sock, (struct sockaddr *)&me, sizeof(me)) == -1) {
+ perror("bind");
+ goto bail;
+ }
+ }
+
+ clnt->sock = sock;
+ clnt->call_stub = rpc_call_udp;
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = server;
+
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ perror("connect");
+ goto bail;
+ }
+
+ goto done;
+ bail:
+ if (clnt) {
+ free(clnt);
+ clnt = NULL;
+ }
+ done:
+ return clnt;
+}
+
+void client_free(struct client *c)
+{
+ if (c->sock != -1)
+ close(c->sock);
+ free(c);
+}
+
+int rpc_call(struct client *client, struct rpc *rpc)
+{
+ return client->call_stub(client, rpc);
+}
diff --git a/usr/kinit/nfsmount/sunrpc.h b/usr/kinit/nfsmount/sunrpc.h
new file mode 100644
index 0000000..1bcfeea
--- /dev/null
+++ b/usr/kinit/nfsmount/sunrpc.h
@@ -0,0 +1,110 @@
+/*
+ * open-coded SunRPC structures
+ */
+#ifndef NFSMOUNT_SUNRPC_H
+#define NFSMOUNT_SUNRPC_H
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#define SUNRPC_PORT 111
+#define MOUNT_PORT 627
+
+#define RPC_CALL 0
+#define RPC_REPLY 1
+
+#define PORTMAP_PROGRAM 100000
+#define NLM_PROGRAM 100021
+
+#define RPC_PMAP_PROGRAM 100000
+#define RPC_PMAP_VERSION 2
+#define RPC_PMAP_PORT 111
+
+#define PMAP_PROC_NULL 0
+#define PMAP_PROC_SET 1
+#define PMAP_PROC_UNSET 2
+#define PMAP_PROC_GETPORT 3
+#define PMAP_PROC_DUMP 4
+
+#define LAST_FRAG 0x80000000
+
+#define REPLY_OK 0
+#define REPLY_DENIED 1
+
+#define SUCCESS 0
+#define PROG_UNAVAIL 1
+#define PROG_MISMATCH 2
+#define PROC_UNAVAIL 3
+#define GARBAGE_ARGS 4
+#define SYSTEM_ERR 5
+
+enum {
+ AUTH_NULL,
+ AUTH_UNIX,
+};
+
+struct rpc_auth {
+ uint32_t flavor;
+ uint32_t len;
+ uint32_t body[];
+};
+
+struct rpc_udp_header {
+ uint32_t xid;
+ uint32_t msg_type;
+};
+
+struct rpc_header {
+ uint32_t frag_hdr;
+ struct rpc_udp_header udp;
+};
+
+struct rpc_call {
+ struct rpc_header hdr;
+ uint32_t rpc_vers;
+
+ uint32_t program;
+ uint32_t prog_vers;
+ uint32_t proc;
+ uint32_t cred_flavor;
+
+ uint32_t cred_len;
+ uint32_t vrf_flavor;
+ uint32_t vrf_len;
+};
+
+struct rpc_reply {
+ struct rpc_header hdr;
+ uint32_t reply_state;
+ uint32_t vrf_flavor;
+ uint32_t vrf_len;
+ uint32_t state;
+};
+
+struct rpc {
+ struct rpc_call *call;
+ size_t call_len;
+ struct rpc_reply *reply;
+ size_t reply_len;
+};
+
+struct client;
+
+typedef int (*call_stub) (struct client *, struct rpc *);
+
+struct client {
+ int sock;
+ call_stub call_stub;
+};
+
+#define CLI_RESVPORT 00000001
+
+struct client *tcp_client(uint32_t server, uint16_t port, uint32_t flags);
+struct client *udp_client(uint32_t server, uint16_t port, uint32_t flags);
+void client_free(struct client *client);
+
+int rpc_call(struct client *client, struct rpc *rpc);
+
+uint32_t portmap(uint32_t server, uint32_t program, uint32_t version, uint32_t proto);
+
+#endif /* NFSMOUNT_SUNRPC_H */