| #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)); |
| |
| struct mount_reply { |
| struct rpc_reply reply; |
| uint32_t status; |
| struct nfs_fh_wire fh; |
| } __attribute__ ((packed)); |
| |
| #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; |
| } |