r3029: implemented byte range lock timeouts.
[samba.git] / source / 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 "include/includes.h"
24 #include "vfs_posix.h"
25
26
27 /*
28   check if we can perform IO on a range that might be locked
29 */
30 NTSTATUS pvfs_check_lock(struct pvfs_state *pvfs,
31                          struct pvfs_file *f,
32                          uint16_t smbpid,
33                          uint64_t offset, uint64_t count,
34                          enum brl_type rw)
35 {
36         if (!(pvfs->flags & PVFS_FLAG_STRICT_LOCKING)) {
37                 return NT_STATUS_OK;
38         }
39
40         return brl_locktest(pvfs->brl_context,
41                             &f->locking_key,
42                             f->fnum,
43                             smbpid,
44                             offset, count, rw);
45 }
46
47 /* this state structure holds information about a lock we are waiting on */
48 struct pending_state {
49         struct pvfs_state *pvfs;
50         union smb_lock *lck;
51         struct pvfs_file *f;
52         struct smbsrv_request *req;
53         int pending_lock;
54         void *wait_handle;
55         time_t end_time;
56 };
57
58
59 /*
60   a secondary attempt to setup a lock has failed - back out
61   the locks we did get and send an error
62 */
63 static void pvfs_lock_async_failed(struct pvfs_state *pvfs,
64                                    struct smbsrv_request *req,
65                                    struct pvfs_file *f,
66                                    struct smb_lock_entry *locks,
67                                    int i,
68                                    NTSTATUS status)
69 {
70         /* undo the locks we just did */
71         for (i=i-1;i>=0;i--) {
72                 brl_unlock(pvfs->brl_context,
73                            &f->locking_key,
74                            locks[i].pid,
75                            f->fnum,
76                            locks[i].offset,
77                            locks[i].count);
78         }
79         req->async.status = status;
80         req->async.send_fn(req);
81 }
82
83
84 /*
85   called when we receive a pending lock notification. It means that
86   either our lock timed out or somoene else has unlocked a overlapping
87   range, so we should try the lock again. Note that on timeout we
88   do retry the lock, giving it a last chance.
89 */
90 static void pvfs_pending_lock_continue(void *private, BOOL timed_out)
91 {
92         struct pending_state *pending = private;
93         struct pvfs_state *pvfs = pending->pvfs;
94         struct pvfs_file *f = pending->f;
95         struct smbsrv_request *req = pending->req;
96         union smb_lock *lck = pending->lck;
97         struct smb_lock_entry *locks;
98         enum brl_type rw;
99         NTSTATUS status;
100         int i;
101
102         locks = lck->lockx.in.locks + lck->lockx.in.ulock_cnt;
103
104         if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
105                 rw = READ_LOCK;
106         } else {
107                 rw = WRITE_LOCK;
108         }
109
110         status = brl_lock(pvfs->brl_context,
111                           &f->locking_key,
112                           req->smbpid,
113                           f->fnum,
114                           locks[pending->pending_lock].offset,
115                           locks[pending->pending_lock].count,
116                           rw, NULL);
117
118         /* if we have failed and timed out, or succeeded, then we
119            don't need the pending lock any more */
120         if (NT_STATUS_IS_OK(status) || timed_out) {
121                 NTSTATUS status2;
122                 status2 = brl_remove_pending(pvfs->brl_context, &f->locking_key, pending);
123                 if (!NT_STATUS_IS_OK(status2)) {
124                         DEBUG(0,("pvfs_lock: failed to remove pending lock - %s\n", nt_errstr(status2)));
125                 }
126                 talloc_free(pending->wait_handle);
127         }
128
129         if (!NT_STATUS_IS_OK(status)) {
130                 if (timed_out) {
131                         /* no more chances */
132                         pvfs_lock_async_failed(pvfs, req, f, locks, pending->pending_lock, status);
133                 }
134                 /* we can try again */
135                 return;
136         }
137
138         /* if we haven't timed out yet, then we can do more pending locks */
139         if (timed_out) {
140                 pending = NULL;
141         } else {
142                 if (rw == READ_LOCK) {
143                         rw = PENDING_READ_LOCK;
144                 } else {
145                         rw = PENDING_WRITE_LOCK;
146                 }
147         }
148
149         /* we've now got the pending lock. try and get the rest, which might
150            lead to more pending locks */
151         for (i=pending->pending_lock;i<lck->lockx.in.lock_cnt;i++) {            
152                 if (pending) {
153                         pending->pending_lock = i;
154                 }
155
156                 status = brl_lock(pvfs->brl_context,
157                                   &f->locking_key,
158                                   req->smbpid,
159                                   f->fnum,
160                                   locks[i].offset,
161                                   locks[i].count,
162                                   rw, pending);
163                 if (!NT_STATUS_IS_OK(status)) {
164                         if (pending) {
165                                 /* a timed lock failed - setup a wait message to handle
166                                    the pending lock notification or a timeout */
167                                 pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, 
168                                                                          pending->end_time,
169                                                                          pvfs_pending_lock_continue,
170                                                                          pending);
171                                 if (pending->wait_handle == NULL) {
172                                         pvfs_lock_async_failed(pvfs, req, f, locks, i, NT_STATUS_NO_MEMORY);
173                                 }
174                                 return;
175                         }
176                         pvfs_lock_async_failed(pvfs, req, f, locks, i, status);
177                         return;
178                 }
179         }
180
181         brl_unlock(pvfs->brl_context,
182                    &f->locking_key,
183                    req->smbpid,
184                    f->fnum,
185                    lck->lock.in.offset,
186                    lck->lock.in.count);
187
188         /* we've managed to get all the locks. Tell the client */
189         req->async.status = NT_STATUS_OK;
190         req->async.send_fn(req);
191 }
192
193
194 /*
195   lock or unlock a byte range
196 */
197 NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
198                    struct smbsrv_request *req, union smb_lock *lck)
199 {
200         struct pvfs_state *pvfs = ntvfs->private_data;
201         struct pvfs_file *f;
202         struct smb_lock_entry *locks;
203         int i;
204         enum brl_type rw;
205         struct pending_state *pending = NULL;
206
207         f = pvfs_find_fd(pvfs, req, lck->generic.in.fnum);
208         if (!f) {
209                 return NT_STATUS_INVALID_HANDLE;
210         }
211
212         switch (lck->generic.level) {
213         case RAW_LOCK_LOCK:
214                 return brl_lock(pvfs->brl_context,
215                                 &f->locking_key,
216                                 req->smbpid,
217                                 f->fnum,
218                                 lck->lock.in.offset,
219                                 lck->lock.in.count,
220                                 WRITE_LOCK, NULL);
221                                 
222         case RAW_LOCK_UNLOCK:
223                 return brl_unlock(pvfs->brl_context,
224                                   &f->locking_key,
225                                   req->smbpid,
226                                   f->fnum,
227                                   lck->lock.in.offset,
228                                   lck->lock.in.count);
229
230         case RAW_LOCK_GENERIC:
231                 return NT_STATUS_INVALID_LEVEL;
232
233         case RAW_LOCK_LOCKX:
234                 /* fall through to the most complex case */
235                 break;
236         }
237
238         /* now the lockingX case, most common and also most complex */
239         if (lck->lockx.in.timeout != 0) {
240                 pending = talloc_p(req, struct pending_state);
241                 if (pending == NULL) {
242                         return NT_STATUS_NO_MEMORY;
243                 }
244
245                 pending->pvfs = pvfs;
246                 pending->lck = lck;
247                 pending->f = f;
248                 pending->req = req;
249
250                 /* round up to the nearest second */
251                 pending->end_time = time(NULL) + ((lck->lockx.in.timeout+999)/1000);
252         }
253
254         if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
255                 rw = pending? PENDING_READ_LOCK : READ_LOCK;
256         } else {
257                 rw = pending? PENDING_WRITE_LOCK : WRITE_LOCK;
258         }
259
260         if (lck->lockx.in.mode & 
261             (LOCKING_ANDX_OPLOCK_RELEASE |
262              LOCKING_ANDX_CHANGE_LOCKTYPE |
263              LOCKING_ANDX_CANCEL_LOCK)) {
264                 /* todo: need to add support for these */
265                 return NT_STATUS_NOT_IMPLEMENTED;
266         }
267
268
269         /* the unlocks happen first */
270         locks = lck->lockx.in.locks;
271
272         for (i=0;i<lck->lockx.in.ulock_cnt;i++) {
273                 NTSTATUS status;
274                 status = brl_unlock(pvfs->brl_context,
275                                     &f->locking_key,
276                                     locks[i].pid,
277                                     f->fnum,
278                                     locks[i].offset,
279                                     locks[i].count);
280                 if (!NT_STATUS_IS_OK(status)) {
281                         return status;
282                 }
283         }
284
285         locks += i;
286
287         for (i=0;i<lck->lockx.in.lock_cnt;i++) {
288                 NTSTATUS status;
289
290                 if (pending) {
291                         pending->pending_lock = i;
292                 }
293
294                 status = brl_lock(pvfs->brl_context,
295                                   &f->locking_key,
296                                   locks[i].pid,
297                                   f->fnum,
298                                   locks[i].offset,
299                                   locks[i].count,
300                                   rw, pending);
301                 if (!NT_STATUS_IS_OK(status)) {
302                         if (pending) {
303                                 /* a timed lock failed - setup a wait message to handle
304                                    the pending lock notification or a timeout */
305                                 pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, 
306                                                                          pending->end_time,
307                                                                          pvfs_pending_lock_continue,
308                                                                          pending);
309                                 if (pending->wait_handle == NULL) {
310                                         return NT_STATUS_NO_MEMORY;
311                                 }
312                                 return NT_STATUS_OK;
313                         }
314                         /* undo the locks we just did */
315                         for (i=i-1;i>=0;i--) {
316                                 brl_unlock(pvfs->brl_context,
317                                            &f->locking_key,
318                                            locks[i].pid,
319                                            f->fnum,
320                                            locks[i].offset,
321                                            locks[i].count);
322                         }
323                         return status;
324                 }
325         }
326
327         return NT_STATUS_OK;
328 }
329