[SCHEDSVC] Add stubs for the wlnotify control codes
[reactos.git] / base / services / nfsd / delegation.c
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 "delegation.h"
23 #include "nfs41_ops.h"
24 #include "name_cache.h"
25 #include "util.h"
26 #include "daemon_debug.h"
27
28 #include <devioctl.h>
29 #include "nfs41_driver.h" /* for making downcall to invalidate cache */
30 #include "util.h"
31
32 #define DGLVL 2 /* dprintf level for delegation logging */
33
34
35 /* allocation and reference counting */
36 static int delegation_create(
37 IN const nfs41_path_fh *parent,
38 IN const nfs41_path_fh *file,
39 IN const open_delegation4 *delegation,
40 OUT nfs41_delegation_state **deleg_out)
41 {
42 nfs41_delegation_state *state;
43 int status = NO_ERROR;
44
45 state = calloc(1, sizeof(nfs41_delegation_state));
46 if (state == NULL) {
47 status = GetLastError();
48 goto out;
49 }
50
51 memcpy(&state->state, delegation, sizeof(open_delegation4));
52
53 abs_path_copy(&state->path, file->path);
54 path_fh_init(&state->file, &state->path);
55 fh_copy(&state->file.fh, &file->fh);
56 path_fh_init(&state->parent, &state->path);
57 last_component(state->path.path, state->file.name.name,
58 &state->parent.name);
59 fh_copy(&state->parent.fh, &parent->fh);
60
61 list_init(&state->client_entry);
62 state->status = DELEGATION_GRANTED;
63 InitializeSRWLock(&state->lock);
64 InitializeConditionVariable(&state->cond);
65 state->ref_count = 1;
66 *deleg_out = state;
67 out:
68 return status;
69 }
70
71 void nfs41_delegation_ref(
72 IN nfs41_delegation_state *state)
73 {
74 const LONG count = InterlockedIncrement(&state->ref_count);
75 dprintf(DGLVL, "nfs41_delegation_ref(%s) count %d\n",
76 state->path.path, count);
77 }
78
79 void nfs41_delegation_deref(
80 IN nfs41_delegation_state *state)
81 {
82 const LONG count = InterlockedDecrement(&state->ref_count);
83 dprintf(DGLVL, "nfs41_delegation_deref(%s) count %d\n",
84 state->path.path, count);
85 if (count == 0)
86 free(state);
87 }
88
89 #define open_entry(pos) list_container(pos, nfs41_open_state, client_entry)
90
91 static void delegation_remove(
92 IN nfs41_client *client,
93 IN nfs41_delegation_state *deleg)
94 {
95 struct list_entry *entry;
96
97 /* remove from the client's list */
98 EnterCriticalSection(&client->state.lock);
99 list_remove(&deleg->client_entry);
100
101 /* remove from each associated open */
102 list_for_each(entry, &client->state.opens) {
103 nfs41_open_state *open = open_entry(entry);
104 AcquireSRWLockExclusive(&open->lock);
105 if (open->delegation.state == deleg) {
106 /* drop the delegation reference */
107 nfs41_delegation_deref(open->delegation.state);
108 open->delegation.state = NULL;
109 }
110 ReleaseSRWLockExclusive(&open->lock);
111 }
112 LeaveCriticalSection(&client->state.lock);
113
114 /* signal threads waiting on delegreturn */
115 AcquireSRWLockExclusive(&deleg->lock);
116 deleg->status = DELEGATION_RETURNED;
117 WakeAllConditionVariable(&deleg->cond);
118 ReleaseSRWLockExclusive(&deleg->lock);
119
120 /* release the client's reference */
121 nfs41_delegation_deref(deleg);
122 }
123
124
125 /* delegation return */
126 #define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry)
127
128 static bool_t has_delegated_locks(
129 IN nfs41_open_state *open)
130 {
131 struct list_entry *entry;
132 list_for_each(entry, &open->locks.list) {
133 if (lock_entry(entry)->delegated)
134 return TRUE;
135 }
136 return FALSE;
137 }
138
139 static int open_deleg_cmp(const struct list_entry *entry, const void *value)
140 {
141 nfs41_open_state *open = open_entry(entry);
142 int result = -1;
143
144 /* open must match the delegation and have state to reclaim */
145 AcquireSRWLockShared(&open->lock);
146 if (open->delegation.state != value) goto out;
147 if (open->do_close && !has_delegated_locks(open)) goto out;
148 result = 0;
149 out:
150 ReleaseSRWLockShared(&open->lock);
151 return result;
152 }
153
154 /* find the first open that needs recovery */
155 static nfs41_open_state* deleg_open_find(
156 IN struct client_state *state,
157 IN const nfs41_delegation_state *deleg)
158 {
159 struct list_entry *entry;
160 nfs41_open_state *open = NULL;
161
162 EnterCriticalSection(&state->lock);
163 entry = list_search(&state->opens, deleg, open_deleg_cmp);
164 if (entry) {
165 open = open_entry(entry);
166 nfs41_open_state_ref(open); /* return a reference */
167 }
168 LeaveCriticalSection(&state->lock);
169 return open;
170 }
171
172 /* find the first lock that needs recovery */
173 static bool_t deleg_lock_find(
174 IN nfs41_open_state *open,
175 OUT nfs41_lock_state *lock_out)
176 {
177 struct list_entry *entry;
178 bool_t found = FALSE;
179
180 AcquireSRWLockShared(&open->lock);
181 list_for_each(entry, &open->locks.list) {
182 nfs41_lock_state *lock = lock_entry(entry);
183 if (lock->delegated) {
184 /* copy offset, length, type */
185 lock_out->offset = lock->offset;
186 lock_out->length = lock->length;
187 lock_out->exclusive = lock->exclusive;
188 lock_out->id = lock->id;
189 found = TRUE;
190 break;
191 }
192 }
193 ReleaseSRWLockShared(&open->lock);
194 return found;
195 }
196
197 /* find the matching lock by id, and reset lock.delegated */
198 static void deleg_lock_update(
199 IN nfs41_open_state *open,
200 IN const nfs41_lock_state *source)
201 {
202 struct list_entry *entry;
203
204 AcquireSRWLockExclusive(&open->lock);
205 list_for_each(entry, &open->locks.list) {
206 nfs41_lock_state *lock = lock_entry(entry);
207 if (lock->id == source->id) {
208 lock->delegated = FALSE;
209 break;
210 }
211 }
212 ReleaseSRWLockExclusive(&open->lock);
213 }
214
215 static int delegation_flush_locks(
216 IN nfs41_open_state *open,
217 IN bool_t try_recovery)
218 {
219 stateid_arg stateid;
220 nfs41_lock_state lock;
221 int status = NFS4_OK;
222
223 stateid.open = open;
224 stateid.delegation = NULL;
225
226 /* get the starting open/lock stateid */
227 AcquireSRWLockShared(&open->lock);
228 if (open->locks.stateid.seqid) {
229 memcpy(&stateid.stateid, &open->locks.stateid, sizeof(stateid4));
230 stateid.type = STATEID_LOCK;
231 } else {
232 memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4));
233 stateid.type = STATEID_OPEN;
234 }
235 ReleaseSRWLockShared(&open->lock);
236
237 /* send LOCK requests for each delegated lock range */
238 while (deleg_lock_find(open, &lock)) {
239 status = nfs41_lock(open->session, &open->file,
240 &open->owner, lock.exclusive ? WRITE_LT : READ_LT,
241 lock.offset, lock.length, FALSE, try_recovery, &stateid);
242 if (status)
243 break;
244 deleg_lock_update(open, &lock);
245 }
246
247 /* save the updated lock stateid */
248 if (stateid.type == STATEID_LOCK) {
249 AcquireSRWLockExclusive(&open->lock);
250 if (open->locks.stateid.seqid == 0) {
251 /* if it's a new lock stateid, copy it in */
252 memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4));
253 } else if (stateid.stateid.seqid > open->locks.stateid.seqid) {
254 /* update the seqid if it's more recent */
255 open->locks.stateid.seqid = stateid.stateid.seqid;
256 }
257 ReleaseSRWLockExclusive(&open->lock);
258 }
259 return status;
260 }
261
262 #pragma warning (disable : 4706) /* assignment within conditional expression */
263
264 static int delegation_return(
265 IN nfs41_client *client,
266 IN nfs41_delegation_state *deleg,
267 IN bool_t truncate,
268 IN bool_t try_recovery)
269 {
270 stateid_arg stateid;
271 nfs41_open_state *open;
272 int status;
273
274 if (deleg->srv_open) {
275 /* make an upcall to the kernel: invalide data cache */
276 HANDLE pipe;
277 unsigned char inbuf[sizeof(HANDLE)], *buffer = inbuf;
278 DWORD inbuf_len = sizeof(HANDLE), outbuf_len, dstatus;
279 uint32_t length;
280 dprintf(1, "delegation_return: making a downcall for srv_open=%x\n",
281 deleg->srv_open);
282 pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ|GENERIC_WRITE,
283 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
284 if (pipe == INVALID_HANDLE_VALUE) {
285 eprintf("delegation_return: Unable to open downcall pipe %d\n",
286 GetLastError());
287 goto out_downcall;
288 }
289 length = inbuf_len;
290 safe_write(&buffer, &length, &deleg->srv_open, sizeof(HANDLE));
291
292 dstatus = DeviceIoControl(pipe, IOCTL_NFS41_INVALCACHE, inbuf, inbuf_len,
293 NULL, 0, (LPDWORD)&outbuf_len, NULL);
294 if (!dstatus)
295 eprintf("IOCTL_NFS41_INVALCACHE failed %d\n", GetLastError());
296 CloseHandle(pipe);
297 }
298 out_downcall:
299
300 /* recover opens and locks associated with the delegation */
301 while (open = deleg_open_find(&client->state, deleg)) {
302 status = nfs41_delegation_to_open(open, try_recovery);
303 if (status == NFS4_OK)
304 status = delegation_flush_locks(open, try_recovery);
305 nfs41_open_state_deref(open);
306
307 if (status)
308 break;
309 }
310
311 /* return the delegation */
312 stateid.type = STATEID_DELEG_FILE;
313 stateid.open = NULL;
314 stateid.delegation = deleg;
315 AcquireSRWLockShared(&deleg->lock);
316 memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4));
317 ReleaseSRWLockShared(&deleg->lock);
318
319 status = nfs41_delegreturn(client->session,
320 &deleg->file, &stateid, try_recovery);
321 if (status == NFS4ERR_BADSESSION)
322 goto out;
323
324 delegation_remove(client, deleg);
325 out:
326 return status;
327 }
328
329 /* open delegation */
330 int nfs41_delegation_granted(
331 IN nfs41_session *session,
332 IN nfs41_path_fh *parent,
333 IN nfs41_path_fh *file,
334 IN open_delegation4 *delegation,
335 IN bool_t try_recovery,
336 OUT nfs41_delegation_state **deleg_out)
337 {
338 stateid_arg stateid;
339 nfs41_client *client = session->client;
340 nfs41_delegation_state *state;
341 int status = NO_ERROR;
342
343 if (delegation->type != OPEN_DELEGATE_READ &&
344 delegation->type != OPEN_DELEGATE_WRITE)
345 goto out;
346
347 if (delegation->recalled) {
348 status = NFS4ERR_DELEG_REVOKED;
349 goto out_return;
350 }
351
352 /* allocate the delegation state */
353 status = delegation_create(parent, file, delegation, &state);
354 if (status)
355 goto out_return;
356
357 /* register the delegation with the client */
358 EnterCriticalSection(&client->state.lock);
359 /* XXX: check for duplicates by fh and stateid? */
360 list_add_tail(&client->state.delegations, &state->client_entry);
361 LeaveCriticalSection(&client->state.lock);
362
363 nfs41_delegation_ref(state); /* return a reference */
364 *deleg_out = state;
365 out:
366 return status;
367
368 out_return: /* return the delegation on failure */
369 memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4));
370 stateid.type = STATEID_DELEG_FILE;
371 stateid.open = NULL;
372 stateid.delegation = NULL;
373 nfs41_delegreturn(session, file, &stateid, try_recovery);
374 goto out;
375 }
376
377 #define deleg_entry(pos) list_container(pos, nfs41_delegation_state, client_entry)
378
379 static int deleg_file_cmp(const struct list_entry *entry, const void *value)
380 {
381 const nfs41_fh *lhs = &deleg_entry(entry)->file.fh;
382 const nfs41_fh *rhs = (const nfs41_fh*)value;
383 if (lhs->superblock != rhs->superblock) return -1;
384 if (lhs->fileid != rhs->fileid) return -1;
385 return 0;
386 }
387
388 static bool_t delegation_compatible(
389 IN enum open_delegation_type4 type,
390 IN uint32_t create,
391 IN uint32_t access,
392 IN uint32_t deny)
393 {
394 switch (type) {
395 case OPEN_DELEGATE_WRITE:
396 /* An OPEN_DELEGATE_WRITE delegation allows the client to handle,
397 * on its own, all opens. */
398 return TRUE;
399
400 case OPEN_DELEGATE_READ:
401 /* An OPEN_DELEGATE_READ delegation allows a client to handle,
402 * on its own, requests to open a file for reading that do not
403 * deny OPEN4_SHARE_ACCESS_READ access to others. */
404 if (create == OPEN4_CREATE)
405 return FALSE;
406 if (access & OPEN4_SHARE_ACCESS_WRITE || deny & OPEN4_SHARE_DENY_READ)
407 return FALSE;
408 return TRUE;
409
410 default:
411 return FALSE;
412 }
413 }
414
415 static int delegation_find(
416 IN nfs41_client *client,
417 IN const void *value,
418 IN list_compare_fn cmp,
419 OUT nfs41_delegation_state **deleg_out)
420 {
421 struct list_entry *entry;
422 int status = NFS4ERR_BADHANDLE;
423
424 EnterCriticalSection(&client->state.lock);
425 entry = list_search(&client->state.delegations, value, cmp);
426 if (entry) {
427 /* return a reference to the delegation */
428 *deleg_out = deleg_entry(entry);
429 nfs41_delegation_ref(*deleg_out);
430
431 /* move to the 'most recently used' end of the list */
432 list_remove(entry);
433 list_add_tail(&client->state.delegations, entry);
434 status = NFS4_OK;
435 }
436 LeaveCriticalSection(&client->state.lock);
437 return status;
438 }
439
440 static int delegation_truncate(
441 IN nfs41_delegation_state *deleg,
442 IN nfs41_client *client,
443 IN stateid_arg *stateid,
444 IN nfs41_file_info *info)
445 {
446 nfs41_superblock *superblock = deleg->file.fh.superblock;
447
448 /* use SETATTR to truncate the file */
449 info->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE |
450 FATTR4_WORD1_TIME_MODIFY_SET;
451
452 get_nfs_time(&info->time_create);
453 get_nfs_time(&info->time_modify);
454 info->time_delta = &superblock->time_delta;
455
456 /* mask out unsupported attributes */
457 nfs41_superblock_supported_attrs(superblock, &info->attrmask);
458
459 return nfs41_setattr(client->session, &deleg->file, stateid, info);
460 }
461
462 int nfs41_delegate_open(
463 IN nfs41_open_state *state,
464 IN uint32_t create,
465 IN OPTIONAL nfs41_file_info *createattrs,
466 OUT nfs41_file_info *info)
467 {
468 nfs41_client *client = state->session->client;
469 nfs41_path_fh *file = &state->file;
470 uint32_t access = state->share_access;
471 uint32_t deny = state->share_deny;
472 nfs41_delegation_state *deleg;
473 stateid_arg stateid;
474 int status;
475
476 /* search for a delegation with this filehandle */
477 status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg);
478 if (status)
479 goto out;
480
481 AcquireSRWLockExclusive(&deleg->lock);
482 if (deleg->status != DELEGATION_GRANTED) {
483 /* the delegation is being returned, wait for it to finish */
484 while (deleg->status != DELEGATION_RETURNED)
485 SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0);
486 status = NFS4ERR_BADHANDLE;
487 }
488 else if (!delegation_compatible(deleg->state.type, create, access, deny)) {
489 #ifdef DELEGATION_RETURN_ON_CONFLICT
490 /* this open will conflict, start the delegation return */
491 deleg->status = DELEGATION_RETURNING;
492 status = NFS4ERR_DELEG_REVOKED;
493 #else
494 status = NFS4ERR_BADHANDLE;
495 #endif
496 } else if (create == OPEN4_CREATE) {
497 /* copy the stateid for SETATTR */
498 stateid.open = NULL;
499 stateid.delegation = deleg;
500 stateid.type = STATEID_DELEG_FILE;
501 memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4));
502 }
503 if (!status) {
504 dprintf(1, "nfs41_delegate_open: updating srv_open from %x to %x\n",
505 deleg->srv_open, state->srv_open);
506 deleg->srv_open = state->srv_open;
507 }
508 ReleaseSRWLockExclusive(&deleg->lock);
509
510 if (status == NFS4ERR_DELEG_REVOKED)
511 goto out_return;
512 if (status)
513 goto out_deleg;
514
515 if (create == OPEN4_CREATE) {
516 memcpy(info, createattrs, sizeof(nfs41_file_info));
517
518 /* write delegations allow us to simulate OPEN4_CREATE with SETATTR */
519 status = delegation_truncate(deleg, client, &stateid, info);
520 if (status)
521 goto out_deleg;
522 }
523
524 /* TODO: check access against deleg->state.permissions or send ACCESS */
525
526 state->delegation.state = deleg;
527 status = NFS4_OK;
528 out:
529 return status;
530
531 out_return:
532 delegation_return(client, deleg, create == OPEN4_CREATE, TRUE);
533
534 out_deleg:
535 nfs41_delegation_deref(deleg);
536 goto out;
537 }
538
539 int nfs41_delegation_to_open(
540 IN nfs41_open_state *open,
541 IN bool_t try_recovery)
542 {
543 open_delegation4 ignore;
544 open_claim4 claim;
545 stateid4 open_stateid = { 0 };
546 stateid_arg deleg_stateid;
547 int status = NFS4_OK;
548
549 AcquireSRWLockExclusive(&open->lock);
550 if (open->delegation.state == NULL) /* no delegation to reclaim */
551 goto out_unlock;
552
553 if (open->do_close) /* already have an open stateid */
554 goto out_unlock;
555
556 /* if another thread is reclaiming the open stateid,
557 * wait for it to finish before returning success */
558 if (open->delegation.reclaim) {
559 do {
560 SleepConditionVariableSRW(&open->delegation.cond, &open->lock,
561 INFINITE, 0);
562 } while (open->delegation.reclaim);
563 if (open->do_close)
564 goto out_unlock;
565 }
566 open->delegation.reclaim = 1;
567
568 AcquireSRWLockShared(&open->delegation.state->lock);
569 deleg_stateid.open = open;
570 deleg_stateid.delegation = NULL;
571 deleg_stateid.type = STATEID_DELEG_FILE;
572 memcpy(&deleg_stateid.stateid, &open->delegation.state->state.stateid,
573 sizeof(stateid4));
574 ReleaseSRWLockShared(&open->delegation.state->lock);
575
576 ReleaseSRWLockExclusive(&open->lock);
577
578 /* send OPEN with CLAIM_DELEGATE_CUR */
579 claim.claim = CLAIM_DELEGATE_CUR;
580 claim.u.deleg_cur.delegate_stateid = &deleg_stateid;
581 claim.u.deleg_cur.name = &open->file.name;
582
583 status = nfs41_open(open->session, &open->parent, &open->file,
584 &open->owner, &claim, open->share_access, open->share_deny,
585 OPEN4_NOCREATE, 0, NULL, try_recovery, &open_stateid, &ignore, NULL);
586
587 AcquireSRWLockExclusive(&open->lock);
588 if (status == NFS4_OK) {
589 /* save the new open stateid */
590 memcpy(&open->stateid, &open_stateid, sizeof(stateid4));
591 open->do_close = 1;
592 } else if (open->do_close && (status == NFS4ERR_BAD_STATEID ||
593 status == NFS4ERR_STALE_STATEID || status == NFS4ERR_EXPIRED)) {
594 /* something triggered client state recovery, and the open stateid
595 * has already been reclaimed; see recover_stateid_delegation() */
596 status = NFS4_OK;
597 }
598 open->delegation.reclaim = 0;
599
600 /* signal anyone waiting on the open stateid */
601 WakeAllConditionVariable(&open->delegation.cond);
602 out_unlock:
603 ReleaseSRWLockExclusive(&open->lock);
604 if (status)
605 eprintf("nfs41_delegation_to_open(%p) failed with %s\n",
606 open, nfs_error_string(status));
607 return status;
608 }
609
610 void nfs41_delegation_remove_srvopen(
611 IN nfs41_session *session,
612 IN nfs41_path_fh *file)
613 {
614 nfs41_delegation_state *deleg = NULL;
615
616 /* find a delegation for this file */
617 if (delegation_find(session->client, &file->fh, deleg_file_cmp, &deleg))
618 return;
619 dprintf(1, "nfs41_delegation_remove_srvopen: removing reference to "
620 "srv_open=%x\n", deleg->srv_open);
621 AcquireSRWLockExclusive(&deleg->lock);
622 deleg->srv_open = NULL;
623 ReleaseSRWLockExclusive(&deleg->lock);
624 nfs41_delegation_deref(deleg);
625 }
626
627 /* synchronous delegation return */
628 #ifdef DELEGATION_RETURN_ON_CONFLICT
629 int nfs41_delegation_return(
630 IN nfs41_session *session,
631 IN nfs41_path_fh *file,
632 #ifndef __REACTOS__
633 IN enum open_delegation_type4 access,
634 #else
635 IN int access,
636 #endif
637 IN bool_t truncate)
638 {
639 nfs41_client *client = session->client;
640 nfs41_delegation_state *deleg;
641 int status;
642
643 /* find a delegation for this file */
644 status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg);
645 if (status)
646 goto out;
647
648 AcquireSRWLockExclusive(&deleg->lock);
649 if (deleg->status == DELEGATION_GRANTED) {
650 /* return unless delegation is write and access is read */
651 if (deleg->state.type != OPEN_DELEGATE_WRITE
652 || access != OPEN_DELEGATE_READ) {
653 deleg->status = DELEGATION_RETURNING;
654 status = NFS4ERR_DELEG_REVOKED;
655 }
656 } else {
657 /* the delegation is being returned, wait for it to finish */
658 while (deleg->status == DELEGATION_RETURNING)
659 SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0);
660 status = NFS4ERR_BADHANDLE;
661 }
662 ReleaseSRWLockExclusive(&deleg->lock);
663
664 if (status == NFS4ERR_DELEG_REVOKED) {
665 delegation_return(client, deleg, truncate, TRUE);
666 status = NFS4_OK;
667 }
668
669 nfs41_delegation_deref(deleg);
670 out:
671 return status;
672 }
673 #endif
674
675
676 /* asynchronous delegation recall */
677 struct recall_thread_args {
678 nfs41_client *client;
679 nfs41_delegation_state *delegation;
680 bool_t truncate;
681 };
682
683 static unsigned int WINAPI delegation_recall_thread(void *args)
684 {
685 struct recall_thread_args *recall = (struct recall_thread_args*)args;
686
687 delegation_return(recall->client, recall->delegation, recall->truncate, TRUE);
688
689 /* clean up thread arguments */
690 nfs41_delegation_deref(recall->delegation);
691 nfs41_root_deref(recall->client->root);
692 free(recall);
693 return 0;
694 }
695
696 static int deleg_stateid_cmp(const struct list_entry *entry, const void *value)
697 {
698 const stateid4 *lhs = &deleg_entry(entry)->state.stateid;
699 const stateid4 *rhs = (const stateid4*)value;
700 return memcmp(lhs->other, rhs->other, NFS4_STATEID_OTHER);
701 }
702
703 int nfs41_delegation_recall(
704 IN nfs41_client *client,
705 IN nfs41_fh *fh,
706 IN const stateid4 *stateid,
707 IN bool_t truncate)
708 {
709 nfs41_delegation_state *deleg;
710 struct recall_thread_args *args;
711 int status;
712
713 dprintf(2, "--> nfs41_delegation_recall()\n");
714
715 /* search for the delegation by stateid instead of filehandle;
716 * deleg_file_cmp() relies on a proper superblock and fileid,
717 * which we don't get with CB_RECALL */
718 status = delegation_find(client, stateid, deleg_stateid_cmp, &deleg);
719 if (status)
720 goto out;
721
722 AcquireSRWLockExclusive(&deleg->lock);
723 if (deleg->state.recalled) {
724 /* return BADHANDLE if we've already responded to CB_RECALL */
725 status = NFS4ERR_BADHANDLE;
726 } else {
727 deleg->state.recalled = 1;
728
729 if (deleg->status == DELEGATION_GRANTED) {
730 /* start the delegation return */
731 deleg->status = DELEGATION_RETURNING;
732 status = NFS4ERR_DELEG_REVOKED;
733 } /* else return NFS4_OK */
734 }
735 ReleaseSRWLockExclusive(&deleg->lock);
736
737 if (status != NFS4ERR_DELEG_REVOKED)
738 goto out_deleg;
739
740 /* allocate thread arguments */
741 args = calloc(1, sizeof(struct recall_thread_args));
742 if (args == NULL) {
743 status = NFS4ERR_SERVERFAULT;
744 eprintf("nfs41_delegation_recall() failed to allocate arguments\n");
745 goto out_deleg;
746 }
747
748 /* hold a reference on the root */
749 nfs41_root_ref(client->root);
750 args->client = client;
751 args->delegation = deleg;
752 args->truncate = truncate;
753
754 /* the callback thread can't make rpc calls, so spawn a separate thread */
755 if (_beginthreadex(NULL, 0, delegation_recall_thread, args, 0, NULL) == 0) {
756 status = NFS4ERR_SERVERFAULT;
757 eprintf("nfs41_delegation_recall() failed to start thread\n");
758 goto out_args;
759 }
760 status = NFS4_OK;
761 out:
762 dprintf(DGLVL, "<-- nfs41_delegation_recall() returning %s\n",
763 nfs_error_string(status));
764 return status;
765
766 out_args:
767 free(args);
768 nfs41_root_deref(client->root);
769 out_deleg:
770 nfs41_delegation_deref(deleg);
771 goto out;
772 }
773
774
775 static int deleg_fh_cmp(const struct list_entry *entry, const void *value)
776 {
777 const nfs41_fh *lhs = &deleg_entry(entry)->file.fh;
778 const nfs41_fh *rhs = (const nfs41_fh*)value;
779 if (lhs->len != rhs->len) return -1;
780 return memcmp(lhs->fh, rhs->fh, lhs->len);
781 }
782
783 int nfs41_delegation_getattr(
784 IN nfs41_client *client,
785 IN const nfs41_fh *fh,
786 IN const bitmap4 *attr_request,
787 OUT nfs41_file_info *info)
788 {
789 nfs41_delegation_state *deleg;
790 uint64_t fileid;
791 int status;
792
793 dprintf(2, "--> nfs41_delegation_getattr()\n");
794
795 /* search for a delegation on this file handle */
796 status = delegation_find(client, fh, deleg_fh_cmp, &deleg);
797 if (status)
798 goto out;
799
800 AcquireSRWLockShared(&deleg->lock);
801 fileid = deleg->file.fh.fileid;
802 if (deleg->status != DELEGATION_GRANTED ||
803 deleg->state.type != OPEN_DELEGATE_WRITE) {
804 status = NFS4ERR_BADHANDLE;
805 }
806 ReleaseSRWLockShared(&deleg->lock);
807 if (status)
808 goto out_deleg;
809
810 ZeroMemory(info, sizeof(nfs41_file_info));
811
812 /* find attributes for the given fileid */
813 status = nfs41_attr_cache_lookup(
814 client_name_cache(client), fileid, info);
815 if (status) {
816 status = NFS4ERR_BADHANDLE;
817 goto out_deleg;
818 }
819 out_deleg:
820 nfs41_delegation_deref(deleg);
821 out:
822 dprintf(DGLVL, "<-- nfs41_delegation_getattr() returning %s\n",
823 nfs_error_string(status));
824 return status;
825 }
826
827
828 void nfs41_client_delegation_free(
829 IN nfs41_client *client)
830 {
831 struct list_entry *entry, *tmp;
832
833 EnterCriticalSection(&client->state.lock);
834 list_for_each_tmp (entry, tmp, &client->state.delegations) {
835 list_remove(entry);
836 nfs41_delegation_deref(deleg_entry(entry));
837 }
838 LeaveCriticalSection(&client->state.lock);
839 }
840
841
842 static int delegation_recovery_status(
843 IN nfs41_delegation_state *deleg)
844 {
845 int status = NFS4_OK;
846
847 AcquireSRWLockExclusive(&deleg->lock);
848 if (deleg->status == DELEGATION_GRANTED) {
849 if (deleg->revoked) {
850 deleg->status = DELEGATION_RETURNED;
851 status = NFS4ERR_BADHANDLE;
852 } else if (deleg->state.recalled) {
853 deleg->status = DELEGATION_RETURNING;
854 status = NFS4ERR_DELEG_REVOKED;
855 }
856 }
857 ReleaseSRWLockExclusive(&deleg->lock);
858 return status;
859 }
860
861 int nfs41_client_delegation_recovery(
862 IN nfs41_client *client)
863 {
864 struct list_entry *entry, *tmp;
865 nfs41_delegation_state *deleg;
866 int status = NFS4_OK;
867
868 list_for_each_tmp(entry, tmp, &client->state.delegations) {
869 deleg = list_container(entry, nfs41_delegation_state, client_entry);
870
871 status = delegation_recovery_status(deleg);
872 switch (status) {
873 case NFS4ERR_DELEG_REVOKED:
874 /* the delegation was reclaimed, but flagged as recalled;
875 * return it with try_recovery=FALSE */
876 status = delegation_return(client, deleg, FALSE, FALSE);
877 break;
878
879 case NFS4ERR_BADHANDLE:
880 /* reclaim failed, so we have no delegation state on the server;
881 * 'forget' the delegation without trying to return it */
882 delegation_remove(client, deleg);
883 status = NFS4_OK;
884 break;
885 }
886
887 if (status == NFS4ERR_BADSESSION)
888 goto out;
889 }
890
891 /* use DELEGPURGE to indicate that we're done reclaiming delegations */
892 status = nfs41_delegpurge(client->session);
893
894 /* support for DELEGPURGE is optional; ignore any errors but BADSESSION */
895 if (status != NFS4ERR_BADSESSION)
896 status = NFS4_OK;
897 out:
898 return status;
899 }
900
901
902 int nfs41_client_delegation_return_lru(
903 IN nfs41_client *client)
904 {
905 struct list_entry *entry;
906 nfs41_delegation_state *state = NULL;
907 int status = NFS4ERR_BADHANDLE;
908
909 /* starting from the least recently opened, find and return
910 * the first delegation that's not 'in use' (currently open) */
911
912 /* TODO: use a more robust algorithm, taking into account:
913 * -number of total opens
914 * -time since last operation on an associated open, or
915 * -number of operations/second over last n seconds */
916 EnterCriticalSection(&client->state.lock);
917 list_for_each(entry, &client->state.delegations) {
918 state = deleg_entry(entry);
919
920 /* skip if it's currently in use for an open; note that ref_count
921 * can't go from 1 to 2 without holding client->state.lock */
922 if (state->ref_count > 1)
923 continue;
924
925 AcquireSRWLockExclusive(&state->lock);
926 if (state->status == DELEGATION_GRANTED) {
927 /* start returning the delegation */
928 state->status = DELEGATION_RETURNING;
929 status = NFS4ERR_DELEG_REVOKED;
930 }
931 ReleaseSRWLockExclusive(&state->lock);
932
933 if (status == NFS4ERR_DELEG_REVOKED)
934 break;
935 }
936 LeaveCriticalSection(&client->state.lock);
937
938 if (status == NFS4ERR_DELEG_REVOKED)
939 status = delegation_return(client, state, FALSE, TRUE);
940 return status;
941 }