/*
Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
*/
#include "rpcsvc.h"
#include <glusterfs/dict.h>
#include <glusterfs/xlator.h>
#include "mount3.h"
#include "xdr-nfs3.h"
#include "msg-nfs3.h"
#include <glusterfs/iobuf.h>
#include "nfs-common.h"
#include "nfs3-fh.h"
#include "nfs-fops.h"
#include "nfs-inodes.h"
#include "nfs-generics.h"
#include <glusterfs/locking.h>
#include <glusterfs/iatt.h>
#include "nfs-mem-types.h"
#include "nfs.h"
#include <glusterfs/common-utils.h>
#include <glusterfs/store.h>
#include "glfs-internal.h"
#include "glfs.h"
#include "mount3-auth.h"
#include <glusterfs/hashfn.h>
#include "nfs-messages.h"
#include <errno.h>
#include <sys/socket.h>
#include <sys/uio.h>
/* This macro will assist in freeing up entire link list
* of host_auth_spec structure.
*/
#define FREE_HOSTSPEC(exp) \
do { \
struct host_auth_spec *host = exp->hostspec; \
while (NULL != host) { \
struct host_auth_spec *temp = host; \
host = host->next; \
if (NULL != temp->host_addr) { \
GF_FREE(temp->host_addr); \
} \
GF_FREE(temp); \
} \
exp->hostspec = NULL; \
} while (0)
/* Paths for export and netgroup files */
const char *exports_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/exports";
const char *netgroups_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/netgroups";
typedef ssize_t (*mnt3_serializer)(struct iovec outmsg, void *args);
extern void *
mount3udp_thread(void *argv);
static void
mnt3_export_free(struct mnt3_export *exp)
{
if (!exp)
return;
if (exp->exptype == MNT3_EXPTYPE_DIR)
FREE_HOSTSPEC(exp);
GF_FREE(exp->expname);
GF_FREE(exp->fullpath);
GF_FREE(exp);
}
/* Generic reply function for MOUNTv3 specific replies. */
int
mnt3svc_submit_reply(rpcsvc_request_t *req, void *arg, mnt3_serializer sfunc)
{
struct iovec outmsg = {
0,
};
struct iobuf *iob = NULL;
struct mount3_state *ms = NULL;
int ret = -1;
ssize_t msglen = 0;
struct iobref *iobref = NULL;
if (!req)
return -1;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"mount state not found");
goto ret;
}
/* First, get the io buffer into which the reply in arg will
* be serialized.
*/
/* TODO: use 'xdrproc_t' instead of 'sfunc' to get the xdr-size */
iob = iobuf_get(ms->iobpool);
if (!iob) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to get iobuf");
goto ret;
}
iobuf_to_iovec(iob, &outmsg);
/* Use the given serializer to translate the give C structure in arg
* to XDR format which will be written into the buffer in outmsg.
*/
msglen = sfunc(outmsg, arg);
if (msglen < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ENCODE_MSG_FAIL,
"Failed to encode message");
goto ret;
}
outmsg.iov_len = msglen;
iobref = iobref_new();
if (iobref == NULL) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to get iobref");
goto ret;
}
ret = iobref_add(iobref, iob);
if (ret) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to add iob to iobref");
goto ret;
}
/* Then, submit the message for transmission. */
ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref);
if (ret == -1) {
gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_REP_SUBMIT_FAIL,
"Reply submission failed");
goto ret;
}
ret = 0;
ret:
if (NULL != iob)
iobuf_unref(iob);
if (NULL != iobref)
iobref_unref(iobref);
return ret;
}
/**
* __mountdict_insert -- Insert a mount entry into the mount state
*
* @ms: The mount state holding the entries
* @me: The mount entry to insert
*
* Not for external use.
*/
void
__mountdict_insert(struct mount3_state *ms, struct mountentry *me)
{
char *exname = NULL;
char *fpath = NULL;
data_t *medata = NULL;
GF_VALIDATE_OR_GOTO(GF_MNT, ms, out);
GF_VALIDATE_OR_GOTO(GF_MNT, me, out);
/* We don't want export names with leading slashes */
exname = me->exname;
while (exname[0] == '/')
exname++;
/* Get the fullpath for the export */
fpath = me->fullpath;
if (me->has_full_path) {
while (fpath[0] == '/')
fpath++;
/* Export names can either be just volumes or paths inside that
* volume. */
exname = fpath;
}
snprintf(me->hashkey, sizeof(me->hashkey), "%s:%s", exname, me->hostname);
medata = bin_to_data(me, sizeof(*me));
dict_set(ms->mountdict, me->hashkey, medata);
gf_msg_trace(GF_MNT, 0, "Inserted into mountdict: %s", me->hashkey);
out:
return;
}
/**
* __mountdict_remove -- Remove a mount entry from the mountstate.
*
* @ms: The mount state holding the entries
* @me: The mount entry to remove
*
* Not for external use.
*/
void
__mountdict_remove(struct mount3_state *ms, struct mountentry *me)
{
dict_del(ms->mountdict, me->hashkey);
}
/* Generic error reply function, just pass the err status
* and it will do the rest, including transmission.
*/
int
mnt3svc_mnt_error_reply(rpcsvc_request_t *req, int mntstat)
{
mountres3 res;
if (!req)
return -1;
res.fhs_status = mntstat;
mnt3svc_submit_reply(req, (void *)&res,
(mnt3_serializer)xdr_serialize_mountres3);
return 0;
}
mountstat3
mnt3svc_errno_to_mnterr(int32_t errnum)
{
mountstat3 stat;
switch (errnum) {
case 0:
stat = MNT3_OK;
break;
case ENOENT:
stat = MNT3ERR_NOENT;
break;
case EPERM:
stat = MNT3ERR_PERM;
break;
case EIO:
stat = MNT3ERR_IO;
break;
case EACCES:
stat = MNT3ERR_ACCES;
break;
case ENOTDIR:
stat = MNT3ERR_NOTDIR;
break;
case EINVAL:
stat = MNT3ERR_INVAL;
break;
case ENOSYS:
stat = MNT3ERR_NOTSUPP;
break;
case ENOMEM:
stat = MNT3ERR_SERVERFAULT;
break;
default:
stat = MNT3ERR_SERVERFAULT;
break;
}
return stat;
}
mountres3
mnt3svc_set_mountres3(mountstat3 stat, struct nfs3_fh *fh, int *authflavor,
u_int aflen)
{
mountres3 res = {
0,
};
uint32_t fhlen = 0;
res.fhs_status = stat;
if (fh)
fhlen = nfs3_fh_compute_size();
res.mountres3_u.mountinfo.fhandle.fhandle3_len = fhlen;
res.mountres3_u.mountinfo.fhandle.fhandle3_val = (char *)fh;
res.mountres3_u.mountinfo.auth_flavors.auth_flavors_val = authflavor;
res.mountres3_u.mountinfo.auth_flavors.auth_flavors_len = aflen;
return res;
}
/* Read the rmtab from the store_handle and append (or not) the entries to the
* mountlist.
*
* Requires the store_handle to be locked.
*/
static int
__mount_read_rmtab(gf_store_handle_t *sh, struct list_head *mountlist,
gf_boolean_t append)
{
int ret = 0;
unsigned int idx = 0;
struct mountentry *me = NULL, *tmp = NULL;
/* me->hostname is a char[MNTPATHLEN] */
char key[MNTPATHLEN + 11];
GF_ASSERT(sh && mountlist);
if (!gf_store_locked_local(sh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_READ_LOCKED,
"Not reading unlocked %s", sh->path);
return -1;
}
if (!append) {
list_for_each_entry_safe(me, tmp, mountlist, mlist)
{
list_del(&me->mlist);
GF_FREE(me);
}
me = NULL;
}
for (;;) {
char *value = NULL;
if (me && append) {
/* do not add duplicates */
list_for_each_entry(tmp, mountlist, mlist)
{
if (!strcmp(tmp->hostname, me->hostname) &&
!strcmp(tmp->exname, me->exname)) {
GF_FREE(me);
goto dont_add;
}
}
list_add_tail(&me->mlist, mountlist);
} else if (me) {
list_add_tail(&me->mlist, mountlist);
}
dont_add:
me = GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry);
if (!me) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Out of memory");
ret = -1;
goto out;
}
INIT_LIST_HEAD(&me->mlist);
snprintf(key, 9 + MNTPATHLEN, "hostname-%d", idx);
ret = gf_store_retrieve_value(sh, key, &value);
if (ret)
break;
snprintf(me->hostname, MNTPATHLEN, "%s", value);
GF_FREE(value);
snprintf(key, 11 + MNTPATHLEN, "mountpoint-%d", idx);
ret = gf_store_retrieve_value(sh, key, &value);
if (ret)
break;
snprintf(me->exname, MNTPATHLEN, "%s", value);
GF_FREE(value);
idx++;
gf_msg_trace(GF_MNT, 0, "Read entries %s:%s", me->hostname, me->exname);
}
gf_msg_debug(GF_MNT, 0, "Read %d entries from '%s'", idx, sh->path);
GF_FREE(me);
out:
return ret;
}
/* Overwrite the contents of the rwtab with the in-memory client list.
* Fail gracefully if the stora_handle is not locked.
*/
static void
__mount_rewrite_rmtab(struct mount3_state *ms, gf_store_handle_t *sh)
{
struct mountentry *me = NULL;
char key[16];
int fd, ret;
unsigned int idx = 0;
if (!gf_store_locked_local(sh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_MODIFY_LOCKED,
"Not modifying unlocked %s", sh->path);
return;
}
fd = gf_store_mkstemp(sh);
if (fd == -1) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
"Failed to open %s", sh->path);
return;
}
list_for_each_entry(me, &ms->mountlist, mlist)
{
snprintf(key, 16, "hostname-%d", idx);
ret = gf_store_save_value(fd, key, me->hostname);
if (ret)
goto fail;
snprintf(key, 16, "mountpoint-%d", idx);
ret = gf_store_save_value(fd, key, me->exname);
if (ret)
goto fail;
idx++;
}
gf_msg_debug(GF_MNT, 0, "Updated rmtab with %d entries", idx);
if (gf_store_rename_tmppath(sh))
gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_RWTAB_OVERWRITE_FAIL,
"Failed to overwrite rwtab %s", sh->path);
return;
fail:
gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_UPDATE_FAIL,
"Failed to update %s", sh->path);
gf_store_unlink_tmppath(sh);
}
static gf_boolean_t
mount_open_rmtab(const char *rmtab, gf_store_handle_t **sh)
{
int ret = -1;
/* updating the rmtab is disabled, use in-memory only */
if (!rmtab || rmtab[0] == '\0')
return _gf_false;
ret = gf_store_handle_new(rmtab, sh);
if (ret) {
gf_log(GF_MNT, GF_LOG_WARNING, "Failed to open '%s'", rmtab);
return _gf_false;
}
return _gf_true;
}
/* Read the rmtab into a clean ms->mountlist.
*/
static void
mount_read_rmtab(struct mount3_state *ms)
{
gf_store_handle_t *sh = NULL;
struct nfs_state *nfs = NULL;
gf_boolean_t read_rmtab = _gf_false;
nfs = (struct nfs_state *)ms->nfsx->private;
read_rmtab = mount_open_rmtab(nfs->rmtab, &sh);
if (!read_rmtab)
return;
if (gf_store_lock(sh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_LOCK_FAIL,
"Failed to lock '%s'", nfs->rmtab);
goto out;
}
__mount_read_rmtab(sh, &ms->mountlist, _gf_false);
gf_store_unlock(sh);
out:
gf_store_handle_destroy(sh);
}
/* Write the ms->mountlist to the rmtab.
*
* The rmtab could be empty, or it can exists and have been updated by a
* different storage server without our knowing.
*
* 0. if opening the nfs->rmtab fails, return gracefully
* 1. takes the store_handle lock on the current rmtab
* - blocks if an other storage server rewrites the rmtab at the same time
* 2. [if new_rmtab] takes the store_handle lock on the new rmtab
* 3. reads/merges the entries from the current rmtab
* 4. [if new_rmtab] reads/merges the entries from the new rmtab
* 5. [if new_rmtab] writes the new rmtab
* 6. [if not new_rmtab] writes the current rmtab
* 7 [if new_rmtab] replaces nfs->rmtab to point to the new location
* 8. [if new_rmtab] releases the store_handle lock of the new rmtab
* 9. releases the store_handle lock of the old rmtab
*/
void
mount_rewrite_rmtab(struct mount3_state *ms, char *new_rmtab)
{
gf_store_handle_t *sh = NULL, *nsh = NULL;
struct nfs_state *nfs = NULL;
int ret;
char *rmtab = NULL;
gf_boolean_t got_old_rmtab = _gf_false;
nfs = (struct nfs_state *)ms->nfsx->private;
got_old_rmtab = mount_open_rmtab(nfs->rmtab, &sh);
if (!got_old_rmtab && !new_rmtab)
return;
if (got_old_rmtab && gf_store_lock(sh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_REWRITE_ERROR,
"Not rewriting '%s'", nfs->rmtab);
goto free_sh;
}
if (new_rmtab) {
ret = gf_store_handle_new(new_rmtab, &nsh);
if (ret) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_OPEN_FAIL,
"Failed to open '%s'", new_rmtab);
goto unlock_sh;
}
if (gf_store_lock(nsh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_REWRITE_ERROR,
"Not rewriting '%s'", new_rmtab);
goto free_nsh;
}
}
/* always read the currently used rmtab */
if (got_old_rmtab)
__mount_read_rmtab(sh, &ms->mountlist, _gf_true);
if (new_rmtab) {
/* read the new rmtab and write changes to the new location */
__mount_read_rmtab(nsh, &ms->mountlist, _gf_true);
__mount_rewrite_rmtab(ms, nsh);
/* replace the nfs->rmtab reference to the new rmtab */
rmtab = gf_strdup(new_rmtab);
if (rmtab == NULL) {
gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_NO_MEMORY,
"Out of memory, keeping %s as rmtab", nfs->rmtab);
} else {
GF_FREE(nfs->rmtab);
nfs->rmtab = rmtab;
}
gf_store_unlock(nsh);
} else {
/* rewrite the current (unchanged location) rmtab */
__mount_rewrite_rmtab(ms, sh);
}
free_nsh:
if (new_rmtab)
gf_store_handle_destroy(nsh);
unlock_sh:
if (got_old_rmtab)
gf_store_unlock(sh);
free_sh:
if (got_old_rmtab)
gf_store_handle_destroy(sh);
}
/* Add a new NFS-client to the ms->mountlist and update the rmtab if we can.
*
* A NFS-client will only be removed from the ms->mountlist in case the
* NFS-client sends a unmount request. It is possible that a NFS-client
* crashed/rebooted had network loss or something else prevented the NFS-client
* to unmount cleanly. In this case, a duplicate entry would be added to the
* ms->mountlist, which is wrong and we should prevent.
*
* It is fully acceptable that the ms->mountlist is not 100% correct, this is a
* common issue for all(?) NFS-servers.
*/
int
mnt3svc_update_mountlist(struct mount3_state *ms, rpcsvc_request_t *req,
const char *expname, const char *fullpath)
{
struct mountentry *me = NULL;
struct mountentry *cur = NULL;
int ret = -1;
char *colon = NULL;
struct nfs_state *nfs = NULL;
gf_store_handle_t *sh = NULL;
gf_boolean_t update_rmtab = _gf_false;
if ((!ms) || (!req) || (!expname))
return -1;
me = (struct mountentry *)GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry);
if (!me)
return -1;
nfs = (struct nfs_state *)ms->nfsx->private;
update_rmtab = mount_open_rmtab(nfs->rmtab, &sh);
snprintf(me->exname, MNTPATHLEN, "%s", expname);
/* Sometimes we don't care about the full path
* so a NULL value for fullpath is valid.
*/
if (fullpath) {
if (strlen(fullpath) < MNTPATHLEN) {
strcpy(me->fullpath, fullpath);
me->has_full_path = _gf_true;
}
}
INIT_LIST_HEAD(&me->mlist);
/* Must get the IP or hostname of the client so we
* can map it into the mount entry.
*/
ret = rpcsvc_transport_peername(req->trans, me->hostname, MNTPATHLEN);
if (ret == -1)
goto free_err;
colon = strrchr(me->hostname, ':');
if (colon) {
*colon = '\0';
}
LOCK(&ms->mountlock);
{
/* in case locking fails, we just don't write the rmtab */
if (update_rmtab && gf_store_lock(sh)) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_LOCK_FAIL,
"Failed to lock '%s', changes will not be "
"written",
nfs->rmtab);
} else if (update_rmtab) {
__mount_read_rmtab(sh, &ms->mountlist, _gf_false);
}
/* do not add duplicates */
list_for_each_entry(cur, &ms->mountlist, mlist)
{
if (!strcmp(cur->hostname, me->hostname) &&
!strcmp(cur->exname, me->exname)) {
GF_FREE(me);
goto dont_add;
}
}
list_add_tail(&me->mlist, &ms->mountlist);
__mountdict_insert(ms, me);
/* only write the rmtab in case it was locked */
if (update_rmtab && gf_store_locked_local(sh))
__mount_rewrite_rmtab(ms, sh);
}
dont_add:
if (update_rmtab && gf_store_locked_local(sh))
gf_store_unlock(sh);
UNLOCK(&ms->mountlock);
free_err:
if (update_rmtab)
gf_store_handle_destroy(sh);
if (ret == -1)
GF_FREE(me);
return ret;
}
int
__mnt3_get_volume_id(struct mount3_state *ms, xlator_t *mntxl, uuid_t volumeid)
{
int ret = -1;
struct mnt3_export *exp = NULL;
if ((!ms) || (!mntxl))
return ret;
LOCK(&ms->mountlock);
list_for_each_entry(exp, &ms->exportlist, explist)
{
if (exp->vol == mntxl) {
gf_uuid_copy(volumeid, exp->volumeid);
ret = 0;
goto out;
}
}
out:
UNLOCK(&ms->mountlock);
return ret;
}
int
__mnt3_build_mountid_from_path(const char *path, uuid_t mountid)
{
uint32_t hashed_path = 0;
int ret = -1;
if (!path)
goto out;
while (strlen(path) > 0 && path[0] == '/')
path++;
/* Clear the mountid */
gf_uuid_clear(mountid);
hashed_path = SuperFastHash(path, strlen(path));
if (hashed_path == 1) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_HASH_PATH_FAIL,
"failed to hash path: %s", path);
goto out;
}
memcpy(mountid, &hashed_path, sizeof(hashed_path));
ret = 0;
out:
return ret;
}
int
__mnt3_get_mount_id(xlator_t *mntxl, uuid_t mountid)
{
int ret = -1;
uint32_t hashed_path = 0;
/* first clear the mountid */
gf_uuid_clear(mountid);
hashed_path = SuperFastHash(mntxl->name, strlen(mntxl->name));
if (hashed_path == 1) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_HASH_XLATOR_FAIL,
"failed to hash xlator name: %s", mntxl->name);
goto out;
}
memcpy(mountid, &hashed_path, sizeof(hashed_path));
ret = 0;
out:
return ret;
}
int32_t
mnt3svc_lookup_mount_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
int32_t op_ret, int32_t op_errno, inode_t *inode,
struct iatt *buf, dict_t *xattr,
struct iatt *postparent)
{
mountres3 res = {
0,
};
rpcsvc_request_t *req = NULL;
struct nfs3_fh fh = {
{0},
};
struct mount3_state *ms = NULL;
mountstat3 status = 0;
int autharr[10];
int autharrlen = 0;
rpcsvc_t *svc = NULL;
xlator_t *mntxl = NULL;
uuid_t volumeid = {
0,
};
char *path = NULL;
uuid_t mountid = {
1,
};
char fhstr[1536];
int alloclen = 0;
req = (rpcsvc_request_t *)frame->local;
if (!req)
return -1;
mntxl = (xlator_t *)cookie;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"mount state not found");
op_ret = -1;
op_errno = EINVAL;
}
if (op_ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, op_errno, NFS_MSG_LOOKUP_MNT_ERROR,
"error=%s", strerror(op_errno));
status = mnt3svc_errno_to_mnterr(op_errno);
}
if (status != MNT3_OK)
goto xmit_res;
alloclen = strlen(mntxl->name) + 2;
path = GF_MALLOC(alloclen, gf_nfs_mt_char);
if (!path) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed.");
goto xmit_res;
}
snprintf(path, alloclen, "/%s", mntxl->name);
mnt3svc_update_mountlist(ms, req, path, NULL);
GF_FREE(path);
if (gf_nfs_dvm_off(nfs_state(ms->nfsx))) {
fh = nfs3_fh_build_indexed_root_fh(ms->nfsx->children, mntxl);
goto xmit_res;
}
__mnt3_get_mount_id(mntxl, mountid);
__mnt3_get_volume_id(ms, mntxl, volumeid);
fh = nfs3_fh_build_uuid_root_fh(volumeid, mountid);
xmit_res:
nfs3_fh_to_str(&fh, fhstr, sizeof(fhstr));
gf_msg_debug(GF_MNT, 0, "MNT reply: fh %s, status: %d", fhstr, status);
if (op_ret == 0) {
svc = rpcsvc_request_service(req);
autharrlen = rpcsvc_auth_array(svc, mntxl->name, autharr, 10);
}
res = mnt3svc_set_mountres3(status, &fh, autharr, autharrlen);
mnt3svc_submit_reply(req, (void *)&res,
(mnt3_serializer)xdr_serialize_mountres3);
return 0;
}
int
mnt3_match_dirpath_export(const char *expname, const char *dirpath,
gf_boolean_t export_parsing_match)
{
int ret = 0;
size_t dlen;
char *fullpath = NULL;
char *second_slash = NULL;
char *dirdup = NULL;
if ((!expname) || (!dirpath))
return 0;
dirdup = strdupa(dirpath);
/* Some clients send a dirpath for mount that includes the slash at the
* end. String compare for searching the export will fail because our
* exports list does not include that slash. Remove the slash to
* compare.
*/
dlen = strlen(dirdup);
if (dlen && dirdup[dlen - 1] == '/')
dirdup[dlen - 1] = '\0';
/* Here we try to match fullpaths with export names */
fullpath = dirdup;
if (export_parsing_match) {
if (dirdup[0] == '/')
fullpath = dirdup + 1;
second_slash = strchr(fullpath, '/');
if (second_slash)
*second_slash = '\0';
}
/* The export name begins with a slash so move it forward by one
* to ignore the slash when we want to compare the fullpath and
* export.
*/
if (fullpath[0] != '/')
expname++;
if (strcmp(expname, fullpath) == 0)
ret = 1;
return ret;
}
int
mnt3svc_mount_inode(rpcsvc_request_t *req, struct mount3_state *ms,
xlator_t *xl, inode_t *exportinode)
{
int ret = -EFAULT;
nfs_user_t nfu = {
0,
};
loc_t exportloc = {
0,
};
if ((!req) || (!xl) || (!ms) || (!exportinode))
return ret;
ret = nfs_inode_loc_fill(exportinode, &exportloc, NFS_RESOLVE_EXIST);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_INODE_LOC_FILL_ERROR,
"Loc fill failed for export inode"
": gfid %s, volume: %s",
uuid_utoa(exportinode->gfid), xl->name);
goto err;
}
/* To service the mount request, all we need to do
* is to send a lookup fop that returns the stat
* for the root of the child volume. This is
* used to build the root fh sent to the client.
*/
nfs_request_user_init(&nfu, req);
ret = nfs_lookup(ms->nfsx, xl, &nfu, &exportloc, mnt3svc_lookup_mount_cbk,
(void *)req);
nfs_loc_wipe(&exportloc);
err:
return ret;
}
/* For a volume mount request, we just have to create loc on the root inode,
* and send a lookup. In the lookup callback the mount reply is send along with
* the file handle.
*/
int
mnt3svc_volume_mount(rpcsvc_request_t *req, struct mount3_state *ms,
struct mnt3_export *exp)
{
inode_t *exportinode = NULL;
int ret = -EFAULT;
uuid_t rootgfid = {
0,
};
if ((!req) || (!exp) || (!ms))
return ret;
rootgfid[15] = 1;
exportinode = inode_find(exp->vol->itable, rootgfid);
if (!exportinode) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOENT, NFS_MSG_GET_ROOT_INODE_FAIL,
"Failed to get root inode");
ret = -ENOENT;
goto err;
}
ret = mnt3svc_mount_inode(req, ms, exp->vol, exportinode);
inode_unref(exportinode);
err:
return ret;
}
/* The catch with directory exports is that the first component of the export
* name will be the name of the volume.
* Any lookup that needs to be performed to build the directory's file handle
* needs to start from the directory path from the root of the volume. For that
* we need to strip out the volume name first.
*/
char *
mnt3_get_volume_subdir(char *dirpath, char **volname)
{
/* subdir points to the first / after the volume name while dirpath
* points to the first char of the volume name.
*/
char *subdir = NULL;
int volname_len = 0;
static char *root = "/";
/* all callers are expected to pass a valid *dirpath */
GF_ASSERT(dirpath);
if (dirpath[0] == '/')
dirpath++;
subdir = index(dirpath, (int)'/');
if (!subdir) {
subdir = root;
volname_len = strlen(dirpath);
} else {
volname_len = subdir - dirpath;
}
if (!volname)
goto out;
if (!*volname)
goto out;
strncpy(*volname, dirpath, volname_len);
*(*volname + volname_len) = '\0';
out:
return subdir;
}
void
mnt3_resolve_state_wipe(mnt3_resolve_t *mres)
{
if (!mres)
return;
nfs_loc_wipe(&mres->resolveloc);
GF_FREE(mres);
}
/* Sets up the component argument to contain the next component in the path and
* sets up path as an absolute path starting from the next component.
*/
static char *
setup_next_component(char *path, size_t plen, char *component, size_t clen)
{
char *comp = NULL;
char *nextcomp = NULL;
if ((!path) || (!component))
return NULL;
strncpy(component, path, clen);
comp = index(component, (int)'/');
if (!comp)
goto err;
comp++;
nextcomp = index(comp, (int)'/');
if (nextcomp) {
strncpy(path, nextcomp, plen);
*nextcomp = '\0';
} else
path[0] = '\0';
err:
return comp;
}
int32_t
mnt3_resolve_subdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
int32_t op_ret, int32_t op_errno, inode_t *inode,
struct iatt *buf, dict_t *xattr,
struct iatt *postparent);
int32_t
mnt3_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
int32_t op_ret, int32_t op_errno, const char *path,
struct iatt *buf, dict_t *xdata);
/* There are multiple components in the directory export path and each one
* needs to be looked up one after the other.
*/
int
__mnt3_resolve_export_subdir_comp(mnt3_resolve_t *mres)
{
char dupsubdir[MNTPATHLEN];
char *nextcomp = NULL;
int ret = -EFAULT;
nfs_user_t nfu = {
0,
};
uuid_t gfid = {
0,
};
if (!mres)
return ret;
nextcomp = setup_next_component(mres->remainingdir,
sizeof(mres->remainingdir), dupsubdir,
sizeof(dupsubdir));
if (!nextcomp)
goto err;
/* Wipe the contents of the previous component */
gf_uuid_copy(gfid, mres->resolveloc.inode->gfid);
nfs_loc_wipe(&mres->resolveloc);
ret = nfs_entry_loc_fill(mres->mstate->nfsx, mres->exp->vol->itable, gfid,
nextcomp, &mres->resolveloc, NFS_RESOLVE_CREATE,
NULL);
if ((ret < 0) && (ret != -2)) {
gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_RESOLVE_INODE_FAIL,
"Failed to resolve and "
"create inode: parent gfid %s, entry %s",
uuid_utoa(gfid), nextcomp);
ret = -EFAULT;
goto err;
}
nfs_request_user_init(&nfu, mres->req);
if (IA_ISLNK(mres->resolveloc.inode->ia_type)) {
ret = nfs_readlink(mres->mstate->nfsx, mres->exp->vol, &nfu,
&mres->resolveloc, mnt3_readlink_cbk, mres);
gf_msg_debug(GF_MNT, 0,
"Symlink found , need to resolve"
" into directory handle");
goto err;
}
ret = nfs_lookup(mres->mstate->nfsx, mres->exp->vol, &nfu,
&mres->resolveloc, mnt3_resolve_subdir_cbk, mres);
err:
return ret;
}
int
__mnt3_resolve_subdir(mnt3_resolve_t *mres);
/*
* Per the AFR2 comments, this function performs the "fresh" lookup
* by deleting the inode from cache and calling __mnt3_resolve_subdir
* again.
*/
int
__mnt3_fresh_lookup(mnt3_resolve_t *mres)
{
inode_unlink(mres->resolveloc.inode, mres->resolveloc.parent,
mres->resolveloc.name);
strncpy(mres->remainingdir, mres->resolveloc.path,
strlen(mres->resolveloc.path));
nfs_loc_wipe(&mres->resolveloc);
return __mnt3_resolve_subdir(mres);
}
int32_t
mnt3_resolve_subdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
int32_t op_ret, int32_t op_errno, inode_t *inode,
struct iatt *buf, dict_t *xattr,
struct iatt *postparent)
{
mnt3_resolve_t *mres = NULL;
mountstat3 mntstat = MNT3ERR_SERVERFAULT;
struct nfs3_fh fh = {
{0},
};
int autharr[10];
int autharrlen = 0;
rpcsvc_t *svc = NULL;
mountres3 res = {
0,
};
xlator_t *mntxl = NULL;
char *path = NULL;
struct mount3_state *ms = NULL;
int authcode = 0;
char *authorized_host = NULL;
char *authorized_path = NULL;
inode_t *linked_inode = NULL;
mres = frame->local;
ms = mres->mstate;
mntxl = (xlator_t *)cookie;
if (op_ret == -1 && op_errno == ESTALE) {
/* Nuke inode from cache and try the LOOKUP
* request again. */
return __mnt3_fresh_lookup(mres);
} else if (op_ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, op_errno, NFS_MSG_RESOLVE_SUBDIR_FAIL,
"path=%s (%s)", mres->resolveloc.path, strerror(op_errno));
mntstat = mnt3svc_errno_to_mnterr(op_errno);
goto err;
}
linked_inode = inode_link(mres->resolveloc.inode, mres->resolveloc.parent,
mres->resolveloc.name, buf);
if (linked_inode)
nfs_fix_generation(this, linked_inode);
nfs3_fh_build_child_fh(&mres->parentfh, buf, &fh);
if (strlen(mres->remainingdir) <= 0) {
int alloclen;
op_ret = -1;
mntstat = MNT3_OK;
/* Construct the full path */
int resolveloc_path_len = strlen(mres->resolveloc.path);
alloclen = strlen(mres->exp->expname) + resolveloc_path_len + 1;
mres->exp->fullpath = GF_MALLOC(alloclen, gf_nfs_mt_char);
if (!mres->exp->fullpath) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed.");
goto err;
}
snprintf(mres->exp->fullpath, alloclen, "%s%s", mres->exp->expname,
mres->resolveloc.path);
/* Check if this path is authorized to be mounted */
authcode = mnt3_authenticate_request(
ms, mres->req, NULL, NULL, mres->exp->fullpath, &authorized_path,
&authorized_host, FALSE);
if (authcode != 0) {
mntstat = MNT3ERR_ACCES;
gf_msg_debug(GF_MNT, 0, "Client mount not allowed");
op_ret = -1;
goto err;
}
alloclen = strlen(mres->exp->vol->name) + resolveloc_path_len + 2;
path = GF_MALLOC(alloclen, gf_nfs_mt_char);
if (!path) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
/* Build mountid from the authorized path and stick it in the
* filehandle that will get passed back to the client
*/
__mnt3_build_mountid_from_path(authorized_path, fh.mountid);
snprintf(path, alloclen, "/%s%s", mres->exp->vol->name,
mres->resolveloc.path);
mnt3svc_update_mountlist(mres->mstate, mres->req, path,
mres->exp->fullpath);
GF_FREE(path);
} else {
mres->parentfh = fh;
op_ret = __mnt3_resolve_export_subdir_comp(mres);
if (op_ret < 0)
mntstat = mnt3svc_errno_to_mnterr(-op_ret);
}
err:
if (op_ret == -1) {
gf_msg_debug(GF_MNT, 0, "Mount reply status: %d", mntstat);
svc = rpcsvc_request_service(mres->req);
autharrlen = rpcsvc_auth_array(svc, mntxl->name, autharr, 10);
res = mnt3svc_set_mountres3(mntstat, &fh, autharr, autharrlen);
mnt3svc_submit_reply(mres->req, (void *)&res,
(mnt3_serializer)xdr_serialize_mountres3);
mnt3_resolve_state_wipe(mres);
}
GF_FREE(authorized_path);
GF_FREE(authorized_host);
return 0;
}
/* This function resolves symbolic link into directory path from
* the mount and restart the parsing process from the beginning
*
* Note : Path specified in the symlink should be relative to the
* symlink, because that is the one which is consistent through
* out the file system.
* If the symlink resolves into another symlink ,then same process
* will be repeated.
* If symbolic links points outside the file system are not considered
* here.
*
* TODO : 1.) This function cannot handle symlinks points to path which
* goes out of the filesystem and comes backs again to same.
* For example, consider vol is exported volume.It contains
* dir,
* symlink1 which points to ../vol/dir,
* symlink2 which points to ../mnt/../vol/dir,
* symlink1 and symlink2 are not handled right now.
*
* 2.) udp mount routine is much simpler from tcp routine and resolves
* symlink directly.May be ,its better we change this routine
* similar to udp
*/
int32_t
mnt3_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
int32_t op_ret, int32_t op_errno, const char *path,
struct iatt *buf, dict_t *xdata)
{
mnt3_resolve_t *mres = NULL;
int ret = -EFAULT;
char *real_loc = NULL;
size_t path_len = 0;
size_t parent_path_len = 0;
char *parent_path = NULL;
char *absolute_path = NULL;
char *relative_path = NULL;
int mntstat = 0;
GF_ASSERT(frame);
mres = frame->local;
if (!mres || !path || (path[0] == '/') || (op_ret < 0))
goto mnterr;
/* Finding current location of symlink */
parent_path_len = strlen(mres->resolveloc.path) -
strlen(mres->resolveloc.name);
parent_path = gf_strndup(mres->resolveloc.path, parent_path_len);
if (!parent_path) {
ret = -ENOMEM;
goto mnterr;
}
relative_path = gf_strdup(path);
if (!relative_path) {
ret = -ENOMEM;
goto mnterr;
}
/* Resolving into absolute path */
ret = gf_build_absolute_path(parent_path, relative_path, &absolute_path);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SYMLINK_ERROR,
"Cannot resolve symlink, path is out of boundary "
"from current location %s and with relative path "
"%s pointed by symlink",
parent_path, relative_path);
goto mnterr;
}
/* Building the actual mount path to be mounted */
path_len = strlen(mres->exp->vol->name) + strlen(absolute_path) +
strlen(mres->remainingdir) + 1;
real_loc = GF_MALLOC(path_len, gf_nfs_mt_char);
if (!real_loc) {
ret = -ENOMEM;
goto mnterr;
}
snprintf(real_loc, path_len, "%s%s", mres->exp->vol->name, absolute_path);
gf_path_strip_trailing_slashes(real_loc);
/* There may entries after symlink in the mount path,
* we should include remaining entries too */
if (strlen(mres->remainingdir) > 0)
strcat(real_loc, mres->remainingdir);
gf_msg_debug(GF_MNT, 0,
"Resolved path is : %s%s "
"and actual mount path is %s",
absolute_path, mres->remainingdir, real_loc);
/* After the resolving the symlink , parsing should be done
* for the populated mount path
*/
ret = mnt3_parse_dir_exports(mres->req, mres->mstate, real_loc, _gf_true);
if (ret) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_RESOLVE_ERROR,
"Resolved into an unknown path %s%s "
"from the current location of symlink %s",
absolute_path, mres->remainingdir, parent_path);
}
GF_FREE(real_loc);
GF_FREE(absolute_path);
GF_FREE(parent_path);
GF_FREE(relative_path);
return ret;
mnterr:
if (mres) {
mntstat = mnt3svc_errno_to_mnterr(-ret);
mnt3svc_mnt_error_reply(mres->req, mntstat);
} else
gf_msg(GF_MNT, GF_LOG_CRITICAL, EINVAL, NFS_MSG_INVALID_ENTRY,
"mres == NULL, this should *never* happen");
if (absolute_path)
GF_FREE(absolute_path);
if (parent_path)
GF_FREE(parent_path);
if (relative_path)
GF_FREE(relative_path);
return ret;
}
/* We will always have to perform a hard lookup on all the components of a
* directory export for a mount request because in the mount reply we need the
* file handle of the directory. Our file handle creation code is designed with
* the assumption that to build a child file/dir fh, we'll always have the
* parent dir's fh available so that we may copy the hash array of the previous
* dir levels.
*
* Since we do not store the file handles anywhere, for every mount request we
* must resolve the file handles of every component so that the parent dir file
* of the exported directory can be built.
*/
int
__mnt3_resolve_subdir(mnt3_resolve_t *mres)
{
char dupsubdir[MNTPATHLEN];
char *firstcomp = NULL;
int ret = -EFAULT;
nfs_user_t nfu = {
0,
};
uuid_t rootgfid = {
0,
};
if (!mres)
return ret;
firstcomp = setup_next_component(mres->remainingdir,
sizeof(mres->remainingdir), dupsubdir,
sizeof(dupsubdir));
if (!firstcomp)
goto err;
rootgfid[15] = 1;
ret = nfs_entry_loc_fill(mres->mstate->nfsx, mres->exp->vol->itable,
rootgfid, firstcomp, &mres->resolveloc,
NFS_RESOLVE_CREATE, NULL);
if ((ret < 0) && (ret != -2)) {
gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_RESOLVE_INODE_FAIL,
"Failed to resolve and "
"create inode for volume root: %s",
mres->exp->vol->name);
ret = -EFAULT;
goto err;
}
nfs_request_user_init(&nfu, mres->req);
if (IA_ISLNK(mres->resolveloc.inode->ia_type)) {
ret = nfs_readlink(mres->mstate->nfsx, mres->exp->vol, &nfu,
&mres->resolveloc, mnt3_readlink_cbk, mres);
gf_msg_debug(GF_MNT, 0,
"Symlink found , need to resolve "
"into directory handle");
goto err;
}
ret = nfs_lookup(mres->mstate->nfsx, mres->exp->vol, &nfu,
&mres->resolveloc, mnt3_resolve_subdir_cbk, mres);
err:
return ret;
}
static gf_boolean_t
mnt3_match_subnet_v4(struct addrinfo *ai, uint32_t saddr, uint32_t mask)
{
for (; ai; ai = ai->ai_next) {
struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
if (sin->sin_family != AF_INET)
continue;
if (mask_match(saddr, sin->sin_addr.s_addr, mask))
return _gf_true;
}
return _gf_false;
}
/**
* This function will verify if the client is allowed to mount
* the directory or not. Client's IP address will be compared with
* allowed IP list or range present in mnt3_export structure.
*
* @param client_addr - This structure contains client's IP address.
* @param export - mnt3_export structure. Contains allowed IP list/range.
*
* @return 0 - on Success and -EACCES on failure.
*
* TODO: Support IPv6 subnetwork
*/
int
mnt3_verify_auth(struct sockaddr_in *client_addr, struct mnt3_export *export)
{
int retvalue = -EACCES;
int ret = 0;
struct host_auth_spec *host = NULL;
struct sockaddr_in *allowed_addr = NULL;
struct addrinfo *allowed_addrinfo = NULL;
struct addrinfo hint = {
.ai_family = AF_INET,
.ai_protocol = (int)IPPROTO_TCP,
.ai_flags = AI_CANONNAME,
};
/* Sanity check */
if ((NULL == client_addr) || (NULL == export) ||
(NULL == export->hostspec)) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
"Invalid argument");
return retvalue;
}
host = export->hostspec;
/*
* Currently IPv4 subnetwork is supported i.e. AF_INET.
* TODO: IPv6 subnetwork i.e. AF_INET6.
*/
if (client_addr->sin_family != AF_INET) {
gf_msg(GF_MNT, GF_LOG_ERROR, EAFNOSUPPORT, NFS_MSG_UNSUPPORTED_VERSION,
"Only IPv4 is supported for subdir-auth");
return retvalue;
}
/* Try to see if the client IP matches the allowed IP list.*/
while (NULL != host) {
GF_ASSERT(host->host_addr);
if (NULL != allowed_addrinfo) {
freeaddrinfo(allowed_addrinfo);
allowed_addrinfo = NULL;
}
/* Get the addrinfo for the allowed host (host_addr). */
ret = getaddrinfo(host->host_addr, NULL, &hint, &allowed_addrinfo);
if (0 != ret) {
/*
* getaddrinfo() FAILED for the host IP addr. Continue
* to search other allowed hosts in the hostspec list.
*/
gf_msg_debug(GF_MNT, 0, "getaddrinfo: %s\n", gai_strerror(ret));
host = host->next;
continue;
}
allowed_addr = (struct sockaddr_in *)(allowed_addrinfo->ai_addr);
if (NULL == allowed_addr) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
"Invalid structure");
break;
}
/* Check if the network addr of both IPv4 socket match */
if (mnt3_match_subnet_v4(allowed_addrinfo, client_addr->sin_addr.s_addr,
host->netmask)) {
retvalue = 0;
break;
}
/* No match yet, continue the search */
host = host->next;
}
/* FREE the dynamic memory allocated by getaddrinfo() */
if (NULL != allowed_addrinfo) {
freeaddrinfo(allowed_addrinfo);
}
return retvalue;
}
int
mnt3_resolve_subdir(rpcsvc_request_t *req, struct mount3_state *ms,
struct mnt3_export *exp, char *subdir,
gf_boolean_t send_reply)
{
mnt3_resolve_t *mres = NULL;
int ret = -EFAULT;
struct nfs3_fh pfh = GF_NFS3FH_STATIC_INITIALIZER;
struct sockaddr_in *sin = NULL;
if ((!req) || (!ms) || (!exp) || (!subdir))
return ret;
sin = (struct sockaddr_in *)(&(req->trans->peerinfo.sockaddr));
/* Need to check AUTH */
if (NULL != exp->hostspec) {
ret = mnt3_verify_auth(sin, exp);
if (0 != ret) {
gf_msg(GF_MNT, GF_LOG_ERROR, EACCES, NFS_MSG_AUTH_VERIFY_FAILED,
"AUTH verification failed");
return ret;
}
}
/* no reply is needed (WebNFS permissions checking), just return */
if (!send_reply)
return 0; /* no error, mnt3_verify_auth() allowed it */
mres = GF_CALLOC(1, sizeof(mnt3_resolve_t), gf_nfs_mt_mnt3_resolve);
if (!mres) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
mres->exp = exp;
mres->mstate = ms;
mres->req = req;
snprintf(mres->remainingdir, MNTPATHLEN, "%s", subdir);
gf_path_strip_trailing_slashes(mres->remainingdir);
if (gf_nfs_dvm_off(nfs_state(ms->nfsx)))
pfh = nfs3_fh_build_indexed_root_fh(mres->mstate->nfsx->children,
mres->exp->vol);
else
pfh = nfs3_fh_build_uuid_root_fh(exp->volumeid, exp->mountid);
mres->parentfh = pfh;
ret = __mnt3_resolve_subdir(mres);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL,
"Failed to resolve export dir: %s", mres->exp->expname);
GF_FREE(mres);
}
err:
return ret;
}
int
mnt3_resolve_export_subdir(rpcsvc_request_t *req, struct mount3_state *ms,
struct mnt3_export *exp)
{
char *volume_subdir = NULL;
int ret = -EFAULT;
if ((!req) || (!ms) || (!exp))
return ret;
volume_subdir = mnt3_get_volume_subdir(exp->expname, NULL);
ret = mnt3_resolve_subdir(req, ms, exp, volume_subdir, _gf_true);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL,
"Failed to resolve export dir: %s", exp->expname);
goto err;
}
err:
return ret;
}
int
mnt3svc_mount(rpcsvc_request_t *req, struct mount3_state *ms,
struct mnt3_export *exp)
{
int ret = -EFAULT;
if ((!req) || (!ms) || (!exp))
return ret;
if (exp->exptype == MNT3_EXPTYPE_VOLUME)
ret = mnt3svc_volume_mount(req, ms, exp);
else if (exp->exptype == MNT3_EXPTYPE_DIR)
ret = mnt3_resolve_export_subdir(req, ms, exp);
return ret;
}
/* mnt3_mntpath_to_xlator sets this to 1 if the mount is for a full
* volume or 2 for a subdir in the volume.
*
* The parameter 'export_parsing_match' indicates whether this function
* is being called by an exports parser or whether it is being called
* during mount. The behavior is different since we don't have to resolve
* the path when doing the parse.
*/
struct mnt3_export *
mnt3_mntpath_to_export(struct mount3_state *ms, const char *dirpath,
gf_boolean_t export_parsing_match)
{
struct mnt3_export *exp = NULL;
struct mnt3_export *found = NULL;
if ((!ms) || (!dirpath))
return NULL;
LOCK(&ms->mountlock);
list_for_each_entry(exp, &ms->exportlist, explist)
{
/* Search for the an exact match with the volume */
if (mnt3_match_dirpath_export(exp->expname, dirpath,
export_parsing_match)) {
found = exp;
gf_msg_debug(GF_MNT, 0,
"Found export volume: "
"%s",
exp->vol->name);
goto foundexp;
}
}
gf_msg_debug(GF_MNT, 0, "Export not found");
foundexp:
UNLOCK(&ms->mountlock);
return found;
}
static int
mnt3_check_client_net_check(rpcsvc_t *svc, char *expvol, char *ipaddr,
uint16_t port)
{
int ret = RPCSVC_AUTH_REJECT;
if ((!svc) || (!expvol) || (!ipaddr))
goto err;
ret = rpcsvc_auth_check(svc, expvol, ipaddr);
if (ret == RPCSVC_AUTH_REJECT) {
gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_PEER_NOT_ALLOWED,
"Peer %s not allowed", ipaddr);
goto err;
}
ret = rpcsvc_transport_privport_check(svc, expvol, port);
if (ret == RPCSVC_AUTH_REJECT) {
gf_msg(GF_MNT, GF_LOG_INFO, errno, NFS_MSG_PEER_NOT_ALLOWED,
"Peer %s rejected. Unprivileged "
"port %d not allowed",
ipaddr, port);
goto err;
}
ret = RPCSVC_AUTH_ACCEPT;
err:
return ret;
}
int
mnt3_check_client_net_tcp(rpcsvc_request_t *req, char *volname)
{
rpcsvc_t *svc = NULL;
rpc_transport_t *trans = NULL;
union gf_sock_union sock_union;
socklen_t socksize = sizeof(struct sockaddr_in);
char peer[RPCSVC_PEER_STRLEN] = {
0,
};
char *ipaddr = NULL;
uint16_t port = 0;
int ret = RPCSVC_AUTH_REJECT;
if ((!req) || (!volname))
goto err;
svc = rpcsvc_request_service(req);
trans = rpcsvc_request_transport(req);
if ((!svc) || (!trans))
goto err;
ret = rpcsvc_transport_peeraddr(trans, peer, RPCSVC_PEER_STRLEN,
&sock_union.storage, socksize);
if (ret != 0) {
gf_msg(GF_MNT, GF_LOG_WARNING, ENOENT, NFS_MSG_GET_PEER_ADDR_FAIL,
"Failed to get peer "
"addr: %s",
gai_strerror(ret));
ret = RPCSVC_AUTH_REJECT;
goto err;
}
/* peer[] gets IP:PORT formar, slash the port out */
if (!get_host_name((char *)peer, &ipaddr))
ipaddr = peer;
port = ntohs(sock_union.sin.sin_port);
ret = mnt3_check_client_net_check(svc, volname, ipaddr, port);
err:
return ret;
}
static int
mnt3_check_client_net_udp(struct svc_req *req, char *volname, xlator_t *nfsx)
{
rpcsvc_t *svc = NULL;
struct sockaddr_in *sin = NULL;
char ipaddr[INET_ADDRSTRLEN + 1] = {
0,
};
uint16_t port = 0;
int ret = RPCSVC_AUTH_REJECT;
struct nfs_state *nfs = NULL;
if ((!req) || (!volname) || (!nfsx))
goto err;
#if !defined(_TIRPC_SVC_H)
sin = svc_getcaller(req->rq_xprt);
#else
sin = (struct sockaddr_in *)svc_getcaller(req->rq_xprt);
/* TIRPC's svc_getcaller() returns a pointer to a sockaddr_in6, even
* though it might actually be an IPv4 address. It ought return a
* struct sockaddr and make the caller upcast it to the proper
* address family. Sigh.
*/
#endif
if (!sin)
goto err;
/* And let's make sure that it's actually an IPv4 address. */
GF_ASSERT(sin->sin_family == AF_INET);
(void)inet_ntop(AF_INET, &sin->sin_addr, ipaddr, INET_ADDRSTRLEN);
port = ntohs(sin->sin_port);
nfs = (struct nfs_state *)nfsx->private;
if (nfs != NULL)
svc = nfs->rpcsvc;
ret = mnt3_check_client_net_check(svc, volname, ipaddr, port);
err:
return ret;
}
int
mnt3_parse_dir_exports(rpcsvc_request_t *req, struct mount3_state *ms,
char *path, gf_boolean_t send_reply)
{
char volname[1024] = {
0,
};
struct mnt3_export *exp = NULL;
char *volname_ptr = NULL;
char *subdir = NULL;
int ret = -ENOENT;
struct nfs_state *nfs = NULL;
if ((!ms) || (!path))
return -1;
volname_ptr = volname;
subdir = mnt3_get_volume_subdir(path, &volname_ptr);
/* first try to match the full export/subdir */
exp = mnt3_mntpath_to_export(ms, path, _gf_false);
if (!exp) {
gf_msg_trace(GF_MNT, 0,
"Could not find exact matching export "
"for path=%s",
path);
/* if no exact match is found, look for a fallback */
exp = mnt3_mntpath_to_export(ms, volname, _gf_true);
if (!exp) {
gf_msg_trace(GF_MNT, 0,
"Could not find export for "
"volume %s",
volname);
goto err;
}
}
gf_msg_trace(GF_MNT, 0,
"volume %s and export %s will be used for "
"path %s",
exp->vol->name, exp->expname, path);
nfs = (struct nfs_state *)ms->nfsx->private;
if (!nfs)
goto err;
if (!nfs_subvolume_started(nfs, exp->vol)) {
gf_msg_debug(GF_MNT, 0, "Volume %s not started", exp->vol->name);
goto err;
}
ret = mnt3_check_client_net_tcp(req, exp->vol->name);
if (ret == RPCSVC_AUTH_REJECT) {
gf_msg_debug(GF_MNT, 0, "Client mount not allowed");
ret = -EACCES;
goto err;
}
ret = mnt3_resolve_subdir(req, ms, exp, subdir, send_reply);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RESOLVE_SUBDIR_FAIL,
"Failed to resolve export dir: %s", subdir);
goto err;
}
err:
return ret;
}
int
mnt3_find_export(rpcsvc_request_t *req, char *path, struct mnt3_export **e)
{
int ret = -EFAULT;
struct mount3_state *ms = NULL;
struct mnt3_export *exp = NULL;
if ((!req) || (!path) || (!e))
return -1;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"Mount state not present");
rpcsvc_request_seterr(req, SYSTEM_ERR);
goto err;
}
gf_msg_debug(GF_MNT, 0, "dirpath: %s", path);
exp = mnt3_mntpath_to_export(ms, path, _gf_false);
if (exp) {
ret = 0;
*e = exp;
goto err;
}
if (!gf_mnt3_export_dirs(ms)) {
ret = -1;
goto err;
}
ret = mnt3_parse_dir_exports(req, ms, path, _gf_true);
err:
return ret;
}
/**
* _mnt3_get_peer_addr -- Take an rpc request object and return an allocated
* peer address. A peer address is host:port.
*
* @req: An rpc svc request object to extract the peer address from
*
* @return: success: Pointer to an allocated string containing the peer address
* failure: NULL
*/
char *
_mnt3_get_peer_addr(const rpcsvc_request_t *req)
{
rpc_transport_t *trans = NULL;
struct sockaddr_storage sastorage = {
0,
};
char peer[RPCSVC_PEER_STRLEN] = {
0,
};
char *peerdup = NULL;
int ret = 0;
GF_VALIDATE_OR_GOTO(GF_NFS, req, out);
trans = rpcsvc_request_transport(req);
ret = rpcsvc_transport_peeraddr(trans, peer, RPCSVC_PEER_STRLEN, &sastorage,
sizeof(sastorage));
if (ret != 0)
goto out;
peerdup = gf_strdup(peer);
out:
return peerdup;
}
/**
* _mnt3_get_host_from_peer -- Take a peer address and get an allocated
* hostname. The hostname is the string on the
* left side of the colon.
*
* @peer_addr: The peer address to get a hostname from
*
* @return: success: Allocated string containing the hostname
* failure: NULL
*
*/
char *
_mnt3_get_host_from_peer(const char *peer_addr)
{
char *part = NULL;
size_t host_len = 0;
char *colon = NULL;
colon = strrchr(peer_addr, ':');
if (!colon) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_BAD_PEER, "Bad peer %s",
peer_addr);
goto out;
}
host_len = colon - peer_addr;
if (host_len < RPCSVC_PEER_STRLEN)
part = gf_strndup(peer_addr, host_len);
else
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_PEER_TOO_LONG,
"Peer too long %s", peer_addr);
out:
return part;
}
/**
* mnt3_check_cached_fh -- Check if FH is cached.
*
* Calls auxiliary functions based on whether we are checking
* a write operation.
*
*/
int
mnt3_check_cached_fh(struct mount3_state *ms, struct nfs3_fh *fh,
const char *host_addr, gf_boolean_t is_write_op)
{
if (!is_write_op)
return is_nfs_fh_cached(ms->authcache, fh, host_addr);
return is_nfs_fh_cached_and_writeable(ms->authcache, fh, host_addr);
}
/**
* _mnt3_authenticate_req -- Given an RPC request and a path OR a filehandle
* check if the host is authorized to make the
* request. Uses exports/netgroups auth model to
* do this check.
*
* @ms : The mount state
* @req : The RPC request
* @fh : The NFS FH to authenticate (set when authenticating an FOP)
* @path: The path to authenticate (set when authenticating a mount req)
* @authorized_export: Allocate and fill this value when an export is authorized
* @authorized_host: Allocate and fill this value when a host is authorized
* @is_write_op: Is this a write op that we are authenticating?
*
* @return: 0 if authorized
* -EACCES for completely unauthorized fop
* -EROFS for unauthorized write operations (rm, mkdir, write)
*/
int
_mnt3_authenticate_req(struct mount3_state *ms, rpcsvc_request_t *req,
struct nfs3_fh *fh, const char *path,
char **authorized_export, char **authorized_host,
gf_boolean_t is_write_op)
{
char *peer_addr = NULL;
char *host_addr_ip = NULL;
char *host_addr_fqdn = NULL;
int auth_status_code = -EACCES;
char *pathdup = NULL;
size_t dlen = 0;
char *auth_host = NULL;
gf_boolean_t fh_cached = _gf_false;
struct export_item *expitem = NULL;
GF_VALIDATE_OR_GOTO(GF_MNT, ms, out);
GF_VALIDATE_OR_GOTO(GF_MNT, req, out);
peer_addr = _mnt3_get_peer_addr(req);
if (!peer_addr)
goto free_and_out;
host_addr_ip = _mnt3_get_host_from_peer(peer_addr);
if (!host_addr_ip)
goto free_and_out;
if (path) {
/* Need to strip out trailing '/' */
pathdup = strdupa(path);
dlen = strlen(pathdup);
if (dlen > 0 && pathdup[dlen - 1] == '/')
pathdup[dlen - 1] = '\0';
}
/* Check if the filehandle is cached */
fh_cached = mnt3_check_cached_fh(ms, fh, host_addr_ip, is_write_op);
if (fh_cached) {
gf_msg_trace(GF_MNT, 0, "Found cached FH for %s", host_addr_ip);
auth_status_code = 0;
goto free_and_out;
}
/* Check if the IP is authorized */
auth_status_code = mnt3_auth_host(ms->auth_params, host_addr_ip, fh,
pathdup, is_write_op, &expitem);
gf_msg_debug(GF_MNT, 0, "access from IP %s is %s", host_addr_ip,
auth_status_code ? "denied" : "allowed");
if (auth_status_code != 0) {
/* If not, check if the FQDN is authorized */
host_addr_fqdn = gf_rev_dns_lookup(host_addr_ip);
auth_status_code = mnt3_auth_host(ms->auth_params, host_addr_fqdn, fh,
pathdup, is_write_op, &expitem);
gf_msg_debug(GF_MNT, 0, "access from FQDN %s is %s", host_addr_fqdn,
auth_status_code ? "denied" : "allowed");
if (auth_status_code == 0)
auth_host = host_addr_fqdn;
} else
auth_host = host_addr_ip;
/* Skip the lines that set authorized export &
* host if they are null.
*/
if (!authorized_export || !authorized_host) {
/* Cache the file handle if it was authorized */
if (fh && auth_status_code == 0)
cache_nfs_fh(ms->authcache, fh, host_addr_ip, expitem);
goto free_and_out;
}
if (!fh && auth_status_code == 0) {
*authorized_export = gf_strdup(pathdup);
if (!*authorized_export)
gf_msg(GF_MNT, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
"Allocation error when copying "
"authorized path");
*authorized_host = gf_strdup(auth_host);
if (!*authorized_host)
gf_msg(GF_MNT, GF_LOG_CRITICAL, ENOMEM, NFS_MSG_NO_MEMORY,
"Allocation error when copying "
"authorized host");
}
free_and_out:
/* Free allocated strings after doing the auth */
GF_FREE(peer_addr);
GF_FREE(host_addr_fqdn);
GF_FREE(host_addr_ip);
out:
return auth_status_code;
}
/**
* mnt3_authenticate_request -- Given an RPC request and a path, check if the
* host is authorized to make the request. This
* function calls _mnt3_authenticate_req_path ()
* in a loop for the parent of each path while
* the authentication check for that path is
* failing.
*
* E.g. If the requested path is /patchy/L1, and /patchy is authorized, but
* /patchy/L1 is not, it follows this code path :
*
* _mnt3_authenticate_req ("/patchy/L1") -> F
* _mnt3_authenticate_req ("/patchy"); -> T
* return T;
*
* @ms : The mount state
* @req : The RPC request
* @path: The requested path
* @authorized_path: This gets allocated and populated with the authorized path
* @authorized_host: This gets allocated and populated with the authorized host
* @return: 0 if authorized
* -EACCES for completely unauthorized fop
* -EROFS for unauthorized write operations (rm, mkdir, write)
*/
int
mnt3_authenticate_request(struct mount3_state *ms, rpcsvc_request_t *req,
struct nfs3_fh *fh, const char *volname,
const char *path, char **authorized_path,
char **authorized_host, gf_boolean_t is_write_op)
{
int auth_status_code = -EACCES;
char *parent_path = NULL;
const char *parent_old = NULL;
GF_VALIDATE_OR_GOTO(GF_MNT, ms, out);
GF_VALIDATE_OR_GOTO(GF_MNT, req, out);
/* If this option is not set, just allow it through */
if (!ms->nfs->exports_auth) {
/* This function is called in a variety of use-cases (mount
* + each fop) so path/authorized_path are not always present.
* For the cases which it _is_ present we need to populate the
* authorized_path. */
if (path && authorized_path)
*authorized_path = gf_strdup(path);
auth_status_code = 0;
goto out;
}
/* First check if the path is allowed */
auth_status_code = _mnt3_authenticate_req(
ms, req, fh, path, authorized_path, authorized_host, is_write_op);
/* If the filehandle is set, just exit since we have to make only
* one call to the function above
*/
if (fh)
goto out;
parent_old = path;
while (auth_status_code != 0) {
/* Get the path's parent */
parent_path = gf_resolve_path_parent(parent_old);
if (!parent_path) /* Nothing left in the path to resolve */
goto out;
/* Authenticate it */
auth_status_code = _mnt3_authenticate_req(ms, req, fh, parent_path,
authorized_path,
authorized_host, is_write_op);
parent_old = strdupa(parent_path); /* Copy the parent onto the
* stack.
*/
GF_FREE(parent_path); /* Free the allocated parent string */
}
out:
return auth_status_code;
}
int
mnt3svc_mnt(rpcsvc_request_t *req)
{
struct iovec pvec = {
0,
};
char path[MNTPATHLEN];
int ret = -1;
struct mount3_state *ms = NULL;
mountstat3 mntstat = MNT3ERR_SERVERFAULT;
struct mnt3_export *exp = NULL;
struct nfs_state *nfs = NULL;
int authcode = 0;
if (!req)
return -1;
pvec.iov_base = path;
pvec.iov_len = MNTPATHLEN;
ret = xdr_to_mountpath(pvec, req->msg[0]);
if (ret == -1) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR,
"Failed to decode args");
rpcsvc_request_seterr(req, GARBAGE_ARGS);
goto rpcerr;
}
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"Mount state not present");
rpcsvc_request_seterr(req, SYSTEM_ERR);
ret = -1;
goto rpcerr;
}
nfs = (struct nfs_state *)ms->nfsx->private;
gf_msg_debug(GF_MNT, 0, "dirpath: %s", path);
ret = mnt3_find_export(req, path, &exp);
if (ret < 0) {
mntstat = mnt3svc_errno_to_mnterr(-ret);
goto mnterr;
} else if (!exp) {
/*
* SPECIAL CASE: exp is NULL if "path" is subdir in
* call to mnt3_find_export().
*
* This is subdir mount, we are already DONE!
* nfs_subvolume_started() and mnt3_check_client_net_tcp()
* validation are done in mnt3_parse_dir_exports()
* which is invoked through mnt3_find_export().
*
* TODO: All mount should happen thorugh mnt3svc_mount()
* It needs more clean up.
*/
return (0);
}
if (!nfs_subvolume_started(nfs, exp->vol)) {
gf_msg_debug(GF_MNT, 0, "Volume %s not started", exp->vol->name);
ret = -1;
mntstat = MNT3ERR_NOENT;
goto mnterr;
}
ret = mnt3_check_client_net_tcp(req, exp->vol->name);
if (ret == RPCSVC_AUTH_REJECT) {
mntstat = MNT3ERR_ACCES;
gf_msg_debug(GF_MNT, 0, "Client mount not allowed");
ret = -1;
goto mnterr;
}
/* The second authentication check is the exports/netgroups
* check.
*/
authcode = mnt3_authenticate_request(ms, req, NULL, NULL, path, NULL, NULL,
_gf_false);
if (authcode != 0) {
mntstat = MNT3ERR_ACCES;
gf_msg_debug(GF_MNT, 0, "Client mount not allowed");
ret = -1;
goto mnterr;
}
ret = mnt3svc_mount(req, ms, exp);
if (ret < 0)
mntstat = mnt3svc_errno_to_mnterr(-ret);
mnterr:
if (ret < 0) {
mnt3svc_mnt_error_reply(req, mntstat);
ret = 0;
}
rpcerr:
return ret;
}
int
mnt3svc_null(rpcsvc_request_t *req)
{
struct iovec dummyvec = {
0,
};
if (!req) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
"Got NULL request!");
return 0;
}
rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL);
return 0;
}
mountlist
__build_mountlist(struct mount3_state *ms, int *count)
{
struct mountbody *mlist = NULL;
struct mountbody *prev = NULL;
struct mountbody *first = NULL;
size_t namelen = 0;
int ret = -1;
struct mountentry *me = NULL;
if ((!ms) || (!count))
return NULL;
/* read rmtab, other peers might have updated it */
mount_read_rmtab(ms);
*count = 0;
gf_msg_debug(GF_MNT, 0, "Building mount list:");
list_for_each_entry(me, &ms->mountlist, mlist)
{
namelen = strlen(me->exname);
mlist = GF_CALLOC(1, sizeof(*mlist), gf_nfs_mt_mountbody);
if (!mlist) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto free_list;
}
if (!first)
first = mlist;
mlist->ml_directory = GF_MALLOC(namelen + 2, gf_nfs_mt_char);
if (!mlist->ml_directory) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto free_list;
}
strcpy(mlist->ml_directory, me->exname);
namelen = strlen(me->hostname);
mlist->ml_hostname = GF_MALLOC(namelen + 2, gf_nfs_mt_char);
if (!mlist->ml_hostname) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto free_list;
}
strcat(mlist->ml_hostname, me->hostname);
gf_msg_debug(GF_MNT, 0, "mount entry: dir: %s, host: %s",
mlist->ml_directory, mlist->ml_hostname);
if (prev) {
prev->ml_next = mlist;
prev = mlist;
} else
prev = mlist;
(*count)++;
}
ret = 0;
free_list:
if (ret == -1) {
xdr_free_mountlist(first);
first = NULL;
}
return first;
}
mountlist
mnt3svc_build_mountlist(struct mount3_state *ms, int *count)
{
struct mountbody *first = NULL;
LOCK(&ms->mountlock);
{
first = __build_mountlist(ms, count);
}
UNLOCK(&ms->mountlock);
return first;
}
int
mnt3svc_dump(rpcsvc_request_t *req)
{
int ret = -1;
struct mount3_state *ms = NULL;
mountlist mlist;
mountstat3 mstat = 0;
mnt3_serializer sfunc = NULL;
void *arg = NULL;
if (!req)
return -1;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
rpcsvc_request_seterr(req, SYSTEM_ERR);
goto rpcerr;
}
sfunc = (mnt3_serializer)xdr_serialize_mountlist;
mlist = mnt3svc_build_mountlist(ms, &ret);
arg = &mlist;
if (!mlist) {
if (ret != 0) {
rpcsvc_request_seterr(req, SYSTEM_ERR);
ret = -1;
goto rpcerr;
} else {
arg = &mstat;
sfunc = (mnt3_serializer)xdr_serialize_mountstat3;
}
}
mnt3svc_submit_reply(req, arg, sfunc);
xdr_free_mountlist(mlist);
ret = 0;
rpcerr:
return ret;
}
int
mnt3svc_umount(struct mount3_state *ms, char *dirpath, char *hostname)
{
struct mountentry *me = NULL;
int ret = -1;
gf_store_handle_t *sh = NULL;
struct nfs_state *nfs = NULL;
gf_boolean_t update_rmtab = _gf_false;
if ((!ms) || (!dirpath) || (!hostname))
return -1;
nfs = (struct nfs_state *)ms->nfsx->private;
update_rmtab = mount_open_rmtab(nfs->rmtab, &sh);
if (update_rmtab) {
ret = gf_store_lock(sh);
if (ret)
goto out_free;
}
LOCK(&ms->mountlock);
{
if (update_rmtab)
__mount_read_rmtab(sh, &ms->mountlist, _gf_false);
if (list_empty(&ms->mountlist)) {
ret = 0;
goto out_unlock;
}
ret = -1;
list_for_each_entry(me, &ms->mountlist, mlist)
{
if ((strcmp(me->exname, dirpath) == 0) &&
(strcmp(me->hostname, hostname) == 0)) {
ret = 0;
break;
}
}
/* Need this check here because at the end of the search me
* might still be pointing to the last entry, which may not be
* the one we're looking for.
*/
if (ret == -1) { /* Not found in list. */
gf_msg_trace(GF_MNT, 0, "Export not found");
goto out_unlock;
}
if (!me)
goto out_unlock;
gf_msg_debug(GF_MNT, 0, "Unmounting: dir %s, host: %s", me->exname,
me->hostname);
list_del(&me->mlist);
GF_FREE(me);
if (update_rmtab)
__mount_rewrite_rmtab(ms, sh);
}
out_unlock:
UNLOCK(&ms->mountlock);
if (update_rmtab)
gf_store_unlock(sh);
out_free:
if (update_rmtab)
gf_store_handle_destroy(sh);
return ret;
}
int
mnt3svc_umnt(rpcsvc_request_t *req)
{
char hostname[MNTPATHLEN];
char dirpath[MNTPATHLEN];
struct iovec pvec = {
0,
};
int ret = -1;
struct mount3_state *ms = NULL;
mountstat3 mstat = MNT3_OK;
char *colon = NULL;
if (!req)
return -1;
/* Remove the mount point from the exports list. */
pvec.iov_base = dirpath;
pvec.iov_len = MNTPATHLEN;
ret = xdr_to_mountpath(pvec, req->msg[0]);
if (ret == -1) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARGS_DECODE_ERROR,
"Failed decode args");
rpcsvc_request_seterr(req, GARBAGE_ARGS);
goto rpcerr;
}
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"Mount state not present");
rpcsvc_request_seterr(req, SYSTEM_ERR);
ret = -1;
goto rpcerr;
}
ret = rpcsvc_transport_peername(req->trans, hostname, MNTPATHLEN);
if (ret != 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOENT, NFS_MSG_GET_REMOTE_NAME_FAIL,
"Failed to get remote name: %s", gai_strerror(ret));
goto rpcerr;
}
colon = strrchr(hostname, ':');
if (colon) {
*colon = '\0';
}
gf_path_strip_trailing_slashes(dirpath);
gf_msg_debug(GF_MNT, 0, "dirpath: %s, hostname: %s", dirpath, hostname);
ret = mnt3svc_umount(ms, dirpath, hostname);
if (ret == -1) {
ret = 0;
mstat = MNT3ERR_NOENT;
}
/* FIXME: also take care of the corner case where the
* client was resolvable at mount but not at the umount - vice-versa.
*/
mnt3svc_submit_reply(req, &mstat,
(mnt3_serializer)xdr_serialize_mountstat3);
rpcerr:
return ret;
}
int
__mnt3svc_umountall(struct mount3_state *ms)
{
struct mountentry *me = NULL;
struct mountentry *tmp = NULL;
if (!ms)
return -1;
if (list_empty(&ms->mountlist))
return 0;
list_for_each_entry_safe(me, tmp, &ms->mountlist, mlist)
{
list_del(&me->mlist); /* Remove from the mount list */
__mountdict_remove(ms, me); /* Remove from the mount dict */
GF_FREE(me);
}
return 0;
}
int
mnt3svc_umountall(struct mount3_state *ms)
{
int ret = -1;
if (!ms)
return -1;
LOCK(&ms->mountlock);
{
ret = __mnt3svc_umountall(ms);
}
UNLOCK(&ms->mountlock);
return ret;
}
int
mnt3svc_umntall(rpcsvc_request_t *req)
{
int ret = RPCSVC_ACTOR_ERROR;
struct mount3_state *ms = NULL;
mountstat3 mstat = MNT3_OK;
if (!req)
return ret;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"Mount state not present");
rpcsvc_request_seterr(req, SYSTEM_ERR);
goto rpcerr;
}
mnt3svc_umountall(ms);
mnt3svc_submit_reply(req, &mstat,
(mnt3_serializer)xdr_serialize_mountstat3);
ret = RPCSVC_ACTOR_SUCCESS;
rpcerr:
return ret;
}
exports
mnt3_xlchildren_to_exports(rpcsvc_t *svc, struct mount3_state *ms)
{
struct exportnode *elist = NULL;
struct exportnode *prev = NULL;
struct exportnode *first = NULL;
size_t namelen = 0;
int ret = -1;
char *addrstr = NULL;
struct mnt3_export *ent = NULL;
struct nfs_state *nfs = NULL;
if ((!ms) || (!svc))
return NULL;
nfs = (struct nfs_state *)ms->nfsx->private;
if (!nfs)
return NULL;
LOCK(&ms->mountlock);
list_for_each_entry(ent, &ms->exportlist, explist)
{
/* If volume is not started yet, do not list it for tools like
* showmount.
*/
if (!nfs_subvolume_started(nfs, ent->vol))
continue;
elist = GF_CALLOC(1, sizeof(*elist), gf_nfs_mt_exportnode);
if (!elist) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto free_list;
}
if (!first)
first = elist;
namelen = strlen(ent->expname);
elist->ex_dir = GF_MALLOC(namelen + 2, gf_nfs_mt_char);
if (!elist->ex_dir) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
if (first == elist)
first = NULL;
xdr_free_exports_list(elist);
elist = NULL;
goto free_list;
}
strcpy(elist->ex_dir, ent->expname);
addrstr = rpcsvc_volume_allowed(svc->options, ent->vol->name);
if (addrstr) {
/* create a groupnode per allowed client */
char *pos = NULL;
char *addr = NULL;
char *addrs = NULL;
struct groupnode *group = NULL;
struct groupnode *prev_group = NULL;
/* strtok_r() modifies the string, dup it */
addrs = gf_strdup(addrstr);
if (!addrs)
goto free_list;
while (1) {
/* only pass addrs on the 1st call */
addr = strtok_r(group ? NULL : addrs, ",", &pos);
if (addr == NULL)
/* no mode clients */
break;
group = GF_CALLOC(1, sizeof(struct groupnode),
gf_nfs_mt_groupnode);
if (!group) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory "
"allocation failed");
GF_FREE(addrs);
goto free_list;
}
group->gr_name = gf_strdup(addr);
if (!group->gr_name) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory "
"allocation failed");
GF_FREE(group);
GF_FREE(addrs);
goto free_list;
}
/* chain the groups together */
if (!elist->ex_groups)
elist->ex_groups = group;
else if (prev_group && !prev_group->gr_next)
prev_group->gr_next = group;
prev_group = group;
}
GF_FREE(addrs);
} else {
elist->ex_groups = GF_CALLOC(1, sizeof(struct groupnode),
gf_nfs_mt_groupnode);
if (!elist->ex_groups) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation "
"failed");
goto free_list;
}
addrstr = gf_strdup("No Access");
if (!addrstr)
goto free_list;
elist->ex_groups->gr_name = addrstr;
}
if (prev) {
prev->ex_next = elist;
prev = elist;
} else
prev = elist;
}
ret = 0;
free_list:
UNLOCK(&ms->mountlock);
if (ret == -1) {
xdr_free_exports_list(first);
first = NULL;
}
return first;
}
int
mnt3svc_export(rpcsvc_request_t *req)
{
struct mount3_state *ms = NULL;
exports elist = NULL;
int ret = -1;
if (!req)
return -1;
ms = (struct mount3_state *)rpcsvc_request_program_private(req);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
"mount state not found");
rpcsvc_request_seterr(req, SYSTEM_ERR);
goto err;
}
/* Using the children translator names, build the export list */
elist = mnt3_xlchildren_to_exports(rpcsvc_request_service(req), ms);
/* Do not return error when exports list is empty. An exports list can
* be empty when no subvolumes have come up. No point returning error
* and confusing the user.
if (!elist) {
gf_log (GF_MNT, GF_LOG_ERROR, "Failed to build exports list");
nfs_rpcsvc_request_seterr (req, SYSTEM_ERR);
goto err;
}
*/
/* Note how the serializer is passed to the generic reply function. */
mnt3svc_submit_reply(req, &elist, (mnt3_serializer)xdr_serialize_exports);
xdr_free_exports_list(elist);
ret = 0;
err:
return ret;
}
/*
* __mnt3udp_get_mstate() Fetches mount3_state from xlator
* Linkage: Static
* Usage: Used only for UDP MOUNT codepath
*/
static struct mount3_state *
__mnt3udp_get_mstate(xlator_t *nfsx)
{
struct nfs_state *nfs = NULL;
struct mount3_state *ms = NULL;
if (nfsx == NULL)
return NULL;
nfs = (struct nfs_state *)nfsx->private;
if (nfs == NULL)
return NULL;
ms = (struct mount3_state *)nfs->mstate;
return ms;
}
extern int
glfs_resolve_at(struct glfs *, xlator_t *, inode_t *, const char *, loc_t *,
struct iatt *, int, int);
extern struct glfs *
glfs_new_from_ctx(glusterfs_ctx_t *);
extern void
glfs_free_from_ctx(struct glfs *);
static inode_t *
__mnt3udp_get_export_subdir_inode(struct svc_req *req, char *subdir,
char *expname, /* OUT */
struct mnt3_export *exp)
{
inode_t *inode = NULL;
loc_t loc = {
0,
};
struct iatt buf = {
0,
};
int ret = -1;
glfs_t *fs = NULL;
if ((!req) || (!subdir) || (!expname) || (!exp))
return NULL;
/* AUTH check for subdir i.e. nfs.export-dir */
if (exp->hostspec) {
struct sockaddr_in *sin = NULL;
#if !defined(_TIRPC_SVC_H)
sin = svc_getcaller(req->rq_xprt);
#else
sin = (struct sockaddr_in *)svc_getcaller(req->rq_xprt);
/* TIRPC's svc_getcaller() returns a pointer to a
* sockaddr_in6, even though it might actually be an
* IPv4 address. It ought return a struct sockaddr and
* make the caller upcast it to the proper address family.
*/
#endif
/* And let's make sure that it's actually an IPv4 address. */
GF_ASSERT(sin->sin_family == AF_INET);
ret = mnt3_verify_auth(sin, exp);
if (ret) {
gf_msg(GF_MNT, GF_LOG_ERROR, EACCES, NFS_MSG_AUTH_VERIFY_FAILED,
"AUTH(nfs.export-dir) verification failed");
errno = EACCES;
return NULL;
}
}
/*
* IMP: glfs_t fs object is not used by glfs_resolve_at (). The main
* purpose is to not change the ABI of glfs_resolve_at () and not to
* pass a NULL object.
*
* TODO: Instead of linking against libgfapi.so, just for one API
* i.e. glfs_resolve_at(), It would be cleaner if PATH name to
* inode resolution code can be moved to libglusterfs.so or so.
* refer bugzilla for more details :
* https://bugzilla.redhat.com/show_bug.cgi?id=1161573
*/
fs = glfs_new_from_ctx(exp->vol->ctx);
if (!fs)
return NULL;
ret = glfs_resolve_at(fs, exp->vol, NULL, subdir, &loc, &buf,
1 /* Follow link */, 0 /* Hard lookup */);
glfs_free_from_ctx(fs);
if (ret != 0) {
loc_wipe(&loc);
return NULL;
}
inode = inode_ref(loc.inode);
snprintf(expname, PATH_MAX, "/%s%s", exp->vol->name, loc.path);
loc_wipe(&loc);
return inode;
}
static inode_t *
__mnt3udp_get_export_volume_inode(struct svc_req *req, char *volpath,
char *expname, /* OUT */
struct mnt3_export *exp)
{
char *rpath = NULL;
inode_t *inode = NULL;
if ((!req) || (!volpath) || (!expname) || (!exp))
return NULL;
rpath = strchr(volpath, '/');
if (rpath == NULL)
rpath = "/";
inode = inode_from_path(exp->vol->itable, rpath);
snprintf(expname, PATH_MAX, "/%s", exp->vol->name);
return inode;
}
/*
* nfs3_rootfh() is used for NFS MOUNT over UDP i.e. mountudpproc3_mnt_3_svc().
* Especially in mount3udp_thread() THREAD. Gluster NFS starts this thread
* when nfs.mount-udp is ENABLED (set to TRUE/ON).
*/
struct nfs3_fh *
nfs3_rootfh(struct svc_req *req, xlator_t *nfsx, char *path,
char *expname /* OUT */)
{
struct nfs3_fh *fh = NULL;
inode_t *inode = NULL;
struct mnt3_export *exp = NULL;
struct mount3_state *ms = NULL;
struct nfs_state *nfs = NULL;
int mnt3type = MNT3_EXPTYPE_DIR;
int ret = RPCSVC_AUTH_REJECT;
if ((!req) || (!nfsx) || (!path) || (!expname)) {
errno = EFAULT;
return NULL;
}
/*
* 1. First check if the MOUNT is for whole volume.
* i.e. __mnt3udp_get_export_volume_inode ()
* 2. If NOT, then TRY for SUBDIR MOUNT.
* i.e. __mnt3udp_get_export_subdir_inode ()
* 3. If a subdir is exported using nfs.export-dir,
* then the mount type would be MNT3_EXPTYPE_DIR,
* so make sure to find the proper path to be
* resolved using mnt3_get_volume_subdir()
* 3. Make sure subdir export is allowed.
*/
ms = __mnt3udp_get_mstate(nfsx);
if (!ms) {
errno = EFAULT;
return NULL;
}
exp = mnt3_mntpath_to_export(ms, path, _gf_false);
if (exp != NULL)
mnt3type = exp->exptype;
if (mnt3type == MNT3_EXPTYPE_DIR) {
char volname[MNTPATHLEN] = {
0,
};
char *volptr = volname;
/* Subdir export (nfs3.export-dirs) check */
if (!gf_mnt3_export_dirs(ms)) {
errno = EACCES;
return NULL;
}
path = mnt3_get_volume_subdir(path, &volptr);
if (exp == NULL)
exp = mnt3_mntpath_to_export(ms, volname, _gf_false);
}
if (exp == NULL) {
errno = ENOENT;
return NULL;
}
nfs = (struct nfs_state *)nfsx->private;
if (!nfs_subvolume_started(nfs, exp->vol)) {
errno = ENOENT;
return NULL;
}
/* AUTH check: respect nfs.rpc-auth-allow/reject */
ret = mnt3_check_client_net_udp(req, exp->vol->name, nfsx);
if (ret == RPCSVC_AUTH_REJECT) {
errno = EACCES;
return NULL;
}
switch (mnt3type) {
case MNT3_EXPTYPE_VOLUME:
inode = __mnt3udp_get_export_volume_inode(req, path, expname, exp);
break;
case MNT3_EXPTYPE_DIR:
inode = __mnt3udp_get_export_subdir_inode(req, path, expname, exp);
break;
default:
/* Never reachable */
gf_msg(GF_MNT, GF_LOG_ERROR, EFAULT, NFS_MSG_UNKNOWN_MNT_TYPE,
"Unknown MOUNT3 type");
errno = EFAULT;
goto err;
}
if (inode == NULL) {
/* Don't over-write errno */
if (!errno)
errno = ENOENT;
goto err;
}
/* Build the inode from FH */
fh = GF_CALLOC(1, sizeof(*fh), gf_nfs_mt_nfs3_fh);
if (fh == NULL) {
errno = ENOMEM;
goto err;
}
(void)nfs3_build_fh(inode, exp->volumeid, fh);
err:
if (inode)
inode_unref(inode);
return fh;
}
int
mount3udp_add_mountlist(xlator_t *nfsx, char *host, char *export)
{
struct mountentry *me = NULL;
struct mount3_state *ms = NULL;
if ((!host) || (!export) || (!nfsx))
return -1;
ms = __mnt3udp_get_mstate(nfsx);
if (!ms)
return -1;
me = GF_CALLOC(1, sizeof(*me), gf_nfs_mt_mountentry);
if (!me)
return -1;
snprintf(me->exname, MNTPATHLEN, "%s", export);
snprintf(me->hostname, MNTPATHLEN, "%s", host);
INIT_LIST_HEAD(&me->mlist);
LOCK(&ms->mountlock);
{
list_add_tail(&me->mlist, &ms->mountlist);
mount_rewrite_rmtab(ms, NULL);
}
UNLOCK(&ms->mountlock);
return 0;
}
int
mount3udp_delete_mountlist(xlator_t *nfsx, char *hostname, char *export)
{
struct mount3_state *ms = NULL;
if ((!hostname) || (!export) || (!nfsx))
return -1;
ms = __mnt3udp_get_mstate(nfsx);
if (!ms)
return -1;
mnt3svc_umount(ms, export, hostname);
return 0;
}
/**
* This function will parse the hostip (IP address, IP range, or hostname)
* and fill the host_auth_spec structure.
*
* @param hostspec - struct host_auth_spec
* @param hostip - IP address, IP range (CIDR format) or hostname
*
* @return 0 - on success and -1 on failure
*
* NB: This does not support IPv6 currently.
*/
int
mnt3_export_fill_hostspec(struct host_auth_spec *hostspec, const char *hostip)
{
char *ipdupstr = NULL;
char *savptr = NULL;
char *endptr = NULL;
char *ip = NULL;
char *token = NULL;
int ret = -1;
long prefixlen = IPv4_ADDR_SIZE; /* default */
uint32_t shiftbits = 0;
size_t length = 0;
/* Create copy of the string so that the source won't change
*/
ipdupstr = gf_strdup(hostip);
if (NULL == ipdupstr) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
ip = strtok_r(ipdupstr, "/", &savptr);
/* Validate the Hostname or IPv4 address
* TODO: IPv6 support for subdir auth.
*/
length = strlen(ip);
if ((!valid_ipv4_address(ip, (int)length, _gf_false)) &&
(!valid_host_name(ip, (int)length))) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
"Invalid hostname or IPv4 address: %s", ip);
goto err;
}
hostspec->host_addr = gf_strdup(ip);
if (NULL == hostspec->host_addr) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
/**
* User provided CIDR address (xx.xx.xx.xx/n format) is split
* into HOST (IP addr or hostname) and network prefix(n) from
* which netmask would be calculated. This CIDR address may
* denote a single, distinct interface address or the beginning
* address of an entire network.
*
* e.g. the IPv4 block 192.168.100.0/24 represents the 256
* IPv4 addresses from 192.168.100.0 to 192.168.100.255.
* Therefore to check if an IP matches 192.168.100.0/24
* we should mask the IP with FFFFFF00 and compare it with
* host address part of CIDR.
*
* Refer: mask_match() in common-utils.c.
*/
token = strtok_r(NULL, "/", &savptr);
if (token != NULL) {
prefixlen = strtol(token, &endptr, 10);
if ((errno != 0) || (*endptr != '\0') || (prefixlen < 0) ||
(prefixlen > IPv4_ADDR_SIZE)) {
gf_msg(THIS->name, GF_LOG_WARNING, EINVAL, NFS_MSG_INVALID_ENTRY,
"Invalid IPv4 subnetwork mask");
goto err;
}
}
/*
* 1. Calculate the network mask address.
* 2. Convert it into Big-Endian format.
* 3. Store it in hostspec netmask.
*/
shiftbits = IPv4_ADDR_SIZE - prefixlen;
hostspec->netmask = htonl((uint32_t)~0 << shiftbits);
ret = 0; /* SUCCESS */
err:
if (NULL != ipdupstr) {
GF_FREE(ipdupstr);
}
return ret;
}
/**
* This function will parse the AUTH parameter passed along with
* "export-dir" option. If AUTH parameter is present then it will be
* stripped from exportpath and stored in mnt3_export (exp) structure.
*
* @param exp - mnt3_export structure. Holds information needed for mount.
* @param exportpath - Value of "export-dir" key. Holds both export path
* and AUTH parameter for the path.
* exportpath format: <abspath>[(hostdesc[|hostspec|...])]
*
* @return This function will return 0 on success and -1 on failure.
*/
int
mnt3_export_parse_auth_param(struct mnt3_export *exp, char *exportpath)
{
char *token = NULL;
char *savPtr = NULL;
char *hostip = NULL;
struct host_auth_spec *host = NULL;
int ret = 0;
/* Using exportpath directly in strtok_r because we want
* to strip off AUTH parameter from exportpath. */
token = strtok_r(exportpath, "(", &savPtr);
/* Get the next token, which will be the AUTH parameter. */
token = strtok_r(NULL, ")", &savPtr);
if (NULL == token) {
/* If AUTH is not present then we should return success. */
return 0;
}
/* Free any previously allocated hostspec structure. */
if (NULL != exp->hostspec) {
GF_FREE(exp->hostspec);
exp->hostspec = NULL;
}
exp->hostspec = GF_CALLOC(1, sizeof(*(exp->hostspec)), gf_nfs_mt_auth_spec);
if (NULL == exp->hostspec) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
return -1;
}
/* AUTH parameter can have multiple entries. For each entry
* a host_auth_spec structure is created. */
host = exp->hostspec;
hostip = strtok_r(token, "|", &savPtr);
/* Parse all AUTH parameters separated by '|' */
while (NULL != hostip) {
ret = mnt3_export_fill_hostspec(host, hostip);
if (0 != ret) {
gf_msg(GF_MNT, GF_LOG_WARNING, 0, NFS_MSG_PARSE_HOSTSPEC_FAIL,
"Failed to parse hostspec: %s", hostip);
goto err;
}
hostip = strtok_r(NULL, "|", &savPtr);
if (NULL == hostip) {
break;
}
host->next = GF_CALLOC(1, sizeof(*(host)), gf_nfs_mt_auth_spec);
if (NULL == host->next) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
host = host->next;
}
/* In case of success return from here */
return 0;
err:
/* In case of failure free up hostspec structure. */
FREE_HOSTSPEC(exp);
return -1;
}
/**
* exportpath will also have AUTH options (ip address, subnet address or
* hostname) mentioned.
* exportpath format: <abspath>[(hostdesc[|hostspec|...])]
*/
struct mnt3_export *
mnt3_init_export_ent(struct mount3_state *ms, xlator_t *xl, char *exportpath,
uuid_t volumeid)
{
struct mnt3_export *exp = NULL;
int alloclen = 0;
int ret = -1;
if ((!ms) || (!xl))
return NULL;
exp = GF_CALLOC(1, sizeof(*exp), gf_nfs_mt_mnt3_export);
if (!exp) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
return NULL;
}
if (NULL != exportpath) {
/* If exportpath is not NULL then we should check if AUTH
* parameter is present or not. If AUTH parameter is present
* then it will be stripped and stored in mnt3_export (exp)
* structure.
*/
if (0 != mnt3_export_parse_auth_param(exp, exportpath)) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_PARSE_AUTH_PARAM_FAIL,
"Failed to parse auth param");
goto err;
}
}
INIT_LIST_HEAD(&exp->explist);
if (exportpath)
alloclen = strlen(xl->name) + 2 + strlen(exportpath);
else
alloclen = strlen(xl->name) + 2;
exp->expname = GF_MALLOC(alloclen, gf_nfs_mt_char);
if (!exp->expname) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
goto err;
}
if (exportpath) {
gf_msg_trace(GF_MNT, 0, "Initing dir export: %s:%s", xl->name,
exportpath);
exp->exptype = MNT3_EXPTYPE_DIR;
ret = snprintf(exp->expname, alloclen, "/%s%s", xl->name, exportpath);
} else {
gf_msg_trace(GF_MNT, 0, "Initing volume export: %s", xl->name);
exp->exptype = MNT3_EXPTYPE_VOLUME;
ret = snprintf(exp->expname, alloclen, "/%s", xl->name);
}
if (ret < 0) {
gf_msg(xl->name, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_FAIL,
"Failed to set the export name");
goto err;
}
/* Just copy without discrimination, we'll determine whether to
* actually use it when a mount request comes in and a file handle
* needs to be built.
*/
gf_uuid_copy(exp->volumeid, volumeid);
exp->vol = xl;
/* On success we should return from here*/
return exp;
err:
/* On failure free exp and it's members.*/
if (NULL != exp) {
mnt3_export_free(exp);
exp = NULL;
}
return exp;
}
int
__mnt3_init_volume_direxports(struct mount3_state *ms, xlator_t *xlator,
char *optstr, uuid_t volumeid)
{
struct mnt3_export *newexp = NULL;
int ret = -1;
char *savptr = NULL;
char *dupopt = NULL;
char *token = NULL;
if ((!ms) || (!xlator) || (!optstr))
return -1;
dupopt = strdupa(optstr);
token = strtok_r(dupopt, ",", &savptr);
while (token) {
newexp = mnt3_init_export_ent(ms, xlator, token, volumeid);
if (!newexp) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_INIT_DIR_EXP_FAIL,
"Failed to init dir "
"export: %s",
token);
ret = -1;
goto err;
}
list_add_tail(&newexp->explist, &ms->exportlist);
token = strtok_r(NULL, ",", &savptr);
}
ret = 0;
err:
return ret;
}
int
__mnt3_init_volume(struct mount3_state *ms, dict_t *opts, xlator_t *xlator)
{
struct mnt3_export *newexp = NULL;
int ret = -1;
char searchstr[1024];
char *optstr = NULL;
uuid_t volumeid = {
0,
};
if ((!ms) || (!xlator) || (!opts))
return -1;
gf_uuid_clear(volumeid);
if (gf_nfs_dvm_off(nfs_state(ms->nfsx)))
goto no_dvm;
ret = snprintf(searchstr, 1024, "nfs3.%s.volume-id", xlator->name);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SNPRINTF_FAIL,
"snprintf failed");
ret = -1;
goto err;
}
if (dict_get(opts, searchstr)) {
ret = dict_get_str(opts, searchstr, &optstr);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED,
"Failed to read "
"option: %s",
searchstr);
ret = -1;
goto err;
}
} else {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_VOLID_MISSING,
"DVM is on but volume-id not "
"given for volume: %s",
xlator->name);
ret = -1;
goto err;
}
if (optstr) {
ret = gf_uuid_parse(optstr, volumeid);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_PARSE_VOL_UUID_FAIL,
"Failed to parse "
"volume UUID");
ret = -1;
goto err;
}
}
no_dvm:
ret = snprintf(searchstr, 1024, "nfs3.%s.export-dir", xlator->name);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SNPRINTF_FAIL,
"snprintf failed");
ret = -1;
goto err;
}
if (dict_get(opts, searchstr)) {
ret = dict_get_str(opts, searchstr, &optstr);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED,
"Failed to read "
"option: %s",
searchstr);
ret = -1;
goto err;
}
ret = __mnt3_init_volume_direxports(ms, xlator, optstr, volumeid);
if (ret == -1) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_DIR_EXP_SETUP_FAIL,
"Dir export "
"setup failed for volume: %s",
xlator->name);
goto err;
}
}
if (ms->export_volumes) {
newexp = mnt3_init_export_ent(ms, xlator, NULL, volumeid);
if (!newexp) {
ret = -1;
goto err;
}
list_add_tail(&newexp->explist, &ms->exportlist);
}
ret = 0;
err:
return ret;
}
int
__mnt3_init_volume_export(struct mount3_state *ms, dict_t *opts)
{
int ret = -1;
char *optstr = NULL;
/* On by default. */
gf_boolean_t boolt = _gf_true;
if ((!ms) || (!opts))
return -1;
if (!dict_get(opts, "nfs3.export-volumes")) {
ret = 0;
goto err;
}
ret = dict_get_str(opts, "nfs3.export-volumes", &optstr);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED,
"Failed to read option: nfs3.export-volumes");
ret = -1;
goto err;
}
ret = gf_string2boolean(optstr, &boolt);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_STR2BOOL_FAIL,
"Failed to convert string to boolean");
}
err:
if (boolt == _gf_false) {
gf_msg_trace(GF_MNT, 0, "Volume exports disabled");
ms->export_volumes = 0;
} else {
gf_msg_trace(GF_MNT, 0, "Volume exports enabled");
ms->export_volumes = 1;
}
return ret;
}
int
__mnt3_init_dir_export(struct mount3_state *ms, dict_t *opts)
{
int ret = -1;
char *optstr = NULL;
/* On by default. */
gf_boolean_t boolt = _gf_true;
if ((!ms) || (!opts))
return -1;
if (!dict_get(opts, "nfs3.export-dirs")) {
ret = 0;
goto err;
}
ret = dict_get_str(opts, "nfs3.export-dirs", &optstr);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_DICT_GET_FAILED,
"Failed to read option: nfs3.export-dirs");
ret = -1;
goto err;
}
ret = gf_string2boolean(optstr, &boolt);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_STR2BOOL_FAIL,
"Failed to convert string to boolean");
}
err:
if (boolt == _gf_false) {
gf_msg_trace(GF_MNT, 0, "Dir exports disabled");
ms->export_dirs = 0;
} else {
gf_msg_trace(GF_MNT, 0, "Dir exports enabled");
ms->export_dirs = 1;
}
return ret;
}
int
mnt3_init_options(struct mount3_state *ms, dict_t *options)
{
xlator_list_t *volentry = NULL;
int ret = -1;
if ((!ms) || (!options))
return -1;
__mnt3_init_volume_export(ms, options);
__mnt3_init_dir_export(ms, options);
volentry = ms->nfsx->children;
while (volentry) {
gf_msg_trace(GF_MNT, 0, "Initing options for: %s",
volentry->xlator->name);
ret = __mnt3_init_volume(ms, options, volentry->xlator);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_VOL_INIT_FAIL,
"Volume init failed");
goto err;
}
volentry = volentry->next;
}
ret = 0;
err:
return ret;
}
struct mount3_state *
mnt3_init_state(xlator_t *nfsx)
{
struct mount3_state *ms = NULL;
int ret = -1;
if (!nfsx)
return NULL;
ms = GF_CALLOC(1, sizeof(*ms), gf_nfs_mt_mount3_state);
if (!ms) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Memory allocation failed");
return NULL;
}
ms->iobpool = nfsx->ctx->iobuf_pool;
ms->nfsx = nfsx;
INIT_LIST_HEAD(&ms->exportlist);
ret = mnt3_init_options(ms, nfsx->options);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_OPT_INIT_FAIL,
"Options init failed");
return NULL;
}
INIT_LIST_HEAD(&ms->mountlist);
LOCK_INIT(&ms->mountlock);
return ms;
}
int
mount_init_state(xlator_t *nfsx)
{
int ret = -1;
struct nfs_state *nfs = NULL;
if (!nfsx)
goto out;
nfs = (struct nfs_state *)nfs_state(nfsx);
/*Maintaining global state for MOUNT1 and MOUNT3*/
nfs->mstate = mnt3_init_state(nfsx);
if (!nfs->mstate) {
gf_msg(GF_NFS, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to allocate mount state");
goto out;
}
ret = 0;
out:
return ret;
}
rpcsvc_actor_t mnt3svc_actors[MOUNT3_PROC_COUNT] = {
{"NULL", MOUNT3_NULL, mnt3svc_null, NULL, 0, DRC_NA},
{"MNT", MOUNT3_MNT, mnt3svc_mnt, NULL, 0, DRC_NA},
{"DUMP", MOUNT3_DUMP, mnt3svc_dump, NULL, 0, DRC_NA},
{"UMNT", MOUNT3_UMNT, mnt3svc_umnt, NULL, 0, DRC_NA},
{"UMNTALL", MOUNT3_UMNTALL, mnt3svc_umntall, NULL, 0, DRC_NA},
{"EXPORT", MOUNT3_EXPORT, mnt3svc_export, NULL, 0, DRC_NA}};
/* Static init parts are assigned here, dynamic ones are done in
* mnt3svc_init and mnt3_init_state.
* Making MOUNT3 a synctask so that the blocking DNS calls during rpc auth
* gets offloaded to syncenv, keeping the main/poll thread unblocked
*/
rpcsvc_program_t mnt3prog = {
.progname = "MOUNT3",
.prognum = MOUNT_PROGRAM,
.progver = MOUNT_V3,
.progport = GF_MOUNTV3_PORT,
.actors = mnt3svc_actors,
.numactors = MOUNT3_PROC_COUNT,
.min_auth = AUTH_NULL,
.synctask = _gf_true,
};
/**
* __mnt3_mounted_exports_walk -- Walk through the mounted export directories
* and unmount the directories that are no
* longer authorized to be mounted.
* @dict: The dict to walk
* @key : The key we are on
* @val : The value associated with that key
* @tmp : Additional params (pointer to an auth params struct passed here)
*
*/
int
__mnt3_mounted_exports_walk(dict_t *dict, char *key, data_t *val, void *tmp)
{
char *path = NULL;
char *host_addr_ip = NULL;
char *host_addr_fqdn = NULL;
char *keydup = NULL;
char *colon = NULL;
struct mnt3_auth_params *auth_params = NULL;
int ret = 0;
int auth_status_code = 0;
gf_msg_trace(GF_MNT, 0, "Checking if key %s is authorized.", key);
auth_params = (struct mnt3_auth_params *)tmp;
/* Since we haven't obtained a lock around the mount dict
* here, we want to duplicate the key and then process it.
* Otherwise we would potentially have a race condition
* by modifying the key in the dict when other threads
* are accessing it.
*/
keydup = strdupa(key);
colon = strchr(keydup, ':');
if (!colon)
return 0;
*colon = '\0';
path = alloca(strlen(keydup) + 2);
snprintf(path, strlen(keydup) + 2, "/%s", keydup);
/* Host is one character after ':' */
host_addr_ip = colon + 1;
/* Check if the IP is authorized */
auth_status_code = mnt3_auth_host(auth_params, host_addr_ip, NULL, path,
FALSE, NULL);
if (auth_status_code == 0) {
goto out;
}
ret = gf_get_hostname_from_ip(host_addr_ip, &host_addr_fqdn);
if (ret != 0) {
gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR,
"Authorization failed for IP [%s], but name "
"resolution also failed!",
host_addr_ip);
goto unmount;
}
/* If not, check if the FQDN is authorized */
gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR,
"Authorization failed for IP [%s], attempting to"
" auth hostname [%s]...",
host_addr_ip, host_addr_fqdn);
auth_status_code = mnt3_auth_host(auth_params, host_addr_fqdn, NULL, path,
FALSE, NULL);
if (auth_status_code == 0) {
gf_msg(GF_MNT, GF_LOG_DEBUG, 0, NFS_MSG_AUTH_ERROR,
"Authorization succeeded for "
"Client [IP=%s, Hostname=%s].",
host_addr_ip, host_addr_fqdn);
goto out;
}
unmount:
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_AUTH_ERROR,
"Client [IP=%s, Hostname=%s] not authorized for this mount. "
"Unmounting!",
host_addr_ip, host_addr_fqdn);
mnt3svc_umount(auth_params->ms, path, host_addr_ip);
out:
GF_FREE(host_addr_fqdn);
return 0;
}
/**
* _mnt3_invalidate_old_mounts -- Calls __mnt3_mounted_exports_walk which checks
* checks if hosts are authorized to be mounted
* and umounts them.
*
* @ms: The mountstate for this service that holds all the information we need
*
*/
void
_mnt3_invalidate_old_mounts(struct mount3_state *ms)
{
gf_msg_debug(GF_MNT, 0, "Invalidating old mounts ...");
dict_foreach(ms->mountdict, __mnt3_mounted_exports_walk, ms->auth_params);
}
/**
* _mnt3_has_file_changed -- Checks if a file has changed on disk
*
* @path: The path of the file on disk
* @oldmtime: The previous mtime of the file
*
* @return: file changed: TRUE
* otherwise : FALSE
*
* Uses get_file_mtime () in common-utils.c
*/
gf_boolean_t
_mnt3_has_file_changed(const char *path, time_t *oldmtime)
{
gf_boolean_t changed = _gf_false;
time_t mtime = {0};
int ret = 0;
GF_VALIDATE_OR_GOTO(GF_MNT, path, out);
GF_VALIDATE_OR_GOTO(GF_MNT, oldmtime, out);
ret = get_file_mtime(path, &mtime);
if (ret < 0)
goto out;
if (mtime != *oldmtime) {
changed = _gf_true;
*oldmtime = mtime;
}
out:
return changed;
}
/**
* _mnt_auth_param_refresh_thread - Started using pthread_create () in
* mnt3svc_init (). Reloads exports/netgroups
* files from disk and sets the auth params
* structure in the mount state to reflect
* any changes from disk.
* @argv: Unused argument
* @return: Always returns NULL
*/
void *
_mnt3_auth_param_refresh_thread(void *argv)
{
struct mount3_state *mstate = (struct mount3_state *)argv;
char *exp_file_path = NULL;
char *ng_file_path = NULL;
size_t nbytes = 0;
time_t exp_time = 0;
time_t ng_time = 0;
gf_boolean_t any_file_changed = _gf_false;
int ret = 0;
nbytes = strlen(exports_file_path) + 1;
exp_file_path = alloca(nbytes);
snprintf(exp_file_path, nbytes, "%s", exports_file_path);
nbytes = strlen(netgroups_file_path) + 1;
ng_file_path = alloca(nbytes);
snprintf(ng_file_path, nbytes, "%s", netgroups_file_path);
/* Set the initial timestamps to avoid reloading right after
* mnt3svc_init () spawns this thread */
get_file_mtime(exp_file_path, &exp_time);
get_file_mtime(ng_file_path, &ng_time);
while (_gf_true) {
if (mstate->stop_refresh)
break;
any_file_changed = _gf_false;
/* Sleep before checking the file again */
sleep(mstate->nfs->auth_refresh_time_secs);
if (_mnt3_has_file_changed(exp_file_path, &exp_time)) {
gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_UPDATING_EXP,
"File %s changed, updating exports,", exp_file_path);
ret = mnt3_auth_set_exports_auth(mstate->auth_params,
exp_file_path);
if (ret)
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL,
"Failed to set export auth params.");
else
any_file_changed = _gf_true;
}
if (_mnt3_has_file_changed(ng_file_path, &ng_time)) {
gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_UPDATING_NET_GRP,
"File %s changed,"
"updating netgroups",
ng_file_path);
ret = mnt3_auth_set_netgroups_auth(mstate->auth_params,
ng_file_path);
if (ret)
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SET_NET_GRP_FAIL,
"Failed to set netgroup auth params.");
else
any_file_changed = _gf_true;
}
/* If no files changed, go back to sleep */
if (!any_file_changed)
continue;
gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_PURGING_AUTH_CACHE,
"Purging auth cache.");
auth_cache_purge(mstate->authcache);
/* Walk through mounts that are no longer authorized
* and unmount them on the server side. This will
* cause subsequent file ops to fail with access denied.
*/
_mnt3_invalidate_old_mounts(mstate);
}
return NULL;
}
/**
* _mnt3_init_auth_params -- Initialize authentication parameters by allocating
* the struct and setting the exports & netgroups
* files as parameters.
*
* @mstate : The mount state we are going to set the auth parameters in it.
*
* @return : success: 0 for success
* failure: -EINVAL for bad args, -ENOMEM for allocation errors, < 0
* for other errors (parsing the files, etc.) These are
* bubbled up from the functions we call to set the params.
*/
int
_mnt3_init_auth_params(struct mount3_state *mstate)
{
int ret = -EINVAL;
char *exp_file_path = NULL;
char *ng_file_path = NULL;
size_t nbytes = 0;
GF_VALIDATE_OR_GOTO(GF_MNT, mstate, out);
mstate->auth_params = mnt3_auth_params_init(mstate);
if (!mstate->auth_params) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to init mount auth params.");
ret = -ENOMEM;
goto out;
}
nbytes = strlen(exports_file_path) + 1;
exp_file_path = alloca(nbytes);
snprintf(exp_file_path, nbytes, "%s", exports_file_path);
ret = mnt3_auth_set_exports_auth(mstate->auth_params, exp_file_path);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL,
"Failed to set export auth params.");
goto out;
}
nbytes = strlen(netgroups_file_path) + 1;
ng_file_path = alloca(nbytes);
snprintf(ng_file_path, nbytes, "%s", netgroups_file_path);
ret = mnt3_auth_set_netgroups_auth(mstate->auth_params, ng_file_path);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_SET_EXP_AUTH_PARAM_FAIL,
"Failed to set netgroup auth params.");
goto out;
}
ret = 0;
out:
return ret;
}
/**
* mnt3svc_deinit -- Function called by the nfs translator to cleanup all state
*
* @nfsx : The NFS translator used to perform the cleanup
* This structure holds all the pointers to memory that we need to free
* as well as the threads that have been started.
*/
void
mnt3svc_deinit(xlator_t *nfsx)
{
struct mount3_state *mstate = NULL;
struct nfs_state *nfs = NULL;
if (!nfsx || !nfsx->private)
return;
nfs = (struct nfs_state *)nfsx->private;
mstate = (struct mount3_state *)nfs->mstate;
if (nfs->refresh_auth) {
/* Mark as true and wait for thread to exit */
mstate->stop_refresh = _gf_true;
pthread_join(mstate->auth_refresh_thread, NULL);
}
if (nfs->exports_auth)
mnt3_auth_params_deinit(mstate->auth_params);
/* Unmount everything and clear mountdict */
LOCK(&mstate->mountlock);
{
__mnt3svc_umountall(mstate);
dict_unref(mstate->mountdict);
}
UNLOCK(&mstate->mountlock);
}
rpcsvc_program_t *
mnt3svc_init(xlator_t *nfsx)
{
struct mount3_state *mstate = NULL;
struct nfs_state *nfs = NULL;
dict_t *options = NULL;
char *portstr = NULL;
int ret = -1;
pthread_t udp_thread;
if (!nfsx || !nfsx->private)
return NULL;
nfs = (struct nfs_state *)nfsx->private;
gf_msg_debug(GF_MNT, 0, "Initing Mount v3 state");
mstate = (struct mount3_state *)nfs->mstate;
if (!mstate) {
gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_MNT_STATE_INIT_FAIL,
"Mount v3 state init failed");
goto err;
}
mstate->nfs = nfs;
mstate->mountdict = dict_new();
if (!mstate->mountdict) {
gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
"Failed to setup mount dict. Allocation error.");
goto err;
}
if (nfs->exports_auth) {
ret = _mnt3_init_auth_params(mstate);
if (ret < 0)
goto err;
mstate->authcache = auth_cache_init(nfs->auth_cache_ttl_sec);
if (!mstate->authcache) {
ret = -ENOMEM;
goto err;
}
mstate->stop_refresh = _gf_false; /* Allow thread to run */
ret = gf_thread_create(&mstate->auth_refresh_thread, NULL,
_mnt3_auth_param_refresh_thread, mstate,
"nfsauth");
if (ret) {
gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Thread creation failed");
}
} else
gf_msg(GF_MNT, GF_LOG_INFO, 0, NFS_MSG_EXP_AUTH_DISABLED,
"Exports auth has been disabled!");
mnt3prog.private = mstate;
options = dict_new();
ret = gf_asprintf(&portstr, "%d", GF_MOUNTV3_PORT);
if (ret == -1)
goto err;
ret = dict_set_dynstr(options, "transport.socket.listen-port", portstr);
if (ret == -1)
goto err;
ret = dict_set_str(options, "transport-type", "socket");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
if (nfs->allow_insecure) {
ret = dict_set_str(options, "rpc-auth-allow-insecure", "on");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
ret = dict_set_str(options, "rpc-auth.ports.insecure", "on");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
}
ret = rpcsvc_create_listeners(nfs->rpcsvc, options, nfsx->name);
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL,
"Unable to create listeners");
dict_unref(options);
goto err;
}
if (nfs->mount_udp) {
ret = gf_thread_create(&udp_thread, NULL, mount3udp_thread, nfsx,
"nfsudp");
if (ret) {
gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Thread creation failed");
}
}
return &mnt3prog;
err:
return NULL;
}
rpcsvc_actor_t mnt1svc_actors[MOUNT1_PROC_COUNT] = {
{"NULL", MOUNT1_NULL, mnt3svc_null, NULL, 0, DRC_NA},
{"MNT", MOUNT1_MNT, NULL, NULL, 0, DRC_NA},
{"DUMP", MOUNT1_DUMP, mnt3svc_dump, NULL, 0, DRC_NA},
{"UMNT", MOUNT1_UMNT, mnt3svc_umnt, NULL, 0, DRC_NA},
{"UMNTALL", MOUNT1_UMNTALL, NULL, NULL, 0, DRC_NA},
{"EXPORT", MOUNT1_EXPORT, mnt3svc_export, NULL, 0, DRC_NA}};
rpcsvc_program_t mnt1prog = {
.progname = "MOUNT1",
.prognum = MOUNT_PROGRAM,
.progver = MOUNT_V1,
.progport = GF_MOUNTV1_PORT,
.actors = mnt1svc_actors,
.numactors = MOUNT1_PROC_COUNT,
.min_auth = AUTH_NULL,
.synctask = _gf_true,
};
rpcsvc_program_t *
mnt1svc_init(xlator_t *nfsx)
{
struct mount3_state *mstate = NULL;
struct nfs_state *nfs = NULL;
dict_t *options = NULL;
char *portstr = NULL;
int ret = -1;
if (!nfsx || !nfsx->private)
return NULL;
nfs = (struct nfs_state *)nfsx->private;
gf_msg_debug(GF_MNT, GF_LOG_DEBUG, "Initing Mount v1 state");
mstate = (struct mount3_state *)nfs->mstate;
if (!mstate) {
gf_msg(GF_MNT, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_INIT_FAIL,
"Mount v3 state init failed");
goto err;
}
mnt1prog.private = mstate;
options = dict_new();
ret = gf_asprintf(&portstr, "%d", GF_MOUNTV1_PORT);
if (ret == -1)
goto err;
ret = dict_set_dynstr(options, "transport.socket.listen-port", portstr);
if (ret == -1)
goto err;
ret = dict_set_str(options, "transport-type", "socket");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
if (nfs->allow_insecure) {
ret = dict_set_str(options, "rpc-auth-allow-insecure", "on");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
ret = dict_set_str(options, "rpc-auth.ports.insecure", "on");
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
"dict_set_str error");
goto err;
}
}
#ifdef IPV6_DEFAULT
ret = dict_set_str(options, "transport.address-family", "inet6");
if (ret == -1) {
gf_log(GF_NFS, GF_LOG_ERROR,
"dict_set_str error when trying to enable ipv6");
goto err;
}
#endif
ret = rpcsvc_create_listeners(nfs->rpcsvc, options, nfsx->name);
if (ret == -1) {
gf_msg(GF_NFS, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL,
"Unable to create listeners");
dict_unref(options);
goto err;
}
return &mnt1prog;
err:
return NULL;
}
int
mount_reconfigure_state(xlator_t *nfsx, dict_t *options)
{
int ret = -1;
struct nfs_state *nfs = NULL;
struct mount3_state *ms = NULL;
struct mnt3_export *exp = NULL;
struct mnt3_export *texp = NULL;
if ((!nfsx) || (!options))
return (-1);
nfs = (struct nfs_state *)nfs_state(nfsx);
if (!nfs)
return (-1);
ms = nfs->mstate;
if (!ms)
return (-1);
/*
* Free() up the old export list. mnt3_init_options() will
* rebuild the export list from scratch. Do it with locking
* to avoid unnecessary race conditions.
*/
LOCK(&ms->mountlock);
list_for_each_entry_safe(exp, texp, &ms->exportlist, explist)
{
list_del(&exp->explist);
mnt3_export_free(exp);
}
ret = mnt3_init_options(ms, options);
UNLOCK(&ms->mountlock);
if (ret < 0) {
gf_msg(GF_MNT, GF_LOG_ERROR, ret, NFS_MSG_RECONF_FAIL,
"Options reconfigure failed");
return (-1);
}
return (0);
}