xref: /reactos/base/services/nfsd/delegation.c (revision c2c66aff)
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 */
delegation_create(IN const nfs41_path_fh * parent,IN const nfs41_path_fh * file,IN const open_delegation4 * delegation,OUT nfs41_delegation_state ** deleg_out)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 
nfs41_delegation_ref(IN nfs41_delegation_state * state)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 
nfs41_delegation_deref(IN nfs41_delegation_state * state)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 
delegation_remove(IN nfs41_client * client,IN nfs41_delegation_state * deleg)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 
has_delegated_locks(IN nfs41_open_state * open)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 
open_deleg_cmp(const struct list_entry * entry,const void * value)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 */
deleg_open_find(IN struct client_state * state,IN const nfs41_delegation_state * deleg)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 */
deleg_lock_find(IN nfs41_open_state * open,OUT nfs41_lock_state * lock_out)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 */
deleg_lock_update(IN nfs41_open_state * open,IN const nfs41_lock_state * source)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 
delegation_flush_locks(IN nfs41_open_state * open,IN bool_t try_recovery)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 
delegation_return(IN nfs41_client * client,IN nfs41_delegation_state * deleg,IN bool_t truncate,IN bool_t try_recovery)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 */
nfs41_delegation_granted(IN nfs41_session * session,IN nfs41_path_fh * parent,IN nfs41_path_fh * file,IN open_delegation4 * delegation,IN bool_t try_recovery,OUT nfs41_delegation_state ** deleg_out)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 
deleg_file_cmp(const struct list_entry * entry,const void * value)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 
delegation_compatible(IN enum open_delegation_type4 type,IN uint32_t create,IN uint32_t access,IN uint32_t deny)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 
delegation_find(IN nfs41_client * client,IN const void * value,IN list_compare_fn cmp,OUT nfs41_delegation_state ** deleg_out)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 
delegation_truncate(IN nfs41_delegation_state * deleg,IN nfs41_client * client,IN stateid_arg * stateid,IN nfs41_file_info * info)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 
nfs41_delegate_open(IN nfs41_open_state * state,IN uint32_t create,IN OPTIONAL nfs41_file_info * createattrs,OUT nfs41_file_info * info)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 
nfs41_delegation_to_open(IN nfs41_open_state * open,IN bool_t try_recovery)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 
nfs41_delegation_remove_srvopen(IN nfs41_session * session,IN nfs41_path_fh * file)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
nfs41_delegation_return(IN nfs41_session * session,IN nfs41_path_fh * file,IN enum open_delegation_type4 access,IN bool_t truncate)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 
delegation_recall_thread(void * args)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 
deleg_stateid_cmp(const struct list_entry * entry,const void * value)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 
nfs41_delegation_recall(IN nfs41_client * client,IN nfs41_fh * fh,IN const stateid4 * stateid,IN bool_t truncate)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 
deleg_fh_cmp(const struct list_entry * entry,const void * value)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 
nfs41_delegation_getattr(IN nfs41_client * client,IN const nfs41_fh * fh,IN const bitmap4 * attr_request,OUT nfs41_file_info * info)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 
nfs41_client_delegation_free(IN nfs41_client * client)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 
delegation_recovery_status(IN nfs41_delegation_state * deleg)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 
nfs41_client_delegation_recovery(IN nfs41_client * client)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 
nfs41_client_delegation_return_lru(IN nfs41_client * client)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 }
942