1 /* NFSv4.1 client for Windows
2 * Copyright � 2012 The Regents of the University of Michigan
3 *
4 * Olga Kornievskaia <aglo@umich.edu>
5 * Casey Bodley <cbodley@umich.edu>
6 *
7 * This library is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or (at
10 * your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful, but
13 * without any warranty; without even the implied warranty of merchantability
14 * or fitness for a particular purpose. See the GNU Lesser General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 */
21
22 #include <windows.h>
23 #include <strsafe.h>
24
25 #include "nfs41_ops.h"
26 #include "util.h"
27 #include "daemon_debug.h"
28
29
30 #define NSLVL 2 /* dprintf level for namespace logging */
31
32
33 #define client_entry(pos) list_container(pos, nfs41_client, root_entry)
34
35
36 /* nfs41_root */
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)37 int nfs41_root_create(
38 IN const char *name,
39 IN uint32_t sec_flavor,
40 IN uint32_t wsize,
41 IN uint32_t rsize,
42 OUT nfs41_root **root_out)
43 {
44 int status = NO_ERROR;
45 nfs41_root *root;
46
47 dprintf(NSLVL, "--> nfs41_root_create()\n");
48
49 root = calloc(1, sizeof(nfs41_root));
50 if (root == NULL) {
51 status = GetLastError();
52 goto out;
53 }
54
55 list_init(&root->clients);
56 root->wsize = wsize;
57 root->rsize = rsize;
58 InitializeCriticalSection(&root->lock);
59 root->ref_count = 1;
60 root->sec_flavor = sec_flavor;
61
62 /* generate a unique client_owner */
63 status = nfs41_client_owner(name, sec_flavor, &root->client_owner);
64 if (status) {
65 eprintf("nfs41_client_owner() failed with %d\n", status);
66 free(root);
67 goto out;
68 }
69
70 *root_out = root;
71 out:
72 dprintf(NSLVL, "<-- nfs41_root_create() returning %d\n", status);
73 return status;
74 }
75
root_free(IN nfs41_root * root)76 static void root_free(
77 IN nfs41_root *root)
78 {
79 struct list_entry *entry, *tmp;
80
81 dprintf(NSLVL, "--> nfs41_root_free()\n");
82
83 /* free clients */
84 list_for_each_tmp(entry, tmp, &root->clients)
85 nfs41_client_free(client_entry(entry));
86 DeleteCriticalSection(&root->lock);
87 free(root);
88
89 dprintf(NSLVL, "<-- nfs41_root_free()\n");
90 }
91
nfs41_root_ref(IN nfs41_root * root)92 void nfs41_root_ref(
93 IN nfs41_root *root)
94 {
95 const LONG count = InterlockedIncrement(&root->ref_count);
96
97 dprintf(NSLVL, "nfs41_root_ref() count %d\n", count);
98 }
99
nfs41_root_deref(IN nfs41_root * root)100 void nfs41_root_deref(
101 IN nfs41_root *root)
102 {
103 const LONG count = InterlockedDecrement(&root->ref_count);
104
105 dprintf(NSLVL, "nfs41_root_deref() count %d\n", count);
106 if (count == 0)
107 root_free(root);
108 }
109
110
111 /* root_client_find_addrs() */
112 struct cl_addr_info {
113 const multi_addr4 *addrs;
114 uint32_t roles;
115 };
116
cl_addr_compare(IN const struct list_entry * entry,IN const void * value)117 static int cl_addr_compare(
118 IN const struct list_entry *entry,
119 IN const void *value)
120 {
121 nfs41_client *client = client_entry(entry);
122 const struct cl_addr_info *info = (const struct cl_addr_info*)value;
123 uint32_t i, roles;
124
125 /* match any of the desired roles */
126 AcquireSRWLockShared(&client->exid_lock);
127 roles = info->roles & client->roles;
128 ReleaseSRWLockShared(&client->exid_lock);
129
130 if (roles == 0)
131 return ERROR_FILE_NOT_FOUND;
132
133 /* match any address in 'addrs' with any address in client->rpc->addrs */
134 for (i = 0; i < info->addrs->count; i++)
135 if (multi_addr_find(&client->rpc->addrs, &info->addrs->arr[i], NULL))
136 return NO_ERROR;
137
138 return ERROR_FILE_NOT_FOUND;
139 }
140
root_client_find_addrs(IN nfs41_root * root,IN const multi_addr4 * addrs,IN bool_t is_data,OUT nfs41_client ** client_out)141 static int root_client_find_addrs(
142 IN nfs41_root *root,
143 IN const multi_addr4 *addrs,
144 IN bool_t is_data,
145 OUT nfs41_client **client_out)
146 {
147 struct cl_addr_info info;
148 struct list_entry *entry;
149 int status;
150
151 dprintf(NSLVL, "--> root_client_find_addrs()\n");
152
153 info.addrs = addrs;
154 info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
155
156 entry = list_search(&root->clients, &info, cl_addr_compare);
157 if (entry) {
158 *client_out = client_entry(entry);
159 status = NO_ERROR;
160 dprintf(NSLVL, "<-- root_client_find_addrs() returning 0x%p\n",
161 *client_out);
162 } else {
163 status = ERROR_FILE_NOT_FOUND;
164 dprintf(NSLVL, "<-- root_client_find_addrs() failed with %d\n",
165 status);
166 }
167 return status;
168 }
169
170 /* root_client_find() */
171 struct cl_exid_info {
172 const nfs41_exchange_id_res *exchangeid;
173 uint32_t roles;
174 };
175
cl_exid_compare(IN const struct list_entry * entry,IN const void * value)176 static int cl_exid_compare(
177 IN const struct list_entry *entry,
178 IN const void *value)
179 {
180 nfs41_client *client = client_entry(entry);
181 const struct cl_exid_info *info = (const struct cl_exid_info*)value;
182 int status = ERROR_FILE_NOT_FOUND;
183
184 AcquireSRWLockShared(&client->exid_lock);
185
186 /* match any of the desired roles */
187 if ((info->roles & client->roles) == 0)
188 goto out;
189 /* match server_owner.major_id */
190 if (strncmp(info->exchangeid->server_owner.so_major_id,
191 client->server->owner, NFS4_OPAQUE_LIMIT) != 0)
192 goto out;
193 /* match server_scope */
194 if (strncmp(info->exchangeid->server_scope,
195 client->server->scope, NFS4_OPAQUE_LIMIT) != 0)
196 goto out;
197 /* match clientid */
198 if (info->exchangeid->clientid != client->clnt_id)
199 goto out;
200
201 status = NO_ERROR;
202 out:
203 ReleaseSRWLockShared(&client->exid_lock);
204 return status;
205 }
206
root_client_find(IN nfs41_root * root,IN const nfs41_exchange_id_res * exchangeid,IN bool_t is_data,OUT nfs41_client ** client_out)207 static int root_client_find(
208 IN nfs41_root *root,
209 IN const nfs41_exchange_id_res *exchangeid,
210 IN bool_t is_data,
211 OUT nfs41_client **client_out)
212 {
213 struct cl_exid_info info;
214 struct list_entry *entry;
215 int status;
216
217 dprintf(NSLVL, "--> root_client_find()\n");
218
219 info.exchangeid = exchangeid;
220 info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS;
221
222 entry = list_search(&root->clients, &info, cl_exid_compare);
223 if (entry) {
224 *client_out = client_entry(entry);
225 status = NO_ERROR;
226 dprintf(NSLVL, "<-- root_client_find() returning 0x%p\n",
227 *client_out);
228 } else {
229 status = ERROR_FILE_NOT_FOUND;
230 dprintf(NSLVL, "<-- root_client_find() failed with %d\n",
231 status);
232 }
233 return status;
234 }
235
session_get_lease(IN nfs41_session * session,IN OPTIONAL uint32_t lease_time)236 static int session_get_lease(
237 IN nfs41_session *session,
238 IN OPTIONAL uint32_t lease_time)
239 {
240 bool_t use_mds_lease;
241 int status;
242
243 /* http://tools.ietf.org/html/rfc5661#section-13.1.1
244 * 13.1.1. Sessions Considerations for Data Servers:
245 * If the reply to EXCHANGE_ID has just the EXCHGID4_FLAG_USE_PNFS_DS role
246 * set, then (as noted in Section 13.6) the client will not be able to
247 * determine the data server's lease_time attribute because GETATTR will
248 * not be permitted. Instead, the rule is that any time a client
249 * receives a layout referring it to a data server that returns just the
250 * EXCHGID4_FLAG_USE_PNFS_DS role, the client MAY assume that the
251 * lease_time attribute from the metadata server that returned the
252 * layout applies to the data server. */
253 AcquireSRWLockShared(&session->client->exid_lock);
254 use_mds_lease = session->client->roles == EXCHGID4_FLAG_USE_PNFS_DS;
255 ReleaseSRWLockShared(&session->client->exid_lock);
256
257 if (!use_mds_lease) {
258 /* the client is allowed to GETATTR, so query the lease_time */
259 nfs41_file_info info = { 0 };
260 bitmap4 attr_request = { 1, { FATTR4_WORD0_LEASE_TIME, 0, 0 } };
261
262 status = nfs41_getattr(session, NULL, &attr_request, &info);
263 if (status) {
264 eprintf("nfs41_getattr() failed with %s\n",
265 nfs_error_string(status));
266 status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
267 goto out;
268 }
269 lease_time = info.lease_time;
270 }
271
272 status = nfs41_session_set_lease(session, lease_time);
273 if (status) {
274 eprintf("nfs41_session_set_lease() failed %d\n", status);
275 goto out;
276 }
277 out:
278 return status;
279 }
280
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)281 static int root_client_create(
282 IN nfs41_root *root,
283 IN nfs41_rpc_clnt *rpc,
284 IN bool_t is_data,
285 IN OPTIONAL uint32_t lease_time,
286 IN const nfs41_exchange_id_res *exchangeid,
287 OUT nfs41_client **client_out)
288 {
289 nfs41_client *client;
290 nfs41_session *session;
291 int status;
292
293 /* create client (transfers ownership of rpc to client) */
294 status = nfs41_client_create(rpc, &root->client_owner,
295 is_data, exchangeid, &client);
296 if (status) {
297 eprintf("nfs41_client_create() failed with %d\n", status);
298 goto out;
299 }
300 client->root = root;
301 rpc->client = client;
302
303 /* create session (and client takes ownership) */
304 status = nfs41_session_create(client, &session);
305 if (status) {
306 eprintf("nfs41_session_create failed %d\n", status);
307 goto out_err;
308 }
309
310 if (!is_data) {
311 /* send RECLAIM_COMPLETE, but don't fail on ERR_NOTSUPP */
312 status = nfs41_reclaim_complete(session);
313 if (status && status != NFS4ERR_NOTSUPP) {
314 eprintf("nfs41_reclaim_complete() failed with %s\n",
315 nfs_error_string(status));
316 status = ERROR_BAD_NETPATH;
317 goto out_err;
318 }
319 }
320
321 /* get least time and start session renewal thread */
322 status = session_get_lease(session, lease_time);
323 if (status)
324 goto out_err;
325
326 *client_out = client;
327 out:
328 return status;
329
330 out_err:
331 nfs41_client_free(client);
332 goto out;
333 }
334
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)335 int nfs41_root_mount_addrs(
336 IN nfs41_root *root,
337 IN const multi_addr4 *addrs,
338 IN bool_t is_data,
339 IN OPTIONAL uint32_t lease_time,
340 OUT nfs41_client **client_out)
341 {
342 nfs41_exchange_id_res exchangeid = { 0 };
343 nfs41_rpc_clnt *rpc;
344 nfs41_client *client, *existing;
345 int status;
346
347 dprintf(NSLVL, "--> nfs41_root_mount_addrs()\n");
348
349 /* look for an existing client that matches the address and role */
350 EnterCriticalSection(&root->lock);
351 status = root_client_find_addrs(root, addrs, is_data, &client);
352 LeaveCriticalSection(&root->lock);
353
354 if (status == NO_ERROR)
355 goto out;
356
357 /* create an rpc client */
358 status = nfs41_rpc_clnt_create(addrs, root->wsize, root->rsize,
359 root->uid, root->gid, root->sec_flavor, &rpc);
360 if (status) {
361 eprintf("nfs41_rpc_clnt_create() failed %d\n", status);
362 goto out;
363 }
364
365 /* get a clientid with exchangeid */
366 status = nfs41_exchange_id(rpc, &root->client_owner,
367 nfs41_exchange_id_flags(is_data), &exchangeid);
368 if (status) {
369 eprintf("nfs41_exchange_id() failed %s\n", nfs_error_string(status));
370 status = ERROR_BAD_NET_RESP;
371 goto out_free_rpc;
372 }
373
374 /* attempt to match existing clients by the exchangeid response */
375 EnterCriticalSection(&root->lock);
376 status = root_client_find(root, &exchangeid, is_data, &client);
377 LeaveCriticalSection(&root->lock);
378
379 if (status == NO_ERROR)
380 goto out_free_rpc;
381
382 /* create a client for this clientid */
383 status = root_client_create(root, rpc, is_data,
384 lease_time, &exchangeid, &client);
385 if (status) {
386 eprintf("nfs41_client_create() failed %d\n", status);
387 /* root_client_create takes care of cleaning up
388 * thus don't go to out_free_rpc */
389 goto out;
390 }
391
392 /* because we don't hold the root's lock over session creation,
393 * we could end up creating multiple clients with the same
394 * server and roles */
395 EnterCriticalSection(&root->lock);
396 status = root_client_find(root, &exchangeid, is_data, &existing);
397
398 if (status) {
399 dprintf(NSLVL, "caching new client 0x%p\n", client);
400
401 /* the client is not a duplicate, so add it to the list */
402 list_add_tail(&root->clients, &client->root_entry);
403 status = NO_ERROR;
404 } else {
405 dprintf(NSLVL, "created a duplicate client 0x%p! using "
406 "existing client 0x%p instead\n", client, existing);
407
408 /* a matching client has been created in parallel, so free
409 * the one we created and use the existing client instead */
410 nfs41_client_free(client);
411 client = existing;
412 }
413 LeaveCriticalSection(&root->lock);
414
415 out:
416 if (status == NO_ERROR)
417 *client_out = client;
418 dprintf(NSLVL, "<-- nfs41_root_mount_addrs() returning %d\n", status);
419 return status;
420
421 out_free_rpc:
422 nfs41_rpc_clnt_free(rpc);
423 goto out;
424 }
425
426
427 /* http://tools.ietf.org/html/rfc5661#section-11.9
428 * 11.9. The Attribute fs_locations
429 * An entry in the server array is a UTF-8 string and represents one of a
430 * traditional DNS host name, IPv4 address, IPv6 address, or a zero-length
431 * string. An IPv4 or IPv6 address is represented as a universal address
432 * (see Section 3.3.9 and [15]), minus the netid, and either with or without
433 * the trailing ".p1.p2" suffix that represents the port number. If the
434 * suffix is omitted, then the default port, 2049, SHOULD be assumed. A
435 * zero-length string SHOULD be used to indicate the current address being
436 * used for the RPC call. */
referral_mount_location(IN nfs41_root * root,IN const fs_location4 * loc,OUT nfs41_client ** client_out)437 static int referral_mount_location(
438 IN nfs41_root *root,
439 IN const fs_location4 *loc,
440 OUT nfs41_client **client_out)
441 {
442 multi_addr4 addrs;
443 int status = ERROR_BAD_NET_NAME;
444 uint32_t i;
445
446 /* create a client and session for the first available server */
447 for (i = 0; i < loc->server_count; i++) {
448 /* XXX: only deals with 'address' as a hostname with default port */
449 status = nfs41_server_resolve(loc->servers[i].address, 2049, &addrs);
450 if (status) continue;
451
452 status = nfs41_root_mount_addrs(root, &addrs, 0, 0, client_out);
453 if (status == NO_ERROR)
454 break;
455 }
456 return status;
457 }
458
nfs41_root_mount_referral(IN nfs41_root * root,IN const fs_locations4 * locations,OUT const fs_location4 ** loc_out,OUT nfs41_client ** client_out)459 int nfs41_root_mount_referral(
460 IN nfs41_root *root,
461 IN const fs_locations4 *locations,
462 OUT const fs_location4 **loc_out,
463 OUT nfs41_client **client_out)
464 {
465 int status = ERROR_BAD_NET_NAME;
466 uint32_t i;
467
468 /* establish a mount to the first available location */
469 for (i = 0; i < locations->location_count; i++) {
470 status = referral_mount_location(root,
471 &locations->locations[i], client_out);
472 if (status == NO_ERROR) {
473 *loc_out = &locations->locations[i];
474 break;
475 }
476 }
477 return status;
478 }
479