reactos/base/services/nfsd/namespace.c

478 lines
14 KiB
C

/* NFSv4.1 client for Windows
* Copyright © 2012 The Regents of the University of Michigan
*
* Olga Kornievskaia <aglo@umich.edu>
* Casey Bodley <cbodley@umich.edu>
*
* This library 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 2.1 of the License, or (at
* your option) any later version.
*
* This library 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
*/
#include <windows.h>
#include <strsafe.h>
#include "nfs41_ops.h"
#include "util.h"
#include "daemon_debug.h"
#define NSLVL 2 /* dprintf level for namespace logging */
#define client_entry(pos) list_container(pos, nfs41_client, root_entry)
/* nfs41_root */
int nfs41_root_create(
IN const char *name,
IN uint32_t sec_flavor,
IN uint32_t wsize,
IN uint32_t rsize,
OUT nfs41_root **root_out)
{
int status = NO_ERROR;
nfs41_root *root;
dprintf(NSLVL, "--> nfs41_root_create()\n");
root = calloc(1, sizeof(nfs41_root));
if (root == NULL) {
status = GetLastError();
goto out;
}
list_init(&root->clients);
root->wsize = wsize;
root->rsize = rsize;
InitializeCriticalSection(&root->lock);
root->ref_count = 1;
root->sec_flavor = sec_flavor;
/* generate a unique client_owner */
status = nfs41_client_owner(name, sec_flavor, &root->client_owner);
if (status) {
eprintf("nfs41_client_owner() failed with %d\n", status);
free(root);
goto out;
}
*root_out = root;
out:
dprintf(NSLVL, "<-- nfs41_root_create() returning %d\n", status);
return status;
}
static void root_free(
IN nfs41_root *root)
{
struct list_entry *entry, *tmp;
dprintf(NSLVL, "--> nfs41_root_free()\n");
/* free clients */
list_for_each_tmp(entry, tmp, &root->clients)
nfs41_client_free(client_entry(entry));
DeleteCriticalSection(&root->lock);
free(root);
dprintf(NSLVL, "<-- nfs41_root_free()\n");
}
void nfs41_root_ref(
IN nfs41_root *root)
{
const LONG count = InterlockedIncrement(&root->ref_count);
dprintf(NSLVL, "nfs41_root_ref() count %d\n", count);
}
void nfs41_root_deref(
IN nfs41_root *root)
{
const LONG count = InterlockedDecrement(&root->ref_count);
dprintf(NSLVL, "nfs41_root_deref() count %d\n", count);
if (count == 0)
root_free(root);
}
/* root_client_find_addrs() */
struct cl_addr_info {
const multi_addr4 *addrs;
uint32_t roles;
};
static int cl_addr_compare(
IN const struct list_entry *entry,
IN const void *value)
{
nfs41_client *client = client_entry(entry);
const struct cl_addr_info *info = (const struct cl_addr_info*)value;
uint32_t i, roles;
/* match any of the desired roles */
AcquireSRWLockShared(&client->exid_lock);
roles = info->roles & client->roles;
ReleaseSRWLockShared(&client->exid_lock);
if (roles == 0)
return ERROR_FILE_NOT_FOUND;
/* match any address in 'addrs' with any address in client->rpc->addrs */
for (i = 0; i < info->addrs->count; i++)
if (multi_addr_find(&client->rpc->addrs, &info->addrs->arr[i], NULL))
return NO_ERROR;
return ERROR_FILE_NOT_FOUND;
}
static int root_client_find_addrs(
IN nfs41_root *root,
IN const multi_addr4 *addrs,
IN bool_t is_data,
OUT nfs41_client **client_out)
{
struct cl_addr_info info;
struct list_entry *entry;
int status;
dprintf(NSLVL, "--> root_client_find_addrs()\n");
info.addrs = addrs;
info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
entry = list_search(&root->clients, &info, cl_addr_compare);
if (entry) {
*client_out = client_entry(entry);
status = NO_ERROR;
dprintf(NSLVL, "<-- root_client_find_addrs() returning 0x%p\n",
*client_out);
} else {
status = ERROR_FILE_NOT_FOUND;
dprintf(NSLVL, "<-- root_client_find_addrs() failed with %d\n",
status);
}
return status;
}
/* root_client_find() */
struct cl_exid_info {
const nfs41_exchange_id_res *exchangeid;
uint32_t roles;
};
static int cl_exid_compare(
IN const struct list_entry *entry,
IN const void *value)
{
nfs41_client *client = client_entry(entry);
const struct cl_exid_info *info = (const struct cl_exid_info*)value;
int status = ERROR_FILE_NOT_FOUND;
AcquireSRWLockShared(&client->exid_lock);
/* match any of the desired roles */
if ((info->roles & client->roles) == 0)
goto out;
/* match server_owner.major_id */
if (strncmp(info->exchangeid->server_owner.so_major_id,
client->server->owner, NFS4_OPAQUE_LIMIT) != 0)
goto out;
/* match server_scope */
if (strncmp(info->exchangeid->server_scope,
client->server->scope, NFS4_OPAQUE_LIMIT) != 0)
goto out;
/* match clientid */
if (info->exchangeid->clientid != client->clnt_id)
goto out;
status = NO_ERROR;
out:
ReleaseSRWLockShared(&client->exid_lock);
return status;
}
static int root_client_find(
IN nfs41_root *root,
IN const nfs41_exchange_id_res *exchangeid,
IN bool_t is_data,
OUT nfs41_client **client_out)
{
struct cl_exid_info info;
struct list_entry *entry;
int status;
dprintf(NSLVL, "--> root_client_find()\n");
info.exchangeid = exchangeid;
info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
entry = list_search(&root->clients, &info, cl_exid_compare);
if (entry) {
*client_out = client_entry(entry);
status = NO_ERROR;
dprintf(NSLVL, "<-- root_client_find() returning 0x%p\n",
*client_out);
} else {
status = ERROR_FILE_NOT_FOUND;
dprintf(NSLVL, "<-- root_client_find() failed with %d\n",
status);
}
return status;
}
static int session_get_lease(
IN nfs41_session *session,
IN OPTIONAL uint32_t lease_time)
{
bool_t use_mds_lease;
int status;
/* http://tools.ietf.org/html/rfc5661#section-13.1.1
* 13.1.1. Sessions Considerations for Data Servers:
* If the reply to EXCHANGE_ID has just the EXCHGID4_FLAG_USE_PNFS_DS role
* set, then (as noted in Section 13.6) the client will not be able to
* determine the data server's lease_time attribute because GETATTR will
* not be permitted. Instead, the rule is that any time a client
* receives a layout referring it to a data server that returns just the
* EXCHGID4_FLAG_USE_PNFS_DS role, the client MAY assume that the
* lease_time attribute from the metadata server that returned the
* layout applies to the data server. */
AcquireSRWLockShared(&session->client->exid_lock);
use_mds_lease = session->client->roles == EXCHGID4_FLAG_USE_PNFS_DS;
ReleaseSRWLockShared(&session->client->exid_lock);
if (!use_mds_lease) {
/* the client is allowed to GETATTR, so query the lease_time */
nfs41_file_info info = { 0 };
bitmap4 attr_request = { 1, { FATTR4_WORD0_LEASE_TIME, 0, 0 } };
status = nfs41_getattr(session, NULL, &attr_request, &info);
if (status) {
eprintf("nfs41_getattr() failed with %s\n",
nfs_error_string(status));
status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
goto out;
}
lease_time = info.lease_time;
}
status = nfs41_session_set_lease(session, lease_time);
if (status) {
eprintf("nfs41_session_set_lease() failed %d\n", status);
goto out;
}
out:
return status;
}
static int root_client_create(
IN nfs41_root *root,
IN nfs41_rpc_clnt *rpc,
IN bool_t is_data,
IN OPTIONAL uint32_t lease_time,
IN const nfs41_exchange_id_res *exchangeid,
OUT nfs41_client **client_out)
{
nfs41_client *client;
nfs41_session *session;
int status;
/* create client (transfers ownership of rpc to client) */
status = nfs41_client_create(rpc, &root->client_owner,
is_data, exchangeid, &client);
if (status) {
eprintf("nfs41_client_create() failed with %d\n", status);
goto out;
}
client->root = root;
rpc->client = client;
/* create session (and client takes ownership) */
status = nfs41_session_create(client, &session);
if (status) {
eprintf("nfs41_session_create failed %d\n", status);
goto out_err;
}
if (!is_data) {
/* send RECLAIM_COMPLETE, but don't fail on ERR_NOTSUPP */
status = nfs41_reclaim_complete(session);
if (status && status != NFS4ERR_NOTSUPP) {
eprintf("nfs41_reclaim_complete() failed with %s\n",
nfs_error_string(status));
status = ERROR_BAD_NETPATH;
goto out_err;
}
}
/* get least time and start session renewal thread */
status = session_get_lease(session, lease_time);
if (status)
goto out_err;
*client_out = client;
out:
return status;
out_err:
nfs41_client_free(client);
goto out;
}
int nfs41_root_mount_addrs(
IN nfs41_root *root,
IN const multi_addr4 *addrs,
IN bool_t is_data,
IN OPTIONAL uint32_t lease_time,
OUT nfs41_client **client_out)
{
nfs41_exchange_id_res exchangeid = { 0 };
nfs41_rpc_clnt *rpc;
nfs41_client *client, *existing;
int status;
dprintf(NSLVL, "--> nfs41_root_mount_addrs()\n");
/* look for an existing client that matches the address and role */
EnterCriticalSection(&root->lock);
status = root_client_find_addrs(root, addrs, is_data, &client);
LeaveCriticalSection(&root->lock);
if (status == NO_ERROR)
goto out;
/* create an rpc client */
status = nfs41_rpc_clnt_create(addrs, root->wsize, root->rsize,
root->uid, root->gid, root->sec_flavor, &rpc);
if (status) {
eprintf("nfs41_rpc_clnt_create() failed %d\n", status);
goto out;
}
/* get a clientid with exchangeid */
status = nfs41_exchange_id(rpc, &root->client_owner,
nfs41_exchange_id_flags(is_data), &exchangeid);
if (status) {
eprintf("nfs41_exchange_id() failed %s\n", nfs_error_string(status));
status = ERROR_BAD_NET_RESP;
goto out_free_rpc;
}
/* attempt to match existing clients by the exchangeid response */
EnterCriticalSection(&root->lock);
status = root_client_find(root, &exchangeid, is_data, &client);
LeaveCriticalSection(&root->lock);
if (status == NO_ERROR)
goto out_free_rpc;
/* create a client for this clientid */
status = root_client_create(root, rpc, is_data,
lease_time, &exchangeid, &client);
if (status) {
eprintf("nfs41_client_create() failed %d\n", status);
/* root_client_create takes care of cleaning up
* thus don't go to out_free_rpc */
goto out;
}
/* because we don't hold the root's lock over session creation,
* we could end up creating multiple clients with the same
* server and roles */
EnterCriticalSection(&root->lock);
status = root_client_find(root, &exchangeid, is_data, &existing);
if (status) {
dprintf(NSLVL, "caching new client 0x%p\n", client);
/* the client is not a duplicate, so add it to the list */
list_add_tail(&root->clients, &client->root_entry);
status = NO_ERROR;
} else {
dprintf(NSLVL, "created a duplicate client 0x%p! using "
"existing client 0x%p instead\n", client, existing);
/* a matching client has been created in parallel, so free
* the one we created and use the existing client instead */
nfs41_client_free(client);
client = existing;
}
LeaveCriticalSection(&root->lock);
out:
if (status == NO_ERROR)
*client_out = client;
dprintf(NSLVL, "<-- nfs41_root_mount_addrs() returning %d\n", status);
return status;
out_free_rpc:
nfs41_rpc_clnt_free(rpc);
goto out;
}
/* http://tools.ietf.org/html/rfc5661#section-11.9
* 11.9. The Attribute fs_locations
* An entry in the server array is a UTF-8 string and represents one of a
* traditional DNS host name, IPv4 address, IPv6 address, or a zero-length
* string. An IPv4 or IPv6 address is represented as a universal address
* (see Section 3.3.9 and [15]), minus the netid, and either with or without
* the trailing ".p1.p2" suffix that represents the port number. If the
* suffix is omitted, then the default port, 2049, SHOULD be assumed. A
* zero-length string SHOULD be used to indicate the current address being
* used for the RPC call. */
static int referral_mount_location(
IN nfs41_root *root,
IN const fs_location4 *loc,
OUT nfs41_client **client_out)
{
multi_addr4 addrs;
int status = ERROR_BAD_NET_NAME;
uint32_t i;
/* create a client and session for the first available server */
for (i = 0; i < loc->server_count; i++) {
/* XXX: only deals with 'address' as a hostname with default port */
status = nfs41_server_resolve(loc->servers[i].address, 2049, &addrs);
if (status) continue;
status = nfs41_root_mount_addrs(root, &addrs, 0, 0, client_out);
if (status == NO_ERROR)
break;
}
return status;
}
int nfs41_root_mount_referral(
IN nfs41_root *root,
IN const fs_locations4 *locations,
OUT const fs_location4 **loc_out,
OUT nfs41_client **client_out)
{
int status = ERROR_BAD_NET_NAME;
uint32_t i;
/* establish a mount to the first available location */
for (i = 0; i < locations->location_count; i++) {
status = referral_mount_location(root,
&locations->locations[i], client_out);
if (status == NO_ERROR) {
*loc_out = &locations->locations[i];
break;
}
}
return status;
}