r15734: This is a major change to the NTVFS subsystem:
[sfrench/samba-autobuild/.git] / source4 / ntvfs / posix / pvfs_lock.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - locking
5
6    Copyright (C) Andrew Tridgell 2004
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24 #include "vfs_posix.h"
25 #include "system/time.h"
26 #include "dlinklist.h"
27 #include "messaging/messaging.h"
28
29
30 /*
31   check if we can perform IO on a range that might be locked
32 */
33 NTSTATUS pvfs_check_lock(struct pvfs_state *pvfs,
34                          struct pvfs_file *f,
35                          uint16_t smbpid,
36                          uint64_t offset, uint64_t count,
37                          enum brl_type rw)
38 {
39         if (!(pvfs->flags & PVFS_FLAG_STRICT_LOCKING)) {
40                 return NT_STATUS_OK;
41         }
42
43         return brl_locktest(pvfs->brl_context,
44                             f->brl_handle,
45                             smbpid,
46                             offset, count, rw);
47 }
48
49 /* this state structure holds information about a lock we are waiting on */
50 struct pvfs_pending_lock {
51         struct pvfs_pending_lock *next, *prev;
52         struct pvfs_state *pvfs;
53         union smb_lock *lck;
54         struct pvfs_file *f;
55         struct ntvfs_request *req;
56         int pending_lock;
57         void *wait_handle;
58         struct timeval end_time;
59 };
60
61 /*
62   a secondary attempt to setup a lock has failed - back out
63   the locks we did get and send an error
64 */
65 static void pvfs_lock_async_failed(struct pvfs_state *pvfs,
66                                    struct ntvfs_request *req,
67                                    struct pvfs_file *f,
68                                    struct smb_lock_entry *locks,
69                                    int i,
70                                    NTSTATUS status)
71 {
72         /* undo the locks we just did */
73         for (i=i-1;i>=0;i--) {
74                 brl_unlock(pvfs->brl_context,
75                            f->brl_handle,
76                            locks[i].pid,
77                            locks[i].offset,
78                            locks[i].count);
79                 f->lock_count--;
80         }
81         req->async_states->status = status;
82         req->async_states->send_fn(req);
83 }
84
85
86 /*
87   called when we receive a pending lock notification. It means that
88   either our lock timed out or somoene else has unlocked a overlapping
89   range, so we should try the lock again. Note that on timeout we
90   do retry the lock, giving it a last chance.
91 */
92 static void pvfs_pending_lock_continue(void *private, enum pvfs_wait_notice reason)
93 {
94         struct pvfs_pending_lock *pending = private;
95         struct pvfs_state *pvfs = pending->pvfs;
96         struct pvfs_file *f = pending->f;
97         struct ntvfs_request *req = pending->req;
98         union smb_lock *lck = pending->lck;
99         struct smb_lock_entry *locks;
100         enum brl_type rw;
101         NTSTATUS status;
102         int i;
103         BOOL timed_out;
104
105         timed_out = (reason != PVFS_WAIT_EVENT);
106
107         locks = lck->lockx.in.locks + lck->lockx.in.ulock_cnt;
108
109         if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
110                 rw = READ_LOCK;
111         } else {
112                 rw = WRITE_LOCK;
113         }
114
115         DLIST_REMOVE(f->pending_list, pending);
116
117         /* we don't retry on a cancel */
118         if (reason == PVFS_WAIT_CANCEL) {
119                 status = NT_STATUS_FILE_LOCK_CONFLICT;
120         } else {
121                 /* 
122                  * here it's important to pass the pending pointer
123                  * because with this we'll get the correct error code
124                  * FILE_LOCK_CONFLICT in the error case
125                  */
126                 status = brl_lock(pvfs->brl_context,
127                                   f->brl_handle,
128                                   req->smbpid,
129                                   locks[pending->pending_lock].offset,
130                                   locks[pending->pending_lock].count,
131                                   rw, pending);
132         }
133         if (NT_STATUS_IS_OK(status)) {
134                 f->lock_count++;
135                 timed_out = False;
136         }
137
138         /* if we have failed and timed out, or succeeded, then we
139            don't need the pending lock any more */
140         if (NT_STATUS_IS_OK(status) || timed_out) {
141                 NTSTATUS status2;
142                 status2 = brl_remove_pending(pvfs->brl_context, 
143                                              f->brl_handle, pending);
144                 if (!NT_STATUS_IS_OK(status2)) {
145                         DEBUG(0,("pvfs_lock: failed to remove pending lock - %s\n", nt_errstr(status2)));
146                 }
147                 talloc_free(pending->wait_handle);
148         }
149
150         if (!NT_STATUS_IS_OK(status)) {
151                 if (timed_out) {
152                         /* no more chances */
153                         pvfs_lock_async_failed(pvfs, req, f, locks, pending->pending_lock, status);
154                 } else {
155                         /* we can try again */
156                         DLIST_ADD(f->pending_list, pending);
157                 }
158                 return;
159         }
160
161         /* if we haven't timed out yet, then we can do more pending locks */
162         if (rw == READ_LOCK) {
163                 rw = PENDING_READ_LOCK;
164         } else {
165                 rw = PENDING_WRITE_LOCK;
166         }
167
168         /* we've now got the pending lock. try and get the rest, which might
169            lead to more pending locks */
170         for (i=pending->pending_lock+1;i<lck->lockx.in.lock_cnt;i++) {          
171                 if (pending) {
172                         pending->pending_lock = i;
173                 }
174
175                 status = brl_lock(pvfs->brl_context,
176                                   f->brl_handle,
177                                   req->smbpid,
178                                   locks[i].offset,
179                                   locks[i].count,
180                                   rw, pending);
181                 if (!NT_STATUS_IS_OK(status)) {
182                         if (pending) {
183                                 /* a timed lock failed - setup a wait message to handle
184                                    the pending lock notification or a timeout */
185                                 pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, 
186                                                                          pending->end_time,
187                                                                          pvfs_pending_lock_continue,
188                                                                          pending);
189                                 if (pending->wait_handle == NULL) {
190                                         pvfs_lock_async_failed(pvfs, req, f, locks, i, NT_STATUS_NO_MEMORY);
191                                 } else {
192                                         talloc_steal(pending, pending->wait_handle);
193                                         DLIST_ADD(f->pending_list, pending);
194                                 }
195                                 return;
196                         }
197                         pvfs_lock_async_failed(pvfs, req, f, locks, i, status);
198                         return;
199                 }
200
201                 f->lock_count++;
202         }
203
204         /* we've managed to get all the locks. Tell the client */
205         req->async_states->status = NT_STATUS_OK;
206         req->async_states->send_fn(req);
207 }
208
209
210 /*
211   called when we close a file that might have locks
212 */
213 void pvfs_lock_close(struct pvfs_state *pvfs, struct pvfs_file *f)
214 {
215         struct pvfs_pending_lock *p, *next;
216
217         if (f->lock_count || f->pending_list) {
218                 DEBUG(5,("pvfs_lock: removing %.0f locks on close\n", 
219                          (double)f->lock_count));
220                 brl_close(f->pvfs->brl_context, f->brl_handle);
221                 f->lock_count = 0;
222         }
223
224         /* reply to all the pending lock requests, telling them the 
225            lock failed */
226         for (p=f->pending_list;p;p=next) {
227                 next = p->next;
228                 DLIST_REMOVE(f->pending_list, p);
229                 p->req->async_states->status = NT_STATUS_RANGE_NOT_LOCKED;
230                 p->req->async_states->send_fn(p->req);
231         }
232 }
233
234
235 /*
236   cancel a set of locks
237 */
238 static NTSTATUS pvfs_lock_cancel(struct pvfs_state *pvfs, struct ntvfs_request *req, union smb_lock *lck,
239                                  struct pvfs_file *f)
240 {
241         struct pvfs_pending_lock *p;
242
243         for (p=f->pending_list;p;p=p->next) {
244                 /* check if the lock request matches exactly - you can only cancel with exact matches */
245                 if (p->lck->lockx.in.ulock_cnt == lck->lockx.in.ulock_cnt &&
246                     p->lck->lockx.in.lock_cnt  == lck->lockx.in.lock_cnt &&
247                     p->lck->lockx.in.file.ntvfs== lck->lockx.in.file.ntvfs &&
248                     p->lck->lockx.in.mode      == (lck->lockx.in.mode & ~LOCKING_ANDX_CANCEL_LOCK)) {
249                         int i;
250
251                         for (i=0;i<lck->lockx.in.ulock_cnt + lck->lockx.in.lock_cnt;i++) {
252                                 if (p->lck->lockx.in.locks[i].pid != lck->lockx.in.locks[i].pid ||
253                                     p->lck->lockx.in.locks[i].offset != lck->lockx.in.locks[i].offset ||
254                                     p->lck->lockx.in.locks[i].count != lck->lockx.in.locks[i].count) {
255                                         break;
256                                 }
257                         }
258                         if (i < lck->lockx.in.ulock_cnt) continue;
259
260                         /* an exact match! we can cancel it, which is equivalent
261                            to triggering the timeout early */
262                         pvfs_pending_lock_continue(p, PVFS_WAIT_TIMEOUT);
263                         return NT_STATUS_OK;
264                 }
265         }
266
267         return NT_STATUS_DOS(ERRDOS, ERRcancelviolation);
268 }
269
270
271 /*
272   lock or unlock a byte range
273 */
274 NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
275                    struct ntvfs_request *req, union smb_lock *lck)
276 {
277         struct pvfs_state *pvfs = ntvfs->private_data;
278         struct pvfs_file *f;
279         struct smb_lock_entry *locks;
280         int i;
281         enum brl_type rw;
282         struct pvfs_pending_lock *pending = NULL;
283         NTSTATUS status;
284
285         if (lck->generic.level != RAW_LOCK_GENERIC) {
286                 return ntvfs_map_lock(ntvfs, req, lck);
287         }
288
289         f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs);
290         if (!f) {
291                 return NT_STATUS_INVALID_HANDLE;
292         }
293
294         if (f->handle->fd == -1) {
295                 return NT_STATUS_FILE_IS_A_DIRECTORY;
296         }
297
298         if (lck->lockx.in.timeout != 0 && 
299             (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
300                 pending = talloc(f, struct pvfs_pending_lock);
301                 if (pending == NULL) {
302                         return NT_STATUS_NO_MEMORY;
303                 }
304
305                 pending->pvfs = pvfs;
306                 pending->lck = lck;
307                 pending->f = f;
308                 pending->req = req;
309
310                 pending->end_time = 
311                         timeval_current_ofs(lck->lockx.in.timeout/1000,
312                                             1000*(lck->lockx.in.timeout%1000));
313         }
314
315         if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
316                 rw = pending? PENDING_READ_LOCK : READ_LOCK;
317         } else {
318                 rw = pending? PENDING_WRITE_LOCK : WRITE_LOCK;
319         }
320
321         if (lck->lockx.in.mode & LOCKING_ANDX_CANCEL_LOCK) {
322                 return pvfs_lock_cancel(pvfs, req, lck, f);
323         }
324
325         if (lck->lockx.in.mode & LOCKING_ANDX_CHANGE_LOCKTYPE) {
326                 /* this seems to not be supported by any windows server,
327                    or used by any clients */
328                 return NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks);
329         }
330
331         if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) {
332                 DEBUG(0,("received unexpected oplock break\n"));
333                 return NT_STATUS_NOT_IMPLEMENTED;
334         }
335
336
337         /* the unlocks happen first */
338         locks = lck->lockx.in.locks;
339
340         for (i=0;i<lck->lockx.in.ulock_cnt;i++) {
341                 status = brl_unlock(pvfs->brl_context,
342                                     f->brl_handle,
343                                     locks[i].pid,
344                                     locks[i].offset,
345                                     locks[i].count);
346                 if (!NT_STATUS_IS_OK(status)) {
347                         return status;
348                 }
349                 f->lock_count--;
350         }
351
352         locks += i;
353
354         for (i=0;i<lck->lockx.in.lock_cnt;i++) {
355                 if (pending) {
356                         pending->pending_lock = i;
357                 }
358
359                 status = brl_lock(pvfs->brl_context,
360                                   f->brl_handle,
361                                   locks[i].pid,
362                                   locks[i].offset,
363                                   locks[i].count,
364                                   rw, pending);
365                 if (!NT_STATUS_IS_OK(status)) {
366                         if (pending) {
367                                 /* a timed lock failed - setup a wait message to handle
368                                    the pending lock notification or a timeout */
369                                 pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, 
370                                                                          pending->end_time,
371                                                                          pvfs_pending_lock_continue,
372                                                                          pending);
373                                 if (pending->wait_handle == NULL) {
374                                         return NT_STATUS_NO_MEMORY;
375                                 }
376                                 talloc_steal(pending, pending->wait_handle);
377                                 DLIST_ADD(f->pending_list, pending);
378                                 return NT_STATUS_OK;
379                         }
380                         /* undo the locks we just did */
381                         for (i=i-1;i>=0;i--) {
382                                 brl_unlock(pvfs->brl_context,
383                                            f->brl_handle,
384                                            locks[i].pid,
385                                            locks[i].offset,
386                                            locks[i].count);
387                                 f->lock_count--;
388                         }
389                         return status;
390                 }
391                 f->lock_count++;
392         }
393
394         return NT_STATUS_OK;
395 }