#include #include #include #include #include #include #include #include #include #include #include "nfsmount.h" #include "sunrpc.h" static __u32 mount_port; struct mount_call { struct rpc_call rpc; __u32 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 : 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 { __u32 size; char data[NFS_MAXFHSIZE_WIRE]; } __attribute__((packed)); struct mount_reply { struct rpc_reply reply; __u32 status; struct nfs_fh_wire fh; } __attribute__((packed)); #define MNT_REPLY_MINSIZE (sizeof(struct rpc_reply) + sizeof(__u32)) static int get_ports(__u32 server, const struct nfs_mount_data *data) { __u32 nfs_ver, mount_ver; __u32 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; } #ifdef NFS_DEBUG void hex_dump (const char *msg, const char *buf, int len) { int i, done = 0; printf ("%s: %p, length %d\n", msg, buf, len); while (done < len) { printf ("%04x: ", done); for (i = done; i < done + 16; i++) { if (i < len) printf ("%02x ", buf[i] & 0xff); else printf (" "); if (i % 4 == 3) printf (" "); } for (i = done; i < done + 16; i++) { if (i < len) printf ("%c", isprint(buf[i]) ? buf[i] : '.'); if (i % 4 == 3) printf (" "); } printf ("\n"); done += 16; } } #endif NFS_DEBUG static inline void dump_params(__u32 server, const char *path, const struct nfs_mount_data *data) { #ifdef NFS_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) { #ifdef NFS_DEBUG hex_dump ("Root file handle", data, len); #endif } static struct mount_reply mnt_reply; static int mount_call(__u32 proc, __u32 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); if ((mnt_call = malloc(call_len)) == 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: %d\n", 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, __u32 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, 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) { perror("mount"); goto bail; } DEBUG(("Mounted %s on %s\n", pathname, path)); goto done; bail: if (mounted) { if (data->flags & NFS_MOUNT_VER3) { umount_v3(path, clnt); } else { umount_v2(path, clnt); } } ret = -1; done: if (clnt) { client_free(clnt); } if (sock != -1) { close(sock); } return ret; }