blob: b7e478911f7ba94dda42b89a23e0e79ed50ecbe4 [file] [log] [blame]
/*
* Copyright CEA/DAM/DIF (2008)
* contributeur : Philippe DENIEL philippe.deniel@cea.fr
* Thomas LEIBOVICI thomas.leibovici@cea.fr
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* ---------------------------------------
*/
/**
* @file exports.c
* @brief Export parsing and management
*/
#include "config.h"
#include "cidr.h"
#include "log.h"
#include "fsal.h"
#include "nfs_core.h"
#include "nfs_file_handle.h"
#include "nfs_exports.h"
#include "nfs_ip_stats.h"
#include "nfs_proto_functions.h"
#include "nfs_dupreq.h"
#include "config_parsing.h"
#include "common_utils.h"
#include <stdlib.h>
#include <fnmatch.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include "export_mgr.h"
#include "fsal_up.h"
#include "sal_functions.h"
#include "pnfs_utils.h"
#include "netgroup_cache.h"
#include "mdcache.h"
/**
* @brief Protect EXPORT_DEFAULTS structure for dynamic update.
*
* If an export->lock is also held by the code, this lock MUST be
* taken AFTER the export->lock to avoid ABBA deadlock.
*
*/
pthread_rwlock_t export_opt_lock = PTHREAD_RWLOCK_INITIALIZER;
#define GLOBAL_EXPORT_PERMS_INITIALIZER \
.def.anonymous_uid = ANON_UID, \
.def.anonymous_gid = ANON_GID, \
/* Note: Access_Type defaults to None on purpose */ \
.def.options = EXPORT_OPTION_ROOT_SQUASH | \
EXPORT_OPTION_NO_ACCESS | \
EXPORT_OPTION_AUTH_DEFAULTS | \
EXPORT_OPTION_PROTO_DEFAULTS | \
EXPORT_OPTION_XPORT_DEFAULTS | \
EXPORT_OPTION_NO_DELEGATIONS, \
.def.set = UINT32_MAX, \
.expire_time_attr = 60,
struct global_export_perms export_opt = {
GLOBAL_EXPORT_PERMS_INITIALIZER
};
/* A second copy used in configuration, so we can atomically update the
* primary set.
*/
struct global_export_perms export_opt_cfg = {
GLOBAL_EXPORT_PERMS_INITIALIZER
};
static void FreeClientList(struct glist_head *clients);
static int StrExportOptions(struct display_buffer *dspbuf,
struct export_perms *p_perms)
{
int b_left = display_start(dspbuf);
if (b_left <= 0)
return b_left;
b_left = display_printf(dspbuf, "options=%08 "PRIx32, p_perms->options);
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_SQUASH_TYPES) != 0) {
if ((p_perms->options & EXPORT_OPTION_ROOT_SQUASH) != 0)
b_left = display_cat(dspbuf, "root_squash ");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_ROOT_ID_SQUASH) != 0)
b_left = display_cat(dspbuf, "root_id_squash");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_ALL_ANONYMOUS) != 0)
b_left = display_cat(dspbuf, "all_squash ");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_SQUASH_TYPES) == 0)
b_left = display_cat(dspbuf, "no_root_squash");
} else
b_left = display_cat(dspbuf, " ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_ACCESS_MASK) != 0) {
if ((p_perms->options & EXPORT_OPTION_READ_ACCESS) != 0)
b_left = display_cat(dspbuf, ", R");
else
b_left = display_cat(dspbuf, ", -");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_WRITE_ACCESS) != 0)
b_left = display_cat(dspbuf, "W");
else
b_left = display_cat(dspbuf, "-");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_MD_READ_ACCESS) != 0)
b_left = display_cat(dspbuf, "r");
else
b_left = display_cat(dspbuf, "-");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_MD_WRITE_ACCESS) != 0)
b_left = display_cat(dspbuf, "w");
else
b_left = display_cat(dspbuf, "-");
} else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_PROTOCOLS) != 0) {
if ((p_perms->options & EXPORT_OPTION_NFSV3) != 0)
b_left = display_cat(dspbuf, ", 3");
else
b_left = display_cat(dspbuf, ", -");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_NFSV4) != 0)
b_left = display_cat(dspbuf, "4");
else
b_left = display_cat(dspbuf, "-");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_9P) != 0)
b_left = display_cat(dspbuf, "9");
else
b_left = display_cat(dspbuf, "-");
} else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_TRANSPORTS) != 0) {
if ((p_perms->options & EXPORT_OPTION_UDP) != 0)
b_left = display_cat(dspbuf, ", UDP");
else
b_left = display_cat(dspbuf, ", ---");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_TCP) != 0)
b_left = display_cat(dspbuf, ", TCP");
else
b_left = display_cat(dspbuf, ", ---");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_RDMA) != 0)
b_left = display_cat(dspbuf, ", RDMA");
else
b_left = display_cat(dspbuf, ", ----");
} else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_MANAGE_GIDS) == 0)
b_left = display_cat(dspbuf, ", ");
else if ((p_perms->options & EXPORT_OPTION_MANAGE_GIDS) != 0)
b_left = display_cat(dspbuf, ", Manage_Gids ");
else
b_left = display_cat(dspbuf, ", No Manage_Gids");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_DELEGATIONS) != 0) {
if ((p_perms->options & EXPORT_OPTION_READ_DELEG) != 0)
b_left = display_cat(dspbuf, ", R");
else
b_left = display_cat(dspbuf, ", -");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_WRITE_DELEG) != 0)
b_left = display_cat(dspbuf, "W Deleg");
else
b_left = display_cat(dspbuf, "- Deleg");
} else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_ANON_UID_SET) != 0)
b_left = display_printf(dspbuf, ", anon_uid=%6d",
(int)p_perms->anonymous_uid);
else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_ANON_GID_SET) != 0)
b_left = display_printf(dspbuf, ", anon_gid=%6d",
(int)p_perms->anonymous_gid);
else
b_left = display_cat(dspbuf, ", ");
if (b_left <= 0)
return b_left;
if ((p_perms->set & EXPORT_OPTION_AUTH_TYPES) != 0) {
if ((p_perms->options & EXPORT_OPTION_AUTH_NONE) != 0)
b_left = display_cat(dspbuf, ", none");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_AUTH_UNIX) != 0)
b_left = display_cat(dspbuf, ", sys");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_NONE) != 0)
b_left = display_cat(dspbuf, ", krb5");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_INTG) != 0)
b_left = display_cat(dspbuf, ", krb5i");
if (b_left <= 0)
return b_left;
if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_PRIV) != 0)
b_left = display_cat(dspbuf, ", krb5p");
}
return b_left;
}
static char *client_types[] = {
[PROTO_CLIENT] = "PROTO_CLIENT",
[HOSTIF_CLIENT] = "HOSTIF_CLIENT",
[NETWORK_CLIENT] = "NETWORK_CLIENT",
[NETGROUP_CLIENT] = "NETGROUP_CLIENT",
[WILDCARDHOST_CLIENT] = "WILDCARDHOST_CLIENT",
[GSSPRINCIPAL_CLIENT] = "GSSPRINCIPAL_CLIENT",
[HOSTIF_CLIENT_V6] = "HOSTIF_CLIENT_V6",
[MATCH_ANY_CLIENT] = "MATCH_ANY_CLIENT",
[BAD_CLIENT] = "BAD_CLIENT"
};
void LogClientListEntry(log_levels_t level,
log_components_t component,
int line,
char *func,
char *tag,
exportlist_client_entry_t *entry)
{
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
char addr[INET6_ADDRSTRLEN];
char *paddr = addr;
char *client_type;
if (!isLevel(component, level))
return;
if (entry->type > BAD_CLIENT) {
sprintf(paddr, "0x%08x", entry->type);
client_type = "UNKNOWN_CLIENT_TYPE";
} else {
client_type = client_types[entry->type];
}
(void) StrExportOptions(&dspbuf, &entry->client_perms);
switch (entry->type) {
case HOSTIF_CLIENT:
if (inet_ntop(AF_INET,
&entry->client.hostif.clientaddr,
addr,
sizeof(addr)) == NULL) {
paddr = "Invalid Host address";
}
break;
case NETWORK_CLIENT:
if (inet_ntop(AF_INET,
&entry->client.network.netaddr,
addr,
sizeof(addr)) == NULL) {
paddr = "Invalid Network address";
}
break;
case NETGROUP_CLIENT:
paddr = entry->client.netgroup.netgroupname;
break;
case WILDCARDHOST_CLIENT:
paddr = entry->client.wildcard.wildcard;
break;
case GSSPRINCIPAL_CLIENT:
paddr = entry->client.gssprinc.princname;
break;
case HOSTIF_CLIENT_V6:
if (inet_ntop(AF_INET6,
&entry->client.hostif.clientaddr6,
addr,
sizeof(addr)) == NULL) {
paddr = "Invalid Host address";
}
break;
case MATCH_ANY_CLIENT:
paddr = "*";
break;
case PROTO_CLIENT:
case BAD_CLIENT:
paddr = "<unknown>";
break;
}
DisplayLogComponentLevel(component, (char *) __FILE__, line, func,
level, "%s%p %s: %s (%s)",
tag, entry, client_type, paddr, perms);
}
static void display_clients(struct gsh_export *export)
{
struct glist_head *glist;
PTHREAD_RWLOCK_rdlock(&export->lock);
glist_for_each(glist, &export->clients) {
exportlist_client_entry_t *client;
client = glist_entry(glist, exportlist_client_entry_t,
cle_list);
LogClientListEntry(NIV_MID_DEBUG,
COMPONENT_EXPORT,
__LINE__,
(char *) __func__,
"",
client);
}
PTHREAD_RWLOCK_unlock(&export->lock);
}
/**
* @brief Expand the client name token into one or more client entries
*
* @param client_list[IN] the client list this gets linked to (in tail order)
* @param client_tok [IN] the name string. We modify it.
* @param type_hint [IN] type hint from parser for client_tok
* @param perms [IN] pointer to the permissions to copy into each
* @param cnode [IN] opaque pointer needed for config_proc_error()
* @param err_type [OUT] error handling ref
*
* @returns 0 on success, error count on failure
*/
static int add_client(struct glist_head *client_list,
const char *client_tok,
enum term_type type_hint,
struct export_perms *perms,
void *cnode,
struct config_error_type *err_type)
{
struct exportlist_client_entry__ *cli;
int errcnt = 0;
struct addrinfo *info;
CIDR *cidr;
uint32_t addr;
int rc;
cli = gsh_calloc(1, sizeof(struct exportlist_client_entry__));
glist_init(&cli->cle_list);
switch (type_hint) {
case TERM_V4_ANY:
cli->type = MATCH_ANY_CLIENT;
break;
case TERM_NETGROUP:
if (strlen(client_tok) > MAXHOSTNAMELEN) {
config_proc_error(cnode, err_type,
"netgroup (%s) name too long",
client_tok);
err_type->invalid = true;
errcnt++;
goto out;
}
cli->client.netgroup.netgroupname = gsh_strdup(client_tok + 1);
cli->type = NETGROUP_CLIENT;
break;
case TERM_V4CIDR: /* this needs to be migrated to libcidr! (no v6) */
cidr = cidr_from_str(client_tok);
if (cidr == NULL) {
config_proc_error(cnode, err_type,
"Expected a IPv4 CIDR address, got (%s)",
client_tok);
err_type->invalid = true;
errcnt++;
goto out;
}
memcpy(&addr, &cidr->addr[12], 4);
cli->client.network.netaddr = ntohl(addr);
memcpy(&addr, &cidr->mask[12], 4);
cli->client.network.netmask = ntohl(addr);
cidr_free(cidr);
cli->type = NETWORK_CLIENT;
break;
case TERM_REGEX:
if (strlen(client_tok) > MAXHOSTNAMELEN) {
config_proc_error(cnode, err_type,
"Wildcard client (%s) name too long",
client_tok);
err_type->invalid = true;
errcnt++;
goto out;
}
cli->client.wildcard.wildcard = gsh_strdup(client_tok);
cli->type = WILDCARDHOST_CLIENT;
break;
case TERM_V4ADDR:
rc = inet_pton(AF_INET, client_tok,
&cli->client.hostif.clientaddr);
assert(rc == 1); /* this had better be grok'd by now! */
cli->type = HOSTIF_CLIENT;
break;
case TERM_V6ADDR:
rc = inet_pton(AF_INET6, client_tok,
&cli->client.hostif.clientaddr6);
assert(rc == 1); /* this had better be grok'd by now! */
cli->type = HOSTIF_CLIENT_V6;
break;
case TERM_TOKEN: /* only dns names now. */
rc = getaddrinfo(client_tok, NULL, NULL, &info);
if (rc == 0) {
struct addrinfo *ap, *ap_last = NULL;
struct in_addr in_addr_last;
struct in6_addr in6_addr_last;
for (ap = info; ap != NULL; ap = ap->ai_next) {
LogFullDebug(COMPONENT_CONFIG,
"flags=%d family=%d socktype=%d protocol=%d addrlen=%d name=%s",
ap->ai_flags,
ap->ai_family,
ap->ai_socktype,
ap->ai_protocol,
(int) ap->ai_addrlen,
ap->ai_canonname);
if (cli == NULL) {
cli = gsh_calloc(1,
sizeof(struct
exportlist_client_entry__));
glist_init(&cli->cle_list);
}
if (ap->ai_family == AF_INET &&
(ap->ai_socktype == SOCK_STREAM ||
ap->ai_socktype == SOCK_DGRAM)) {
struct in_addr infoaddr =
((struct sockaddr_in *)
ap->ai_addr)->sin_addr;
if (ap_last != NULL &&
ap_last->ai_family
== ap->ai_family &&
memcmp(&infoaddr,
&in_addr_last,
sizeof(struct in_addr)) == 0)
continue;
memcpy(&(cli->client.hostif.clientaddr),
&infoaddr,
sizeof(struct in_addr));
cli->type = HOSTIF_CLIENT;
ap_last = ap;
in_addr_last = infoaddr;
} else if (ap->ai_family == AF_INET6 &&
(ap->ai_socktype == SOCK_STREAM ||
ap->ai_socktype == SOCK_DGRAM)) {
struct in6_addr infoaddr =
((struct sockaddr_in6 *)
ap->ai_addr)->sin6_addr;
if (ap_last != NULL &&
ap_last->ai_family == ap->ai_family
&& !memcmp(&infoaddr,
&in6_addr_last,
sizeof(struct in6_addr)))
continue;
/* IPv6 address */
memcpy(
&(cli->client.hostif.clientaddr6),
&infoaddr,
sizeof(struct in6_addr));
cli->type = HOSTIF_CLIENT_V6;
ap_last = ap;
in6_addr_last = infoaddr;
} else
continue;
cli->client_perms = *perms;
LogClientListEntry(NIV_MID_DEBUG,
COMPONENT_CONFIG,
__LINE__,
(char *) __func__,
"",
cli);
glist_add_tail(client_list, &cli->cle_list);
cli = NULL; /* let go of it */
}
freeaddrinfo(info);
goto out;
} else {
config_proc_error(cnode, err_type,
"Client (%s)not found because %s",
client_tok, gai_strerror(rc));
err_type->bogus = true;
errcnt++;
}
break;
default:
config_proc_error(cnode, err_type,
"Expected a client, got a %s for (%s)",
config_term_desc(type_hint),
client_tok);
err_type->bogus = true;
errcnt++;
goto out;
}
cli->client_perms = *perms;
LogClientListEntry(NIV_MID_DEBUG,
COMPONENT_CONFIG,
__LINE__,
(char *) __func__,
"",
cli);
glist_add_tail(client_list, &cli->cle_list);
cli = NULL;
out:
if (cli != NULL)
gsh_free(cli);
return errcnt;
}
/**
* @brief Commit and FSAL sub-block init/commit helpers
*/
/**
* @brief Init for CLIENT sub-block of an export.
*
* Allocate one exportlist_client structure for parameter
* processing. The client_commit will allocate additional
* exportlist_client__ storage for each of its enumerated
* clients and free the initial block. We only free that
* resource here on errors.
*/
static void *client_init(void *link_mem, void *self_struct)
{
struct exportlist_client_entry__ *cli;
assert(link_mem != NULL || self_struct != NULL);
if (link_mem == NULL) {
return self_struct;
} else if (self_struct == NULL) {
cli = gsh_calloc(1, sizeof(struct exportlist_client_entry__));
glist_init(&cli->cle_list);
cli->type = PROTO_CLIENT;
return cli;
} else { /* free resources case */
cli = self_struct;
if (!glist_empty(&cli->cle_list))
FreeClientList(&cli->cle_list);
assert(glist_empty(&cli->cle_list));
gsh_free(cli);
return NULL;
}
}
/**
* @brief Commit this client block
*
* Validate "clients" token(s) and perms. We enter with a client entry
* allocated by proc_block. Since we expand the clients token both
* here and in add_client, we allocate new client entries and free
* what was passed to us rather than try and link it in.
*
* @param node [IN] the config_node **not used**
* @param link_mem [IN] the exportlist entry. add_client adds to its glist.
* @param self_struct [IN] the filled out client entry with a PROTO_CLIENT
*
* @return 0 on success, error count for failure.
*/
static int client_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
struct exportlist_client_entry__ *cli;
struct gsh_export *export;
int errcnt = 0;
export = container_of(link_mem, struct gsh_export, clients);
cli = self_struct;
assert(cli->type == PROTO_CLIENT);
if (glist_empty(&cli->cle_list)) {
LogCrit(COMPONENT_CONFIG,
"No clients specified");
err_type->invalid = true;
errcnt++;
} else {
glist_splice_tail(&export->clients, &cli->cle_list);
}
if (errcnt == 0)
client_init(link_mem, self_struct);
return errcnt;
}
/**
* @brief Clean up EXPORT path strings
*/
void clean_export_paths(struct gsh_export *export)
{
/* Some admins stuff a '/' at the end for some reason.
* chomp it so we have a /dir/path/basename to work
* with. But only if it's a non-root path starting
* with /.
*/
if (export->fullpath && export->fullpath[0] == '/') {
int pathlen;
pathlen = strlen(export->fullpath);
while ((export->fullpath[pathlen - 1] == '/') &&
(pathlen > 1))
pathlen--;
export->fullpath[pathlen] = '\0';
}
/* Remove trailing slash */
if (export->pseudopath && export->pseudopath[0] == '/') {
int pathlen;
pathlen = strlen(export->pseudopath);
while ((export->pseudopath[pathlen - 1] == '/') &&
(pathlen > 1))
pathlen--;
export->pseudopath[pathlen] = '\0';
}
}
/**
* @brief Commit a FSAL sub-block
*
* Use the Name parameter passed in via the link_mem to lookup the
* fsal. If the fsal is not loaded (yet), load it and call its init.
*
* Create an export and pass the FSAL sub-block to it so that the
* fsal method can process the rest of the parameters in the block
*/
static int fsal_cfg_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
struct fsal_export **exp_hdl = link_mem;
struct gsh_export *export =
container_of(exp_hdl, struct gsh_export, fsal_export);
struct fsal_args *fp = self_struct;
struct fsal_module *fsal;
struct root_op_context root_op_context;
uint64_t MaxRead, MaxWrite;
fsal_status_t status;
int errcnt;
/* Initialize req_ctx */
init_root_op_context(&root_op_context, export, NULL, 0, 0,
UNKNOWN_REQUEST);
errcnt = fsal_load_init(node, fp->name, &fsal, err_type);
if (errcnt > 0)
goto err;
clean_export_paths(export);
/* The handle cache (currently MDCACHE) must be at the top of the stack
* of FSALs. To achieve this, call directly into MDCACHE, passing the
* sub-FSAL's fsal_module. MDCACHE will stack itself on top of that
* FSAL, continuing down the chain. */
status = mdcache_fsal_create_export(fsal, node, err_type, &fsal_up_top);
PTHREAD_RWLOCK_rdlock(&export_opt_lock);
if ((export->options_set & EXPORT_OPTION_EXPIRE_SET) == 0)
export->expire_time_attr = export_opt.expire_time_attr;
PTHREAD_RWLOCK_unlock(&export_opt_lock);
if (FSAL_IS_ERROR(status)) {
fsal_put(fsal);
LogCrit(COMPONENT_CONFIG,
"Could not create export for (%s) to (%s)",
export->pseudopath,
export->fullpath);
err_type->export_ = true;
errcnt++;
goto err;
}
assert(root_op_context.req_ctx.fsal_export != NULL);
export->fsal_export = root_op_context.req_ctx.fsal_export;
/* We are connected up to the fsal side. Now
* validate maxread/write etc with fsal params
*/
MaxRead = export->fsal_export->
exp_ops.fs_maxread(export->fsal_export);
MaxWrite = export->fsal_export->
exp_ops.fs_maxwrite(export->fsal_export);
if (export->MaxRead > MaxRead && MaxRead != 0) {
LogInfo(COMPONENT_CONFIG,
"Readjusting MaxRead to FSAL, %" PRIu64 " -> %" PRIu64,
export->MaxRead,
MaxRead);
export->MaxRead = MaxRead;
}
if (export->MaxWrite > MaxWrite && MaxWrite != 0) {
LogInfo(COMPONENT_CONFIG,
"Readjusting MaxWrite to FSAL, %"PRIu64" -> %"PRIu64,
export->MaxWrite,
MaxWrite);
export->MaxWrite = MaxWrite;
}
err:
release_root_op_context();
return errcnt;
}
/**
* @brief Commit a FSAL sub-block for export update
*
* Use the Name parameter passed in via the link_mem to lookup the
* fsal. If the fsal is not loaded (yet), load it and call its init.
*
* Create an export and pass the FSAL sub-block to it so that the
* fsal method can process the rest of the parameters in the block
*/
static int fsal_update_cfg_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
struct fsal_export **exp_hdl = link_mem;
struct gsh_export *probe_exp;
struct gsh_export *export =
container_of(exp_hdl, struct gsh_export, fsal_export);
struct root_op_context root_op_context;
uint64_t MaxRead, MaxWrite;
/* Determine if this is actually an update */
probe_exp = get_gsh_export(export->export_id);
if (probe_exp == NULL) {
/* Export not found by ID, assume it's a new export. */
return fsal_cfg_commit(node, link_mem, self_struct, err_type);
}
/** @todo - we really should verify update of FSAL options in
* export, at the moment any changes there will be
* ignored. In fact, we can't even process the
* FSAL name...
*/
/* We have to clean the export paths so we can properly compare them
* later.
*/
clean_export_paths(export);
PTHREAD_RWLOCK_rdlock(&export_opt_lock);
if ((export->options_set & EXPORT_OPTION_EXPIRE_SET) == 0)
export->expire_time_attr = export_opt.expire_time_attr;
PTHREAD_RWLOCK_unlock(&export_opt_lock);
/* We don't assign export->fsal_export because we don't have a new
* fsal_export to later release...
*/
/* Initialize req_ctx from the probe_exp */
init_root_op_context(&root_op_context, probe_exp,
probe_exp->fsal_export, 0, 0, UNKNOWN_REQUEST);
/* Now validate maxread/write etc with fsal params based on the
* original export, which will then allow us to validate the
* possibly changed values in the new export config.
*/
MaxRead = probe_exp->fsal_export->
exp_ops.fs_maxread(probe_exp->fsal_export);
MaxWrite = probe_exp->fsal_export->
exp_ops.fs_maxwrite(probe_exp->fsal_export);
release_root_op_context();
if (export->MaxRead > MaxRead && MaxRead != 0) {
LogInfo(COMPONENT_CONFIG,
"Readjusting MaxRead to FSAL, %" PRIu64 " -> %" PRIu64,
export->MaxRead,
MaxRead);
export->MaxRead = MaxRead;
}
if (export->MaxWrite > MaxWrite && MaxWrite != 0) {
LogInfo(COMPONENT_CONFIG,
"Readjusting MaxWrite to FSAL, %"PRIu64" -> %"PRIu64,
export->MaxWrite,
MaxWrite);
export->MaxWrite = MaxWrite;
}
LogDebug(COMPONENT_CONFIG,
"Export %d FSAL config update processed",
export->export_id);
put_gsh_export(probe_exp);
return 0;
}
/**
* @brief EXPORT block handlers
*/
/**
* @brief Initialize an export block
*
* There is no link_mem init required because we are allocating
* here and doing an insert_gsh_export at the end of export_commit
* to attach it to the export manager.
*
* Use free_exportlist here because in this case, we have not
* gotten far enough to hand it over to the export manager.
*/
static void *export_init(void *link_mem, void *self_struct)
{
struct gsh_export *export;
if (self_struct == NULL) {
export = alloc_export();
return export;
} else { /* free resources case */
export = self_struct;
/* As part of create_export(), FSAL shall take
* reference to the export if it supports pNFS.
*/
if (export->has_pnfs_ds) {
assert(export->refcnt == 1);
/* export is not yet added to the export
* manager. Hence there shall not be any
* other thread racing here. So no need
* to take lock. */
export->has_pnfs_ds = false;
pnfs_ds_remove(export->export_id, true);
} else {
assert(export->refcnt == 0);
free_export(export);
}
return NULL;
}
}
static inline int strcmp_null(const char *s1, const char *s2)
{
if (s1 == s2) {
/* Both strings are NULL or both are same pointer */
return 0;
}
if (s1 == NULL) {
/* First string is NULL, consider that LESS than */
return -1;
}
if (s2 == NULL) {
/* Second string is NULL, consider that GREATER than */
return 1;
}
return strcmp(s1, s2);
}
static inline void update_atomic_fields(struct gsh_export *export,
struct gsh_export *src)
{
atomic_store_uint64_t(&export->MaxRead, src->MaxRead);
atomic_store_uint64_t(&export->MaxWrite, src->MaxWrite);
atomic_store_uint64_t(&export->PrefRead, src->PrefRead);
atomic_store_uint64_t(&export->PrefWrite, src->PrefWrite);
atomic_store_uint64_t(&export->PrefReaddir, src->PrefReaddir);
atomic_store_uint64_t(&export->MaxOffsetWrite, src->MaxOffsetWrite);
atomic_store_uint64_t(&export->MaxOffsetRead, src->MaxOffsetRead);
atomic_store_uint32_t(&export->options, src->options);
atomic_store_uint32_t(&export->options_set, src->options_set);
atomic_store_int32_t(&export->expire_time_attr, src->expire_time_attr);
}
/**
* @brief Commit an export block
*
* Validate the export level parameters. fsal and client
* parameters are already done.
*/
enum export_commit_type {
initial_export,
add_export,
update_export,
};
static int export_commit_common(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type,
enum export_commit_type commit_type)
{
struct gsh_export *export = self_struct, *probe_exp;
int errcnt = 0;
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
LogFullDebug(COMPONENT_EXPORT, "Processing %p", export);
/* validate the export now */
if (export->export_perms.options & EXPORT_OPTION_NFSV4) {
if (export->pseudopath == NULL) {
LogCrit(COMPONENT_CONFIG,
"Exporting to NFSv4 but not Pseudo path defined");
err_type->invalid = true;
errcnt++;
return errcnt;
} else if (export->export_id == 0 &&
strcmp(export->pseudopath, "/") != 0) {
LogCrit(COMPONENT_CONFIG,
"Export id 0 can only export \"/\" not (%s)",
export->pseudopath);
err_type->invalid = true;
errcnt++;
return errcnt;
}
}
if (export->pseudopath != NULL &&
export->pseudopath[0] != '/') {
LogCrit(COMPONENT_CONFIG,
"A Pseudo path must be an absolute path");
err_type->invalid = true;
errcnt++;
}
if (export->export_id == 0) {
if (export->pseudopath == NULL) {
LogCrit(COMPONENT_CONFIG,
"Pseudo path must be \"/\" for export id 0");
err_type->invalid = true;
errcnt++;
} else if (export->pseudopath[1] != '\0') {
LogCrit(COMPONENT_CONFIG,
"Pseudo path must be \"/\" for export id 0");
err_type->invalid = true;
errcnt++;
}
if ((export->export_perms.options &
EXPORT_OPTION_PROTOCOLS) != EXPORT_OPTION_NFSV4) {
LogCrit(COMPONENT_CONFIG,
"Export id 0 must indicate Protocols=4");
err_type->invalid = true;
errcnt++;
}
}
if (errcnt)
return errcnt; /* have basic errors. don't even try more... */
/* Note: need to check export->fsal_export AFTER we have checked for
* duplicate export_id. That is because an update export WILL NOT
* have fsal_export attached.
*/
probe_exp = get_gsh_export(export->export_id);
if (commit_type == update_export && probe_exp != NULL) {
/* We have an actual update case, probe_exp is the target
* to update. Check all the options that MUST match.
* Note that Path/fullpath will not be NULL, but we compare
* the same way as the other string options for code
* consistency.
*/
LogFullDebug(COMPONENT_EXPORT, "Updating %p", probe_exp);
LogMidDebug(COMPONENT_EXPORT, "Old Client List");
display_clients(probe_exp);
LogMidDebug(COMPONENT_EXPORT, "New Client List");
display_clients(export);
if (strcmp_null(export->FS_tag,
probe_exp->FS_tag) != 0) {
/* Tag does not match, currently not a candidate for
* update.
*/
LogCrit(COMPONENT_CONFIG,
"Tag for export update %d doesn't match",
export->export_id);
err_type->invalid = true;
errcnt++;
}
if (strcmp_null(export->pseudopath,
probe_exp->pseudopath) != 0) {
/* Pseudo does not match, currently not a candidate for
* update.
*/
LogCrit(COMPONENT_CONFIG,
"Pseudo for export update %d doesn't match",
export->export_id);
err_type->invalid = true;
errcnt++;
}
if (strcmp_null(export->fullpath,
probe_exp->fullpath) != 0) {
/* Path does not match, currently not a candidate for
* update.
*/
LogCrit(COMPONENT_CONFIG,
"Path for export update %d doesn't match",
export->export_id);
err_type->invalid = true;
errcnt++;
}
/* At present Filesystem_Id is not updateable, check that
* it did not change.
*/
if (probe_exp->filesystem_id.major
!= export->filesystem_id.major ||
probe_exp->filesystem_id.minor
!= export->filesystem_id.minor) {
LogCrit(COMPONENT_CONFIG,
"Filesystem_Id for export update %d doesn't match",
export->export_id);
err_type->invalid = true;
errcnt++;
}
/* We can't compare the FSAL names because we don't actually
* have an fsal_export for "export".
*/
if (errcnt > 0) {
put_gsh_export(probe_exp);
return errcnt;
}
/* Update atomic fields */
update_atomic_fields(probe_exp, export);
/* Now take lock and swap out client list and export_perms... */
PTHREAD_RWLOCK_wrlock(&probe_exp->lock);
/* Copy the export perms into the existing export. */
probe_exp->export_perms = export->export_perms;
/* Swap the client list from the new export and the existing
* export. When we then dispose of the new export, the
* old client list will also be disposed of.
*/
LogFullDebug(COMPONENT_EXPORT,
"Original clients = (%p,%p) New clients = (%p,%p)",
probe_exp->clients.next, probe_exp->clients.prev,
export->clients.next, export->clients.prev);
glist_swap_lists(&probe_exp->clients, &export->clients);
PTHREAD_RWLOCK_unlock(&probe_exp->lock);
/* We will need to dispose of the config export since we
* updated the existing export.
*/
err_type->dispose = true;
/* Release the reference to the updated export. */
put_gsh_export(probe_exp);
goto success;
}
if (commit_type == update_export) {
/* We found a new export during export update, consider it
* an add_export for the rest of configuration.
*/
commit_type = add_export;
}
if (probe_exp != NULL) {
LogDebug(COMPONENT_CONFIG,
"Export %d already exists", export->export_id);
put_gsh_export(probe_exp);
err_type->exists = true;
errcnt++;
}
/* export->fsal_export is valid iff fsal_cfg_commit succeeds.
* Config code calls export_commit even if fsal_cfg_commit fails at
* the moment, so error out here if fsal_cfg_commit failed.
*/
if (export->fsal_export == NULL) {
err_type->validate = true;
errcnt++;
return errcnt;
}
if (export->FS_tag != NULL) {
probe_exp = get_gsh_export_by_tag(export->FS_tag);
if (probe_exp != NULL) {
put_gsh_export(probe_exp);
LogCrit(COMPONENT_CONFIG,
"Tag (%s) is a duplicate",
export->FS_tag);
if (!err_type->exists)
err_type->invalid = true;
errcnt++;
}
}
if (export->pseudopath != NULL) {
probe_exp = get_gsh_export_by_pseudo(export->pseudopath, true);
if (probe_exp != NULL) {
LogCrit(COMPONENT_CONFIG,
"Pseudo path (%s) is a duplicate",
export->pseudopath);
if (!err_type->exists)
err_type->invalid = true;
errcnt++;
put_gsh_export(probe_exp);
}
}
probe_exp = get_gsh_export_by_path(export->fullpath, true);
if (probe_exp != NULL) {
if (export->pseudopath == NULL &&
export->FS_tag == NULL) {
LogCrit(COMPONENT_CONFIG,
"Duplicate path (%s) without unique tag or Pseudo path",
export->fullpath);
err_type->invalid = true;
errcnt++;
}
/* If unique Tag and/or Pseudo, there is no error, but we still
* need to release the export reference.
*/
put_gsh_export(probe_exp);
}
if (errcnt) {
if (err_type->exists && !err_type->invalid)
LogDebug(COMPONENT_CONFIG,
"Duplicate export id = %d",
export->export_id);
else
LogCrit(COMPONENT_CONFIG,
"Duplicate export id = %d",
export->export_id);
return errcnt; /* have errors. don't init or load a fsal */
}
if (!insert_gsh_export(export)) {
LogCrit(COMPONENT_CONFIG,
"Export id %d already in use.",
export->export_id);
err_type->exists = true;
errcnt++;
return errcnt;
}
/* add_export_commit shouldn't add this export to mount work as
* add_export_commit deals with creating pseudo mount directly.
* So add this export to mount work only if NFSv4 exported and
* is not a dynamically added export.
*/
if (commit_type == initial_export &&
export->export_perms.options & EXPORT_OPTION_NFSV4)
export_add_to_mount_work(export);
if (commit_type != initial_export) {
/* add_export or update_export with new export_id. */
int rc = init_export_root(export);
if (rc) {
export_revert(export);
errcnt++;
switch (rc) {
case EINVAL:
err_type->invalid = true;
break;
case EFAULT:
err_type->internal = true;
break;
default:
err_type->resource = true;
}
goto out;
}
if (!mount_gsh_export(export)) {
export_revert(export);
err_type->internal = true;
errcnt++;
goto out;
}
}
display_clients(export);
success:
(void) StrExportOptions(&dspbuf, &export->export_perms);
LogInfo(COMPONENT_CONFIG,
"Export %d %s at pseudo (%s) with path (%s) and tag (%s) perms (%s)",
export->export_id,
commit_type == update_export ? "updated" : "created",
export->pseudopath,
export->fullpath, export->FS_tag, perms);
LogInfo(COMPONENT_CONFIG,
"Export %d has %zd defined clients", export->export_id,
glist_length(&export->clients));
out:
if (commit_type != update_export) {
/* For initial or add export, insert_gsh_export gave out
* two references, a sentinel reference for the export's
* presence in the export table, and one reference for our
* use here, drop that second reference now.
*
* In the case of update_export, we already dropped the
* reference to the updated export, and this export has
* no references and will be freed by the config code.
*/
put_gsh_export(export);
}
return errcnt;
}
static int export_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
return export_commit_common(node, link_mem, self_struct, err_type,
initial_export);
}
/**
* @brief Display an export block
*
* Validate the export level parameters. fsal and client
* parameters are already done.
*/
static void export_display(const char *step, void *node,
void *link_mem, void *self_struct)
{
struct gsh_export *export = self_struct;
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
(void) StrExportOptions(&dspbuf, &export->export_perms);
LogMidDebug(COMPONENT_CONFIG,
"%s %p Export %d pseudo (%s) with path (%s) and tag (%s) perms (%s)",
step, export, export->export_id, export->pseudopath,
export->fullpath, export->FS_tag, perms);
}
/**
* @brief Commit an add export
* commit the export
* init export root and mount it in pseudo fs
*/
static int add_export_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
return export_commit_common(node, link_mem, self_struct, err_type,
add_export);
}
/**
* @brief Commit an update export
* commit the export
* init export root and mount it in pseudo fs
*/
static int update_export_commit(void *node, void *link_mem, void *self_struct,
struct config_error_type *err_type)
{
return export_commit_common(node, link_mem, self_struct, err_type,
update_export);
}
/**
* @brief Initialize an EXPORT_DEFAULTS block
*
*/
static void *export_defaults_init(void *link_mem, void *self_struct)
{
if (self_struct == NULL)
return &export_opt_cfg;
else
return NULL;
}
/**
* @brief Commit an EXPORT_DEFAULTS block
*
* Validate the export level parameters. fsal and client
* parameters are already done.
*/
static int export_defaults_commit(void *node, void *link_mem,
void *self_struct,
struct config_error_type *err_type)
{
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
(void) StrExportOptions(&dspbuf, &export_opt_cfg.conf);
LogInfo(COMPONENT_CONFIG, "Export Defaults now (%s)", perms);
/* Update under lock. */
PTHREAD_RWLOCK_wrlock(&export_opt_lock);
export_opt = export_opt_cfg;
PTHREAD_RWLOCK_unlock(&export_opt_lock);
return 0;
}
/**
* @brief Display an EXPORT_DEFAULTS block
*
* Validate the export level parameters. fsal and client
* parameters are already done.
*/
static void export_defaults_display(const char *step, void *node,
void *link_mem, void *self_struct)
{
struct export_perms *defaults = self_struct;
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
(void) StrExportOptions(&dspbuf, defaults);
LogMidDebug(COMPONENT_CONFIG,
"%s Export Defaults (%s)",
step, perms);
}
/**
* @brief Configuration processing tables for EXPORT blocks
*/
/**
* @brief Access types list for the Access_type parameter
*/
static struct config_item_list access_types[] = {
CONFIG_LIST_TOK("NONE", 0),
CONFIG_LIST_TOK("RW", (EXPORT_OPTION_RW_ACCESS |
EXPORT_OPTION_MD_ACCESS)),
CONFIG_LIST_TOK("RO", (EXPORT_OPTION_READ_ACCESS |
EXPORT_OPTION_MD_READ_ACCESS)),
CONFIG_LIST_TOK("MDONLY", EXPORT_OPTION_MD_ACCESS),
CONFIG_LIST_TOK("MDONLY_RO", EXPORT_OPTION_MD_READ_ACCESS),
CONFIG_LIST_EOL
};
/**
* @brief Protocols options list for NFS_Protocols parameter
*/
static struct config_item_list nfs_protocols[] = {
CONFIG_LIST_TOK("3", EXPORT_OPTION_NFSV3),
CONFIG_LIST_TOK("4", EXPORT_OPTION_NFSV4),
CONFIG_LIST_TOK("NFS3", EXPORT_OPTION_NFSV3),
CONFIG_LIST_TOK("NFS4", EXPORT_OPTION_NFSV4),
CONFIG_LIST_TOK("V3", EXPORT_OPTION_NFSV3),
CONFIG_LIST_TOK("V4", EXPORT_OPTION_NFSV4),
CONFIG_LIST_TOK("NFSV3", EXPORT_OPTION_NFSV3),
CONFIG_LIST_TOK("NFSV4", EXPORT_OPTION_NFSV4),
CONFIG_LIST_TOK("9P", EXPORT_OPTION_9P),
CONFIG_LIST_EOL
};
/**
* @brief Transport type options list for Transport_Protocols parameter
*/
static struct config_item_list transports[] = {
CONFIG_LIST_TOK("UDP", EXPORT_OPTION_UDP),
CONFIG_LIST_TOK("TCP", EXPORT_OPTION_TCP),
CONFIG_LIST_EOL
};
/**
* @brief Security options list for SecType parameter
*/
static struct config_item_list sec_types[] = {
CONFIG_LIST_TOK("none", EXPORT_OPTION_AUTH_NONE),
CONFIG_LIST_TOK("sys", EXPORT_OPTION_AUTH_UNIX),
CONFIG_LIST_TOK("krb5", EXPORT_OPTION_RPCSEC_GSS_NONE),
CONFIG_LIST_TOK("krb5i", EXPORT_OPTION_RPCSEC_GSS_INTG),
CONFIG_LIST_TOK("krb5p", EXPORT_OPTION_RPCSEC_GSS_PRIV),
CONFIG_LIST_EOL
};
/**
* @brief Client UID squash item list for Squash parameter
*/
static struct config_item_list squash_types[] = {
CONFIG_LIST_TOK("Root", EXPORT_OPTION_ROOT_SQUASH),
CONFIG_LIST_TOK("Root_Squash", EXPORT_OPTION_ROOT_SQUASH),
CONFIG_LIST_TOK("RootSquash", EXPORT_OPTION_ROOT_SQUASH),
CONFIG_LIST_TOK("All", EXPORT_OPTION_ALL_ANONYMOUS),
CONFIG_LIST_TOK("All_Squash", EXPORT_OPTION_ALL_ANONYMOUS),
CONFIG_LIST_TOK("AllSquash", EXPORT_OPTION_ALL_ANONYMOUS),
CONFIG_LIST_TOK("All_Anonymous", EXPORT_OPTION_ALL_ANONYMOUS),
CONFIG_LIST_TOK("AllAnonymous", EXPORT_OPTION_ALL_ANONYMOUS),
CONFIG_LIST_TOK("No_Root_Squash", EXPORT_OPTION_ROOT),
CONFIG_LIST_TOK("None", EXPORT_OPTION_ROOT),
CONFIG_LIST_TOK("NoIdSquash", EXPORT_OPTION_ROOT),
CONFIG_LIST_TOK("RootId", EXPORT_OPTION_ROOT_ID_SQUASH),
CONFIG_LIST_TOK("Root_Id_Squash", EXPORT_OPTION_ROOT_ID_SQUASH),
CONFIG_LIST_TOK("RootIdSquash", EXPORT_OPTION_ROOT_ID_SQUASH),
CONFIG_LIST_EOL
};
/**
* @brief Delegations types list for the Delegations parameter
*/
static struct config_item_list delegations[] = {
CONFIG_LIST_TOK("NONE", EXPORT_OPTION_NO_DELEGATIONS),
CONFIG_LIST_TOK("Read", EXPORT_OPTION_READ_DELEG),
CONFIG_LIST_TOK("Write", EXPORT_OPTION_WRITE_DELEG),
CONFIG_LIST_TOK("Readwrite", EXPORT_OPTION_DELEGATIONS),
CONFIG_LIST_TOK("R", EXPORT_OPTION_READ_DELEG),
CONFIG_LIST_TOK("W", EXPORT_OPTION_WRITE_DELEG),
CONFIG_LIST_TOK("RW", EXPORT_OPTION_DELEGATIONS),
CONFIG_LIST_EOL
};
struct config_item_list deleg_types[] = {
CONFIG_LIST_TOK("NONE", FSAL_OPTION_NO_DELEGATIONS),
CONFIG_LIST_TOK("Read", FSAL_OPTION_FILE_READ_DELEG),
CONFIG_LIST_TOK("Write", FSAL_OPTION_FILE_WRITE_DELEG),
CONFIG_LIST_TOK("Readwrite", FSAL_OPTION_FILE_DELEGATIONS),
CONFIG_LIST_TOK("R", FSAL_OPTION_FILE_READ_DELEG),
CONFIG_LIST_TOK("W", FSAL_OPTION_FILE_WRITE_DELEG),
CONFIG_LIST_TOK("RW", FSAL_OPTION_FILE_DELEGATIONS),
CONFIG_LIST_EOL
};
#define CONF_EXPORT_PERMS(_struct_, _perms_) \
/* Note: Access_Type defaults to None on purpose */ \
CONF_ITEM_ENUM_BITS_SET("Access_Type", \
EXPORT_OPTION_NO_ACCESS, \
EXPORT_OPTION_ACCESS_MASK, \
access_types, _struct_, _perms_.options, _perms_.set), \
CONF_ITEM_LIST_BITS_SET("Protocols", \
EXPORT_OPTION_PROTO_DEFAULTS, EXPORT_OPTION_PROTOCOLS, \
nfs_protocols, _struct_, _perms_.options, _perms_.set), \
CONF_ITEM_LIST_BITS_SET("Transports", \
EXPORT_OPTION_XPORT_DEFAULTS, EXPORT_OPTION_TRANSPORTS, \
transports, _struct_, _perms_.options, _perms_.set), \
CONF_ITEM_ANON_ID_SET("Anonymous_uid", \
ANON_UID, _struct_, _perms_.anonymous_uid, \
EXPORT_OPTION_ANON_UID_SET, _perms_.set), \
CONF_ITEM_ANON_ID_SET("Anonymous_gid", \
ANON_GID, _struct_, _perms_.anonymous_gid, \
EXPORT_OPTION_ANON_GID_SET, _perms_.set), \
CONF_ITEM_LIST_BITS_SET("SecType", \
EXPORT_OPTION_AUTH_DEFAULTS, EXPORT_OPTION_AUTH_TYPES, \
sec_types, _struct_, _perms_.options, _perms_.set), \
CONF_ITEM_BOOLBIT_SET("PrivilegedPort", \
false, EXPORT_OPTION_PRIVILEGED_PORT, \
_struct_, _perms_.options, _perms_.set), \
CONF_ITEM_BOOLBIT_SET("Manage_Gids", \
false, EXPORT_OPTION_MANAGE_GIDS, \
_struct_, _perms_.options, _perms_.set), \
CONF_ITEM_LIST_BITS_SET("Squash", \
EXPORT_OPTION_ROOT_SQUASH, EXPORT_OPTION_SQUASH_TYPES, \
squash_types, _struct_, _perms_.options, _perms_.set), \
CONF_ITEM_BOOLBIT_SET("NFS_Commit", \
false, EXPORT_OPTION_COMMIT, \
_struct_, _perms_.options, _perms_.set), \
CONF_ITEM_ENUM_BITS_SET("Delegations", \
EXPORT_OPTION_NO_DELEGATIONS, EXPORT_OPTION_DELEGATIONS,\
delegations, _struct_, _perms_.options, _perms_.set)
/**
* @brief Process a list of clients for a client block
*
* CONFIG_PROC handler that gets called for each token in the term list.
* Create a exportlist_client_entry__ for each token and link it into
* the proto client's cle_list list head. We will pass that head to the
* export in commit.
*
* NOTES: this is the place to expand a node list with perhaps moving the
* call to add_client into the expander rather than build a list there
* to be then walked here...
*
* @param token [IN] pointer to token string from parse tree
* @param type_hint [IN] a type hint from what the parser recognized
* @param item [IN] pointer to the config item table entry
* @param param_addr [IN] pointer to prototype client entry
* @param err_type [OUT] error handling
* @return error count
*/
static int client_adder(const char *token,
enum term_type type_hint,
struct config_item *item,
void *param_addr,
void *cnode,
struct config_error_type *err_type)
{
struct exportlist_client_entry__ *proto_cli;
int rc;
proto_cli = container_of(param_addr,
struct exportlist_client_entry__,
cle_list);
LogMidDebug(COMPONENT_CONFIG, "Adding client %s", token);
rc = add_client(&proto_cli->cle_list,
token, type_hint,
&proto_cli->client_perms, cnode, err_type);
return rc;
}
/**
* @brief Table of client sub-block parameters
*
* NOTE: node discovery is ordered by this table!
* "Clients" is last because we must have all other params processed
* before we walk the list of accessing clients!
*/
static struct config_item client_params[] = {
CONF_EXPORT_PERMS(exportlist_client_entry__, client_perms),
CONF_ITEM_PROC("Clients", noop_conf_init, client_adder,
exportlist_client_entry__, cle_list),
CONFIG_EOL
};
/**
* @brief Table of DEXPORT_DEFAULTS block parameters
*
* NOTE: node discovery is ordered by this table!
*/
static struct config_item export_defaults_params[] = {
CONF_EXPORT_PERMS(global_export_perms, conf),
CONFIG_EOL
};
/**
* @brief Table of FSAL sub-block parameters
*
* NOTE: this points to a struct that is private to
* fsal_cfg_commit.
*/
static struct config_item fsal_params[] = {
CONF_ITEM_STR("Name", 1, 10, NULL,
fsal_args, name), /* cheater union */
CONFIG_EOL
};
/**
* @brief Common EXPORT block parameters
*/
#define CONF_EXPORT_PARAMS(_struct_) \
CONF_MAND_UI16("Export_id", 0, UINT16_MAX, 1, \
_struct_, export_id), \
CONF_MAND_PATH("Path", 1, MAXPATHLEN, NULL, \
_struct_, fullpath), /* must chomp '/' */ \
CONF_UNIQ_PATH("Pseudo", 1, MAXPATHLEN, NULL, \
_struct_, pseudopath), \
CONF_ITEM_UI64("MaxRead", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, \
_struct_, MaxRead), \
CONF_ITEM_UI64("MaxWrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, \
_struct_, MaxWrite), \
CONF_ITEM_UI64("PrefRead", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, \
_struct_, PrefRead), \
CONF_ITEM_UI64("PrefWrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE,\
_struct_, PrefWrite), \
CONF_ITEM_UI64("PrefReaddir", 512, FSAL_MAXIOSIZE, 16384, \
_struct_, PrefReaddir), \
CONF_ITEM_FSID_SET("Filesystem_id", 666, 666, \
_struct_, filesystem_id, /* major.minor */ \
EXPORT_OPTION_FSID_SET, options_set), \
CONF_ITEM_STR("Tag", 1, MAXPATHLEN, NULL, \
_struct_, FS_tag), \
CONF_ITEM_UI64("MaxOffsetWrite", 512, UINT64_MAX, UINT64_MAX, \
_struct_, MaxOffsetWrite), \
CONF_ITEM_UI64("MaxOffsetRead", 512, UINT64_MAX, UINT64_MAX, \
_struct_, MaxOffsetRead), \
CONF_ITEM_BOOLBIT_SET("UseCookieVerifier", \
false, EXPORT_OPTION_USE_COOKIE_VERIFIER, \
_struct_, options, options_set), \
CONF_ITEM_BOOLBIT_SET("DisableReaddirPlus", \
false, EXPORT_OPTION_NO_READDIR_PLUS, \
_struct_, options, options_set), \
CONF_ITEM_BOOLBIT_SET("Trust_Readdir_Negative_Cache", \
false, EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE, \
_struct_, options, options_set), \
CONF_ITEM_BOOLBIT_SET("Disable_ACL", \
false, EXPORT_OPTION_DISABLE_ACL, \
_struct_, options, options_set), \
CONF_ITEM_I32_SET("Attr_Expiration_Time", -1, INT32_MAX, 60, \
_struct_, expire_time_attr, \
EXPORT_OPTION_EXPIRE_SET, options_set)
/**
* @brief Table of EXPORT block parameters
*/
static struct config_item export_params[] = {
CONF_EXPORT_PARAMS(gsh_export),
CONF_EXPORT_PERMS(gsh_export, export_perms),
/* NOTE: the Client and FSAL sub-blocks must be the *last*
* two entries in the list. This is so all other
* parameters have been processed before these sub-blocks
* are processed.
*/
CONF_ITEM_BLOCK("Client", client_params,
client_init, client_commit,
gsh_export, clients),
CONF_RELAX_BLOCK("FSAL", fsal_params,
fsal_init, fsal_cfg_commit,
gsh_export, fsal_export),
CONFIG_EOL
};
/**
* @brief Table of EXPORT update block parameters
*/
static struct config_item export_update_params[] = {
CONF_EXPORT_PARAMS(gsh_export),
CONF_EXPORT_PERMS(gsh_export, export_perms),
/* NOTE: the Client and FSAL sub-blocks must be the *last*
* two entries in the list. This is so all other
* parameters have been processed before these sub-blocks
* are processed.
*/
CONF_ITEM_BLOCK("Client", client_params,
client_init, client_commit,
gsh_export, clients),
CONF_RELAX_BLOCK("FSAL", fsal_params,
fsal_init, fsal_update_cfg_commit,
gsh_export, fsal_export),
CONFIG_EOL
};
/**
* @brief Top level definition for an EXPORT block
*/
static struct config_block export_param = {
.dbus_interface_name = "org.ganesha.nfsd.config.%d",
.blk_desc.name = "EXPORT",
.blk_desc.type = CONFIG_BLOCK,
.blk_desc.u.blk.init = export_init,
.blk_desc.u.blk.params = export_params,
.blk_desc.u.blk.commit = export_commit,
.blk_desc.u.blk.display = export_display
};
/**
* @brief Top level definition for an ADD EXPORT block
*/
struct config_block add_export_param = {
.dbus_interface_name = "org.ganesha.nfsd.config.%d",
.blk_desc.name = "EXPORT",
.blk_desc.type = CONFIG_BLOCK,
.blk_desc.u.blk.init = export_init,
.blk_desc.u.blk.params = export_params,
.blk_desc.u.blk.commit = add_export_commit,
.blk_desc.u.blk.display = export_display
};
/**
* @brief Top level definition for an UPDATE EXPORT block
*/
struct config_block update_export_param = {
.dbus_interface_name = "org.ganesha.nfsd.config.%d",
.blk_desc.name = "EXPORT",
.blk_desc.type = CONFIG_BLOCK,
.blk_desc.u.blk.init = export_init,
.blk_desc.u.blk.params = export_update_params,
.blk_desc.u.blk.commit = update_export_commit,
.blk_desc.u.blk.display = export_display
};
/**
* @brief Top level definition for an EXPORT_DEFAULTS block
*/
struct config_block export_defaults_param = {
.dbus_interface_name = "org.ganesha.nfsd.config.defaults",
.blk_desc.name = "EXPORT_DEFAULTS",
.blk_desc.type = CONFIG_BLOCK,
.blk_desc.u.blk.init = export_defaults_init,
.blk_desc.u.blk.params = export_defaults_params,
.blk_desc.u.blk.commit = export_defaults_commit,
.blk_desc.u.blk.display = export_defaults_display
};
/**
* @brief builds an export entry for '/' with default parameters
*
* If export_id = 0 has not been specified, and not other export
* for Pseudo "/" has been specified, build an FSAL_PSEUDO export
* for the root of the Pseudo FS.
*
* @return -1 on error, 0 if we already have one, 1 if created one
*/
static int build_default_root(struct config_error_type *err_type)
{
struct gsh_export *export;
struct fsal_module *fsal_hdl = NULL;
struct root_op_context root_op_context;
/* See if export_id = 0 has already been specified */
export = get_gsh_export(0);
if (export != NULL) {
/* export_id = 0 has already been specified */
LogDebug(COMPONENT_CONFIG,
"Export 0 already exists");
put_gsh_export(export);
return 0;
}
/* See if another export with Pseudo = "/" has already been specified.
*/
export = get_gsh_export_by_pseudo("/", true);
if (export != NULL) {
/* Pseudo = / has already been specified */
LogDebug(COMPONENT_CONFIG,
"Pseudo root already exists");
put_gsh_export(export);
return 0;
}
/* allocate and initialize the exportlist part with the id */
LogDebug(COMPONENT_CONFIG,
"Allocating Pseudo root export");
export = alloc_export();
/* Initialize req_ctx */
init_root_op_context(&root_op_context, export, NULL, 0, 0,
UNKNOWN_REQUEST);
export->filesystem_id.major = 152;
export->filesystem_id.minor = 152;
export->MaxWrite = FSAL_MAXIOSIZE;
export->MaxRead = FSAL_MAXIOSIZE;
export->PrefWrite = FSAL_MAXIOSIZE;
export->PrefRead = FSAL_MAXIOSIZE;
export->PrefReaddir = 16384;
/*Don't set anonymous uid and gid, they will actually be ignored */
/* Support only NFS v4 and TCP.
* Root is allowed
* MD Read Access
* Allow use of default auth types
*
* Allow non-privileged client ports to access pseudo export.
*/
export->export_perms.options = EXPORT_OPTION_ROOT |
EXPORT_OPTION_MD_READ_ACCESS |
EXPORT_OPTION_NFSV4 |
EXPORT_OPTION_AUTH_TYPES |
EXPORT_OPTION_TCP;
export->export_perms.set = EXPORT_OPTION_SQUASH_TYPES |
EXPORT_OPTION_ACCESS_MASK |
EXPORT_OPTION_PROTOCOLS |
EXPORT_OPTION_TRANSPORTS |
EXPORT_OPTION_AUTH_TYPES |
EXPORT_OPTION_PRIVILEGED_PORT;
export->options = EXPORT_OPTION_USE_COOKIE_VERIFIER;
export->options_set = EXPORT_OPTION_FSID_SET |
EXPORT_OPTION_USE_COOKIE_VERIFIER;
/* Set the fullpath to "/" */
export->fullpath = gsh_strdup("/");
/* Set Pseudo Path to "/" */
export->pseudopath = gsh_strdup("/");
/* Assign FSAL_PSEUDO */
fsal_hdl = lookup_fsal("PSEUDO");
if (fsal_hdl == NULL) {
LogCrit(COMPONENT_CONFIG,
"FSAL PSEUDO is not loaded!");
goto err_out;
} else {
fsal_status_t rc;
rc = fsal_hdl->m_ops.create_export(fsal_hdl,
NULL,
err_type,
&fsal_up_top);
if (FSAL_IS_ERROR(rc)) {
fsal_put(fsal_hdl);
LogCrit(COMPONENT_CONFIG,
"Could not create FSAL export for %s",
export->fullpath);
goto err_out;
}
}
assert(root_op_context.req_ctx.fsal_export != NULL);
export->fsal_export = root_op_context.req_ctx.fsal_export;
if (!insert_gsh_export(export)) {
export->fsal_export->exp_ops.release(export->fsal_export);
fsal_put(fsal_hdl);
LogCrit(COMPONENT_CONFIG,
"Failed to insert pseudo root In use??");
goto err_out;
}
/* This export must be mounted to the PseudoFS */
export_add_to_mount_work(export);
LogInfo(COMPONENT_CONFIG,
"Export 0 (/) successfully created");
put_gsh_export(export); /* all done, let go */
release_root_op_context();
return 1;
err_out:
free_export(export);
release_root_op_context();
return -1;
}
/**
* @brief Read the export entries from the parsed configuration file.
*
* @param[in] in_config The file that contains the export list
*
* @return A negative value on error,
* the number of export entries else.
*/
int ReadExports(config_file_t in_config,
struct config_error_type *err_type)
{
int rc, num_exp;
rc = load_config_from_parse(in_config,
&export_defaults_param,
NULL,
false,
err_type);
if (rc < 0) {
LogCrit(COMPONENT_CONFIG, "Export defaults block error");
return -1;
}
num_exp = load_config_from_parse(in_config,
&export_param,
NULL,
false,
err_type);
if (num_exp < 0) {
LogCrit(COMPONENT_CONFIG, "Export block error");
return -1;
}
rc = build_default_root(err_type);
if (rc < 0) {
LogCrit(COMPONENT_CONFIG, "No pseudo root!");
return -1;
}
return num_exp;
}
/**
* @brief Reread the export entries from the parsed configuration file.
*
* @param[in] in_config The file that contains the export list
*
* @return A negative value on error,
* the number of export entries else.
*/
int reread_exports(config_file_t in_config,
struct config_error_type *err_type)
{
int rc, num_exp;
LogInfo(COMPONENT_CONFIG, "Reread exports");
rc = load_config_from_parse(in_config,
&export_defaults_param,
NULL,
false,
err_type);
if (rc < 0) {
LogCrit(COMPONENT_CONFIG, "Export defaults block error");
return -1;
}
num_exp = load_config_from_parse(in_config,
&update_export_param,
NULL,
false,
err_type);
if (num_exp < 0) {
LogCrit(COMPONENT_CONFIG, "Export block error");
return -1;
}
return num_exp;
}
static void FreeClientList(struct glist_head *clients)
{
struct glist_head *glist;
struct glist_head *glistn;
glist_for_each_safe(glist, glistn, clients) {
exportlist_client_entry_t *client;
client =
glist_entry(glist, exportlist_client_entry_t, cle_list);
glist_del(&client->cle_list);
if (client->type == NETGROUP_CLIENT &&
client->client.netgroup.netgroupname != NULL)
gsh_free(client->client.netgroup.netgroupname);
if (client->type == WILDCARDHOST_CLIENT &&
client->client.wildcard.wildcard != NULL)
gsh_free(client->client.wildcard.wildcard);
if (client->type == GSSPRINCIPAL_CLIENT &&
client->client.gssprinc.princname != NULL)
gsh_free(client->client.gssprinc.princname);
gsh_free(client);
}
}
/**
* @brief Free resources attached to an export
*
* @param export [IN] pointer to export
*
* @return true if all went well
*/
void free_export_resources(struct gsh_export *export)
{
FreeClientList(&export->clients);
if (export->fsal_export != NULL) {
struct fsal_module *fsal = export->fsal_export->fsal;
export->fsal_export->exp_ops.release(export->fsal_export);
fsal_put(fsal);
}
export->fsal_export = NULL;
/* free strings here */
if (export->fullpath != NULL)
gsh_free(export->fullpath);
if (export->pseudopath != NULL)
gsh_free(export->pseudopath);
if (export->FS_tag != NULL)
gsh_free(export->FS_tag);
}
/**
* @brief pkginit callback to initialize exports from nfs_init
*
* Assumes being called with the export_by_id.lock held.
* true on success
*/
static bool init_export_cb(struct gsh_export *exp, void *state)
{
return !(init_export_root(exp));
}
/**
* @brief Initialize exports over a live cache inode and fsal layer
*/
void exports_pkginit(void)
{
foreach_gsh_export(init_export_cb, NULL);
}
/**
* @brief Return a reference to the root object of the export
*
* Must be called with the caller holding a reference to the export.
*
* Returns with an additional reference to the obj held for use by the
* caller.
*
* @param export [IN] the aforementioned export
* @param entry [IN/OUT] call by ref pointer to store obj
*
* @return FSAL status
*/
fsal_status_t nfs_export_get_root_entry(struct gsh_export *export,
struct fsal_obj_handle **obj)
{
PTHREAD_RWLOCK_rdlock(&export->lock);
if (export->exp_root_obj)
export->exp_root_obj->obj_ops.get_ref(export->exp_root_obj);
PTHREAD_RWLOCK_unlock(&export->lock);
*obj = export->exp_root_obj;
if (!(*obj))
return fsalstat(ERR_FSAL_NOENT, 0);
if ((*obj)->type != DIRECTORY)
return fsalstat(ERR_FSAL_NOTDIR, 0);
return fsalstat(ERR_FSAL_NO_ERROR, 0);
}
/**
* @brief Initialize the root cache inode for an export.
*
* Assumes being called with the export_by_id.lock held.
*
* @param exp [IN] the export
*
* @return 0 if successful otherwise err.
*/
int init_export_root(struct gsh_export *export)
{
fsal_status_t fsal_status;
struct fsal_obj_handle *obj;
struct root_op_context root_op_context;
int my_status;
/* Initialize req_ctx */
init_root_op_context(&root_op_context, export, export->fsal_export,
0, 0, UNKNOWN_REQUEST);
/* Lookup for the FSAL Path */
LogDebug(COMPONENT_EXPORT,
"About to lookup_path for ExportId=%u Path=%s",
export->export_id, export->fullpath);
/* This takes a reference, which will keep the root object around for
* the lifetime of the export. */
fsal_status =
export->fsal_export->exp_ops.lookup_path(export->fsal_export,
export->fullpath, &obj, NULL);
if (FSAL_IS_ERROR(fsal_status)) {
my_status = EINVAL;
LogCrit(COMPONENT_EXPORT,
"Lookup failed on path, ExportId=%u Path=%s FSAL_ERROR=(%s,%u)",
export->export_id, export->fullpath,
msg_fsal_err(fsal_status.major), fsal_status.minor);
goto out;
}
PTHREAD_RWLOCK_wrlock(&obj->state_hdl->state_lock);
PTHREAD_RWLOCK_wrlock(&export->lock);
/* Pass ref off to export */
export->exp_root_obj = obj;
glist_add_tail(&obj->state_hdl->dir.export_roots,
&export->exp_root_list);
/* Protect this entry from removal (unlink) */
(void) atomic_inc_int32_t(&obj->state_hdl->dir.exp_root_refcount);
PTHREAD_RWLOCK_unlock(&export->lock);
PTHREAD_RWLOCK_unlock(&obj->state_hdl->state_lock);
if (isDebug(COMPONENT_EXPORT)) {
LogDebug(COMPONENT_EXPORT,
"Added root obj %p FSAL %s for path %s on export_id=%d",
obj, obj->fsal->name, export->fullpath,
export->export_id);
} else {
LogInfo(COMPONENT_EXPORT,
"Added root obj for path %s on export_id=%d",
export->fullpath, export->export_id);
}
my_status = 0;
out:
release_root_op_context();
return my_status;
}
static inline void clean_up_export(struct gsh_export *export,
struct fsal_obj_handle *root_obj)
{
/* Make export unreachable */
pseudo_unmount_export(export);
remove_gsh_export(export->export_id);
/* Release state belonging to this export */
state_release_export(export);
/* Flush FSAL-specific state */
export->fsal_export->exp_ops.unexport(export->fsal_export, root_obj);
}
/**
* @brief Release all the export state, including the root object
*
* @param exp [IN] the export
*/
static void release_export(struct gsh_export *export)
{
struct fsal_obj_handle *obj = NULL;
fsal_status_t fsal_status;
/* Get a reference to the root entry */
fsal_status = nfs_export_get_root_entry(export, &obj);
if (FSAL_IS_ERROR(fsal_status)) {
/* No more root entry, bail out, this export is
* probably about to be destroyed.
*/
LogInfo(COMPONENT_CACHE_INODE,
"Export root for export id %d status %s",
export->export_id, msg_fsal_err(fsal_status.major));
return;
}
/* Make the export unreachable as a root object */
PTHREAD_RWLOCK_wrlock(&obj->state_hdl->state_lock);
PTHREAD_RWLOCK_wrlock(&export->lock);
glist_del(&export->exp_root_list);
export->exp_root_obj->obj_ops.put_ref(export->exp_root_obj);
export->exp_root_obj = NULL;
(void) atomic_dec_int32_t(&obj->state_hdl->dir.exp_root_refcount);
PTHREAD_RWLOCK_unlock(&export->lock);
PTHREAD_RWLOCK_unlock(&obj->state_hdl->state_lock);
LogDebug(COMPONENT_EXPORT,
"Released root obj %p for path %s on export_id=%d",
obj, export->fullpath, export->export_id);
clean_up_export(export, obj);
/* Release ref taken above */
obj->obj_ops.put_ref(obj);
}
void unexport(struct gsh_export *export)
{
bool op_ctx_set = false;
struct root_op_context ctx;
/* Make the export unreachable */
LogDebug(COMPONENT_EXPORT,
"Unexport %s, Pseduo %s",
export->fullpath, export->pseudopath);
/* Lots of obj_ops may be called during cleanup; make sure that an
* op_ctx exists */
if (!op_ctx) {
init_root_op_context(&ctx, export, export->fsal_export, 0, 0,
UNKNOWN_REQUEST);
op_ctx_set = true;
}
release_export(export);
if (op_ctx_set)
release_root_op_context();
}
/**
* @brief Match a specific option in the client export list
*
* @param[in] hostaddr Host to search for
* @param[in] clients Client list to search
* @param[out] client_found Matching entry
* @param[in] export_option Option to search for
*
* @return true if found, false otherwise.
*/
static exportlist_client_entry_t *client_match(sockaddr_t *hostaddr,
struct gsh_export *export)
{
struct glist_head *glist;
in_addr_t addr = get_in_addr(hostaddr);
int rc;
int ipvalid = -1; /* -1 need to print, 0 - invalid, 1 - ok */
char hostname[MAXHOSTNAMELEN + 1];
char ipstring[SOCK_NAME_MAX + 1];
glist_for_each(glist, &export->clients) {
exportlist_client_entry_t *client;
client = glist_entry(glist, exportlist_client_entry_t,
cle_list);
LogClientListEntry(NIV_MID_DEBUG,
COMPONENT_EXPORT,
__LINE__,
(char *) __func__,
"Match V4: ",
client);
switch (client->type) {
case HOSTIF_CLIENT:
if (client->client.hostif.clientaddr == addr)
return client;
break;
case NETWORK_CLIENT:
if ((client->client.network.netmask & ntohl(addr)) ==
client->client.network.netaddr)
return client;
break;
case NETGROUP_CLIENT:
/* Try to get the entry from th IP/name cache */
rc = nfs_ip_name_get(hostaddr, hostname,
sizeof(hostname));
if (rc == IP_NAME_NOT_FOUND) {
/* IPaddr was not cached, add it to the cache */
rc = nfs_ip_name_add(hostaddr,
hostname,
sizeof(hostname));
}
if (rc != IP_NAME_SUCCESS)
break; /* Fatal failure */
/* At this point 'hostname' should contain the
* name that was found
*/
if (ng_innetgr(client->client.netgroup.netgroupname,
hostname)) {
return client;
}
break;
case WILDCARDHOST_CLIENT:
/* Now checking for IP wildcards */
if (ipvalid < 0)
ipvalid = sprint_sockip(hostaddr,
ipstring,
sizeof(ipstring));
if (ipvalid &&
(fnmatch(client->client.wildcard.wildcard,
ipstring,
FNM_PATHNAME) == 0)) {
return client;
}
/* Try to get the entry from th IP/name cache */
rc = nfs_ip_name_get(hostaddr, hostname,
sizeof(hostname));
if (rc == IP_NAME_NOT_FOUND) {
/* IPaddr was not cached, add it to the cache */
/** @todo this change from 1.5 is not IPv6
* useful. come back to this and use the
* string from client mgr inside req_ctx...
*/
rc = nfs_ip_name_add(hostaddr,
hostname,
sizeof(hostname));
}
if (rc != IP_NAME_SUCCESS)
break;
/* At this point 'hostname' should contain the
* name that was found
*/
if (fnmatch
(client->client.wildcard.wildcard, hostname,
FNM_PATHNAME) == 0) {
return client;
}
break;
case GSSPRINCIPAL_CLIENT:
/** @todo BUGAZOMEU a completer lors de l'integration de RPCSEC_GSS */
LogCrit(COMPONENT_EXPORT,
"Unsupported type GSS_PRINCIPAL_CLIENT");
break;
case HOSTIF_CLIENT_V6:
break;
case MATCH_ANY_CLIENT:
return client;
case BAD_CLIENT:
default:
continue;
}
}
/* no export found for this option */
return NULL;
}
/**
* @brief Match a specific option in the client export list
*
* @param[in] paddrv6 Host to search for
* @param[in] clients Client list to search
* @param[out] client_found Matching entry
* @param[in] export_option Option to search for
*
* @return true if found, false otherwise.
*/
static exportlist_client_entry_t *client_matchv6(struct in6_addr *paddrv6,
struct gsh_export *export)
{
struct glist_head *glist;
glist_for_each(glist, &export->clients) {
exportlist_client_entry_t *client;
client = glist_entry(glist, exportlist_client_entry_t,
cle_list);
LogClientListEntry(NIV_MID_DEBUG,
COMPONENT_EXPORT,
__LINE__,
(char *) __func__,
"Match V6: ",
client);
switch (client->type) {
case HOSTIF_CLIENT:
case NETWORK_CLIENT:
case NETGROUP_CLIENT:
case WILDCARDHOST_CLIENT:
case GSSPRINCIPAL_CLIENT:
case BAD_CLIENT:
break;
case HOSTIF_CLIENT_V6:
if (!memcmp(client->client.hostif.clientaddr6.s6_addr,
paddrv6->s6_addr, 16)) {
/* Remember that IPv6 address are
* 128 bits = 16 bytes long
*/
return client;
}
break;
case MATCH_ANY_CLIENT:
return client;
default:
break;
}
}
/* no export found for this option */
return NULL;
}
static exportlist_client_entry_t *client_match_any(sockaddr_t *hostaddr,
struct gsh_export *export)
{
if (hostaddr->ss_family == AF_INET6) {
struct sockaddr_in6 *psockaddr_in6 =
(struct sockaddr_in6 *)hostaddr;
return client_matchv6(&(psockaddr_in6->sin6_addr), export);
} else {
return client_match(hostaddr, export);
}
}
/**
* @brief Checks if request security flavor is suffcient for the requested
* export
*
* @param[in] req Related RPC request.
*
* @return true if the request flavor exists in the matching export
* false otherwise
*/
bool export_check_security(struct svc_req *req)
{
switch (req->rq_cred.oa_flavor) {
case AUTH_NONE:
if ((op_ctx->export_perms->options &
EXPORT_OPTION_AUTH_NONE) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support AUTH_NONE",
op_ctx->ctx_export->fullpath);
return false;
}
break;
case AUTH_UNIX:
if ((op_ctx->export_perms->options &
EXPORT_OPTION_AUTH_UNIX) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support AUTH_UNIX",
op_ctx->ctx_export->fullpath);
return false;
}
break;
#ifdef _HAVE_GSSAPI
case RPCSEC_GSS:
if ((op_ctx->export_perms->options &
(EXPORT_OPTION_RPCSEC_GSS_NONE |
EXPORT_OPTION_RPCSEC_GSS_INTG |
EXPORT_OPTION_RPCSEC_GSS_PRIV)) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support RPCSEC_GSS",
op_ctx->ctx_export->fullpath);
return false;
} else {
struct svc_rpc_gss_data *gd;
rpc_gss_svc_t svc;
gd = SVCAUTH_PRIVATE(req->rq_auth);
svc = gd->sec.svc;
LogFullDebug(COMPONENT_EXPORT, "Testing svc %d",
(int)svc);
switch (svc) {
case RPCSEC_GSS_SVC_NONE:
if ((op_ctx->export_perms->options &
EXPORT_OPTION_RPCSEC_GSS_NONE) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support RPCSEC_GSS_SVC_NONE",
op_ctx->ctx_export->fullpath);
return false;
}
break;
case RPCSEC_GSS_SVC_INTEGRITY:
if ((op_ctx->export_perms->options &
EXPORT_OPTION_RPCSEC_GSS_INTG) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support RPCSEC_GSS_SVC_INTEGRITY",
op_ctx->ctx_export->fullpath);
return false;
}
break;
case RPCSEC_GSS_SVC_PRIVACY:
if ((op_ctx->export_perms->options &
EXPORT_OPTION_RPCSEC_GSS_PRIV) == 0) {
LogInfo(COMPONENT_EXPORT,
"Export %s does not support RPCSEC_GSS_SVC_PRIVACY",
op_ctx->ctx_export->fullpath);
return false;
}
break;
default:
LogInfo(COMPONENT_EXPORT,
"Export %s does not support unknown RPCSEC_GSS_SVC %d",
op_ctx->ctx_export->fullpath,
(int)svc);
return false;
}
}
break;
#endif
default:
LogInfo(COMPONENT_EXPORT,
"Export %s does not support unknown oa_flavor %d",
op_ctx->ctx_export->fullpath,
(int)req->rq_cred.oa_flavor);
return false;
}
return true;
}
static char ten_bytes_all_0[10];
sockaddr_t *convert_ipv6_to_ipv4(sockaddr_t *ipv6, sockaddr_t *ipv4)
{
struct sockaddr_in *paddr = (struct sockaddr_in *)ipv4;
struct sockaddr_in6 *psockaddr_in6 = (struct sockaddr_in6 *)ipv6;
/* If the client socket is IPv4, then it is wrapped into a
* ::ffff:a.b.c.d IPv6 address. We check this here.
* This kind of adress is shaped like this:
* |---------------------------------------------------------------|
* | 80 bits = 10 bytes | 16 bits = 2 bytes | 32 bits = 4 bytes |
* |---------------------------------------------------------------|
* | 0 | FFFF | IPv4 address |
* |---------------------------------------------------------------|
*/
if ((ipv6->ss_family == AF_INET6)
&& !memcmp(psockaddr_in6->sin6_addr.s6_addr, ten_bytes_all_0, 10)
&& (psockaddr_in6->sin6_addr.s6_addr[10] == 0xFF)
&& (psockaddr_in6->sin6_addr.s6_addr[11] == 0xFF)) {
void *ab;
memset(ipv4, 0, sizeof(*ipv4));
ab = &(psockaddr_in6->sin6_addr.s6_addr[12]);
paddr->sin_port = psockaddr_in6->sin6_port;
paddr->sin_addr.s_addr = *(in_addr_t *) ab;
ipv4->ss_family = AF_INET;
if (isFullDebug(COMPONENT_EXPORT)) {
char ipstring4[SOCK_NAME_MAX];
char ipstring6[SOCK_NAME_MAX];
sprint_sockip(ipv6, ipstring6, sizeof(ipstring6));
sprint_sockip(ipv4, ipstring4, sizeof(ipstring4));
LogMidDebug(COMPONENT_EXPORT,
"Converting IPv6 encapsulated IPv4 address %s to IPv4 %s",
ipstring6, ipstring4);
}
return ipv4;
} else {
return ipv6;
}
}
/**
* @brief Get the best anonymous uid available.
*
* This is safe if there is no op_ctx or there is one but there is no
* export_perms attached.
*
*/
uid_t get_anonymous_uid(void)
{
uid_t anon_uid;
if (op_ctx != NULL && op_ctx->export_perms != NULL) {
/* We have export_perms, use it. */
return op_ctx->export_perms->anonymous_uid;
}
PTHREAD_RWLOCK_rdlock(&export_opt_lock);
if ((export_opt.conf.set & EXPORT_OPTION_ANON_UID_SET) != 0) {
/* Option was set in EXPORT_DEFAULTS */
anon_uid = export_opt.conf.anonymous_uid;
} else {
/* Default to code default. */
anon_uid = export_opt.def.anonymous_uid;
}
PTHREAD_RWLOCK_unlock(&export_opt_lock);
return anon_uid;
}
/**
* @brief Get the best anonymous gid available.
*
* This is safe if there is no op_ctx or there is one but there is no
* export_perms attached.
*
*/
gid_t get_anonymous_gid(void)
{
/* Default to code default. */
gid_t anon_gid = export_opt.def.anonymous_gid;
if (op_ctx != NULL && op_ctx->export_perms != NULL) {
/* We have export_perms, use it. */
return op_ctx->export_perms->anonymous_gid;
}
PTHREAD_RWLOCK_rdlock(&export_opt_lock);
if ((export_opt.conf.set & EXPORT_OPTION_ANON_GID_SET) != 0) {
/* Option was set in EXPORT_DEFAULTS */
anon_gid = export_opt.conf.anonymous_gid;
} else {
/* Default to code default. */
anon_gid = export_opt.def.anonymous_gid;
}
PTHREAD_RWLOCK_unlock(&export_opt_lock);
return anon_gid;
}
/**
* @brief Checks if a machine is authorized to access an export entry
*
* Permissions in the op context get updated based on export and client.
*
* Takes the export->lock in read mode to protect the client list and
* export permissions while performing this work.
*/
void export_check_access(void)
{
exportlist_client_entry_t *client = NULL;
sockaddr_t alt_hostaddr;
sockaddr_t *hostaddr = NULL;
assert(op_ctx != NULL);
assert(op_ctx->export_perms != NULL);
/* Initialize permissions to allow nothing, anonymous_uid and
* anonymous_gid will get set farther down.
*/
memset(op_ctx->export_perms, 0, sizeof(*op_ctx->export_perms));
if (op_ctx->ctx_export != NULL) {
/* Take lock */
PTHREAD_RWLOCK_rdlock(&op_ctx->ctx_export->lock);
} else {
/* Shortcut if no export */
goto no_export;
}
hostaddr = convert_ipv6_to_ipv4(op_ctx->caller_addr, &alt_hostaddr);
if (isMidDebug(COMPONENT_EXPORT)) {
char ipstring[SOCK_NAME_MAX];
ipstring[0] = '\0';
(void) sprint_sockip(hostaddr,
ipstring, sizeof(ipstring));
LogMidDebug(COMPONENT_EXPORT,
"Check for address %s for export id %u fullpath %s",
ipstring, op_ctx->ctx_export->export_id,
op_ctx->ctx_export->fullpath);
}
/* Does the client match anyone on the client list? */
client = client_match_any(hostaddr, op_ctx->ctx_export);
if (client != NULL) {
/* Take client options */
op_ctx->export_perms->options = client->client_perms.options &
client->client_perms.set;
if (client->client_perms.set & EXPORT_OPTION_ANON_UID_SET)
op_ctx->export_perms->anonymous_uid =
client->client_perms.anonymous_uid;
if (client->client_perms.set & EXPORT_OPTION_ANON_GID_SET)
op_ctx->export_perms->anonymous_gid =
client->client_perms.anonymous_gid;
op_ctx->export_perms->set = client->client_perms.set;
}
/* Any options not set by the client, take from the export */
op_ctx->export_perms->options |=
op_ctx->ctx_export->export_perms.options &
op_ctx->ctx_export->export_perms.set &
~op_ctx->export_perms->set;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0 &&
(op_ctx->ctx_export->export_perms.set &
EXPORT_OPTION_ANON_UID_SET) != 0)
op_ctx->export_perms->anonymous_uid =
op_ctx->ctx_export->export_perms.anonymous_uid;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0 &&
(op_ctx->ctx_export->export_perms.set &
EXPORT_OPTION_ANON_GID_SET) != 0)
op_ctx->export_perms->anonymous_gid =
op_ctx->ctx_export->export_perms.anonymous_gid;
op_ctx->export_perms->set |= op_ctx->ctx_export->export_perms.set;
no_export:
PTHREAD_RWLOCK_rdlock(&export_opt_lock);
/* Any options not set by the client or export, take from the
* EXPORT_DEFAULTS block.
*/
op_ctx->export_perms->options |= export_opt.conf.options &
export_opt.conf.set &
~op_ctx->export_perms->set;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0 &&
(export_opt.conf.set & EXPORT_OPTION_ANON_UID_SET) != 0)
op_ctx->export_perms->anonymous_uid =
export_opt.conf.anonymous_uid;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0 &&
(export_opt.conf.set & EXPORT_OPTION_ANON_GID_SET) != 0)
op_ctx->export_perms->anonymous_gid =
export_opt.conf.anonymous_gid;
op_ctx->export_perms->set |= export_opt.conf.set;
/* And finally take any options not yet set from global defaults */
op_ctx->export_perms->options |= export_opt.def.options &
~op_ctx->export_perms->set;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0)
op_ctx->export_perms->anonymous_uid =
export_opt.def.anonymous_uid;
if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0)
op_ctx->export_perms->anonymous_gid =
export_opt.def.anonymous_gid;
op_ctx->export_perms->set |= export_opt.def.set;
if (isMidDebug(COMPONENT_EXPORT)) {
char perms[1024];
struct display_buffer dspbuf = {sizeof(perms), perms, perms};
if (client != NULL) {
(void) StrExportOptions(&dspbuf, &client->client_perms);
LogMidDebug(COMPONENT_EXPORT,
"CLIENT (%s)",
perms);
display_reset_buffer(&dspbuf);
}
if (op_ctx->ctx_export != NULL) {
(void) StrExportOptions(&dspbuf,
&op_ctx->ctx_export->export_perms);
LogMidDebug(COMPONENT_EXPORT,
"EXPORT (%s)",
perms);
display_reset_buffer(&dspbuf);
}
(void) StrExportOptions(&dspbuf, &export_opt.conf);
LogMidDebug(COMPONENT_EXPORT,
"EXPORT_DEFAULTS (%s)",
perms);
display_reset_buffer(&dspbuf);
(void) StrExportOptions(&dspbuf, &export_opt.def);
LogMidDebug(COMPONENT_EXPORT,
"default options (%s)",
perms);
display_reset_buffer(&dspbuf);
(void) StrExportOptions(&dspbuf, op_ctx->export_perms);
LogMidDebug(COMPONENT_EXPORT,
"Final options (%s)",
perms);
}
PTHREAD_RWLOCK_unlock(&export_opt_lock);
if (op_ctx->ctx_export != NULL) {
/* Release lock */
PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->lock);
}
}