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
lock_stateid_arg(IN nfs41_open_state * state,OUT stateid_arg * arg)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 */
lock_stateid_update(OUT nfs41_open_state * state,IN const stateid4 * stateid)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
open_lock_add(IN nfs41_open_state * open,IN const stateid_arg * stateid,IN nfs41_lock_state * lock)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
open_lock_delegate(IN nfs41_open_state * open,IN nfs41_lock_state * lock)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
lock_range_cmp(const struct list_entry * entry,const void * value)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
open_unlock_delegate(IN nfs41_open_state * open,IN const nfs41_lock_state * input)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
open_unlock_remove(IN nfs41_open_state * open,IN const stateid_arg * stateid,IN const nfs41_lock_state * input)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 */
parse_lock(unsigned char * buffer,uint32_t length,nfs41_upcall * upcall)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
get_lock_type(BOOLEAN exclusive,BOOLEAN blocking)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
handle_lock(nfs41_upcall * upcall)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
cancel_lock(IN nfs41_upcall * upcall)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 */
parse_unlock(unsigned char * buffer,uint32_t length,nfs41_upcall * upcall)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
handle_unlock(nfs41_upcall * upcall)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