blob: e0687a6fa8fe43a5aa0d58e51b721c0a6619fab6 [file] [log] [blame]
#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;
}