/* NFSv4.1 client for Windows * Copyright © 2012 The Regents of the University of Michigan * * Olga Kornievskaia * Casey Bodley * * 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 #include #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; }