xref: /reactos/base/services/nfsd/lock.c (revision 8a978a17)
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 <stdio.h>
24 
25 #include "daemon_debug.h"
26 #include "delegation.h"
27 #include "nfs41_ops.h"
28 #include "upcall.h"
29 #include "util.h"
30 
31 
32 #define LKLVL 2 /* dprintf level for lock logging */
33 
34 
35 static void lock_stateid_arg(
36     IN nfs41_open_state *state,
37     OUT stateid_arg *arg)
38 {
39     arg->open = state;
40     arg->delegation = NULL;
41 
42     AcquireSRWLockShared(&state->lock);
43     if (state->locks.stateid.seqid) {
44         memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4));
45         arg->type = STATEID_LOCK;
46     } else if (state->do_close) {
47         memcpy(&arg->stateid, &state->stateid, sizeof(stateid4));
48         arg->type = STATEID_OPEN;
49     } else {
50         memset(&arg->stateid, 0, sizeof(stateid4));
51         arg->type = STATEID_SPECIAL;
52     }
53     ReleaseSRWLockShared(&state->lock);
54 }
55 
56 /* expects the caller to hold an exclusive lock on nfs41_open_state.lock */
57 static void lock_stateid_update(
58     OUT nfs41_open_state *state,
59     IN const stateid4 *stateid)
60 {
61     if (state->locks.stateid.seqid == 0) {
62         /* if it's a new lock stateid, copy it in */
63         memcpy(&state->locks.stateid, stateid, sizeof(stateid4));
64     } else if (stateid->seqid > state->locks.stateid.seqid) {
65         /* update the seqid if it's more recent */
66         state->locks.stateid.seqid = stateid->seqid;
67     }
68 }
69 
70 static void open_lock_add(
71     IN nfs41_open_state *open,
72     IN const stateid_arg *stateid,
73     IN nfs41_lock_state *lock)
74 {
75     AcquireSRWLockExclusive(&open->lock);
76 
77     if (stateid->type == STATEID_LOCK)
78         lock_stateid_update(open, &stateid->stateid);
79 
80     lock->id = open->locks.counter++;
81     list_add_tail(&open->locks.list, &lock->open_entry);
82 
83     ReleaseSRWLockExclusive(&open->lock);
84 }
85 
86 static bool_t open_lock_delegate(
87     IN nfs41_open_state *open,
88     IN nfs41_lock_state *lock)
89 {
90     bool_t delegated = FALSE;
91 
92     AcquireSRWLockExclusive(&open->lock);
93     if (open->delegation.state) {
94         nfs41_delegation_state *deleg = open->delegation.state;
95         AcquireSRWLockShared(&deleg->lock);
96         if (deleg->state.type == OPEN_DELEGATE_WRITE
97             && deleg->status == DELEGATION_GRANTED) {
98             lock->delegated = 1;
99             lock->id = open->locks.counter++;
100             list_add_tail(&open->locks.list, &lock->open_entry);
101             delegated = TRUE;
102         }
103         ReleaseSRWLockShared(&deleg->lock);
104     }
105     ReleaseSRWLockExclusive(&open->lock);
106 
107     return delegated;
108 }
109 
110 #define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry)
111 
112 static int lock_range_cmp(const struct list_entry *entry, const void *value)
113 {
114     const nfs41_lock_state *lhs = lock_entry(entry);
115     const nfs41_lock_state *rhs = (const nfs41_lock_state*)value;
116     if (lhs->offset != rhs->offset) return -1;
117     if (lhs->length != rhs->length) return -1;
118     return 0;
119 }
120 
121 static int open_unlock_delegate(
122     IN nfs41_open_state *open,
123     IN const nfs41_lock_state *input)
124 {
125     struct list_entry *entry;
126     int status = ERROR_NOT_LOCKED;
127 
128     AcquireSRWLockExclusive(&open->lock);
129 
130     /* find lock state that matches this range */
131     entry = list_search(&open->locks.list, input, lock_range_cmp);
132     if (entry) {
133         nfs41_lock_state *lock = lock_entry(entry);
134         if (lock->delegated) {
135             /* if the lock was delegated, remove/free it and return success */
136             list_remove(entry);
137             free(lock);
138             status = NO_ERROR;
139         } else
140             status = ERROR_LOCKED;
141     }
142 
143     ReleaseSRWLockExclusive(&open->lock);
144     return status;
145 }
146 
147 static void open_unlock_remove(
148     IN nfs41_open_state *open,
149     IN const stateid_arg *stateid,
150     IN const nfs41_lock_state *input)
151 {
152     struct list_entry *entry;
153 
154     AcquireSRWLockExclusive(&open->lock);
155     if (stateid->type == STATEID_LOCK)
156         lock_stateid_update(open, &stateid->stateid);
157 
158     /* find and remove the unlocked range */
159     entry = list_search(&open->locks.list, input, lock_range_cmp);
160     if (entry) {
161         list_remove(entry);
162         free(lock_entry(entry));
163     }
164     ReleaseSRWLockExclusive(&open->lock);
165 }
166 
167 
168 /* NFS41_LOCK */
169 static int parse_lock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
170 {
171     int status;
172     lock_upcall_args *args = &upcall->args.lock;
173 
174     status = safe_read(&buffer, &length, &args->offset, sizeof(LONGLONG));
175     if (status) goto out;
176     status = safe_read(&buffer, &length, &args->length, sizeof(LONGLONG));
177     if (status) goto out;
178     status = safe_read(&buffer, &length, &args->exclusive, sizeof(BOOLEAN));
179     if (status) goto out;
180     status = safe_read(&buffer, &length, &args->blocking, sizeof(BOOLEAN));
181     if (status) goto out;
182 
183     dprintf(1, "parsing NFS41_LOCK: offset=0x%llx length=0x%llx exclusive=%u "
184             "blocking=%u\n", args->offset, args->length, args->exclusive,
185             args->blocking);
186 out:
187     return status;
188 }
189 
190 static __inline uint32_t get_lock_type(BOOLEAN exclusive, BOOLEAN blocking)
191 {
192     return blocking == 0
193         ? ( exclusive == 0 ? READ_LT : WRITE_LT )
194         : ( exclusive == 0 ? READW_LT : WRITEW_LT );
195 }
196 
197 static int handle_lock(nfs41_upcall *upcall)
198 {
199     stateid_arg stateid;
200     lock_upcall_args *args = &upcall->args.lock;
201     nfs41_open_state *state = upcall->state_ref;
202     nfs41_lock_state *lock;
203     const uint32_t type = get_lock_type(args->exclusive, args->blocking);
204     int status = NO_ERROR;
205 
206     /* 18.10.3. Operation 12: LOCK - Create Lock
207      * "To lock the file from a specific offset through the end-of-file
208      * (no matter how long the file actually is) use a length field equal
209      * to NFS4_UINT64_MAX." */
210     if (args->length >= NFS4_UINT64_MAX - args->offset)
211         args->length = NFS4_UINT64_MAX;
212 
213     /* allocate the lock state */
214     lock = calloc(1, sizeof(nfs41_lock_state));
215     if (lock == NULL) {
216         status = GetLastError();
217         goto out;
218     }
219     lock->offset = args->offset;
220     lock->length = args->length;
221     lock->exclusive = args->exclusive;
222 
223     /* if we hold a write delegation, handle the lock locally */
224     if (open_lock_delegate(state, lock)) {
225         dprintf(LKLVL, "delegated lock { %llu, %llu }\n",
226             lock->offset, lock->length);
227         args->acquired = TRUE; /* for cancel_lock() */
228         goto out;
229     }
230 
231     /* open_to_lock_owner4 requires an open stateid; if we
232      * have a delegation, convert it to an open stateid */
233     status = nfs41_delegation_to_open(state, TRUE);
234     if (status) {
235         status = ERROR_FILE_INVALID;
236         goto out_free;
237     }
238 
239     EnterCriticalSection(&state->locks.lock);
240 
241     lock_stateid_arg(state, &stateid);
242 
243     status = nfs41_lock(state->session, &state->file, &state->owner,
244         type, lock->offset, lock->length, FALSE, TRUE, &stateid);
245     if (status) {
246         dprintf(LKLVL, "nfs41_lock failed with %s\n",
247             nfs_error_string(status));
248         status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
249         LeaveCriticalSection(&state->locks.lock);
250         goto out_free;
251     }
252 
253     /* save lock state with the open */
254     open_lock_add(state, &stateid, lock);
255     LeaveCriticalSection(&state->locks.lock);
256 
257     args->acquired = TRUE; /* for cancel_lock() */
258 out:
259     return status;
260 
261 out_free:
262     free(lock);
263     goto out;
264 }
265 
266 static void cancel_lock(IN nfs41_upcall *upcall)
267 {
268     stateid_arg stateid;
269     nfs41_lock_state input;
270     lock_upcall_args *args = &upcall->args.lock;
271     nfs41_open_state *state = upcall->state_ref;
272     int status = NO_ERROR;
273 
274     dprintf(1, "--> cancel_lock()\n");
275 
276     /* can't do 'if (upcall->status)' here, because a handle_lock() success
277      * could be overwritten by upcall_marshall() or allocation failure */
278     if (!args->acquired)
279         goto out;
280 
281     input.offset = args->offset;
282     input.length = args->length;
283 
284     /* search for the range to unlock, and remove if delegated */
285     status = open_unlock_delegate(state, &input);
286     if (status != ERROR_LOCKED)
287         goto out;
288 
289     EnterCriticalSection(&state->locks.lock);
290     lock_stateid_arg(state, &stateid);
291 
292     status = nfs41_unlock(state->session, &state->file,
293         args->offset, args->length, &stateid);
294 
295     open_unlock_remove(state, &stateid, &input);
296     LeaveCriticalSection(&state->locks.lock);
297 
298     status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
299 out:
300     dprintf(1, "<-- cancel_lock() returning %d\n", status);
301 }
302 
303 
304 /* NFS41_UNLOCK */
305 static int parse_unlock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
306 {
307     int status;
308     unlock_upcall_args *args = &upcall->args.unlock;
309 
310     status = safe_read(&buffer, &length, &args->count, sizeof(ULONG));
311     if (status) goto out;
312 
313     args->buf = buffer;
314     args->buf_len = length;
315 
316     dprintf(1, "parsing NFS41_UNLOCK: count=%u\n", args->count);
317 out:
318     return status;
319 }
320 
321 static int handle_unlock(nfs41_upcall *upcall)
322 {
323     nfs41_lock_state input;
324     stateid_arg stateid;
325     unlock_upcall_args *args = &upcall->args.unlock;
326     nfs41_open_state *state = upcall->state_ref;
327     unsigned char *buf = args->buf;
328     uint32_t buf_len = args->buf_len;
329     uint32_t i;
330     int status = NO_ERROR;
331 
332     for (i = 0; i < args->count; i++) {
333         if (safe_read(&buf, &buf_len, &input.offset, sizeof(LONGLONG))) break;
334         if (safe_read(&buf, &buf_len, &input.length, sizeof(LONGLONG))) break;
335 
336         /* do the same translation as LOCK, or the ranges won't match */
337         if (input.length >= NFS4_UINT64_MAX - input.offset)
338             input.length = NFS4_UINT64_MAX;
339 
340         /* search for the range to unlock, and remove if delegated */
341         status = open_unlock_delegate(state, &input);
342         if (status != ERROR_LOCKED)
343             continue;
344 
345         EnterCriticalSection(&state->locks.lock);
346         lock_stateid_arg(state, &stateid);
347 
348         status = nfs41_unlock(state->session, &state->file,
349             input.offset, input.length, &stateid);
350 
351         open_unlock_remove(state, &stateid, &input);
352         LeaveCriticalSection(&state->locks.lock);
353 
354         status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
355     }
356     return status;
357 }
358 
359 
360 const nfs41_upcall_op nfs41_op_lock = {
361     parse_lock,
362     handle_lock,
363     NULL,
364     cancel_lock
365 };
366 const nfs41_upcall_op nfs41_op_unlock = {
367     parse_unlock,
368     handle_unlock
369 };
370