]> arthur.barton.de Git - netatalk.git/blob - libatalk/adouble/ad_lock.c
Remove flock lock support.
[netatalk.git] / libatalk / adouble / ad_lock.c
1 /* 
2  * $Id: ad_lock.c,v 1.6 2002-11-14 17:15:22 srittau Exp $
3  *
4  * Copyright (c) 1998,1999 Adrian Sun (asun@zoology.washington.edu)
5  * All Rights Reserved. See COPYRIGHT for more information.
6  *
7  * Byte-range locks. This uses either whole-file flocks to fake byte
8  * locks or fcntl-based actual byte locks. Because fcntl locks are
9  * process-oriented, we need to keep around a list of file descriptors
10  * that refer to the same file. Currently, this doesn't serialize access 
11  * to the locks. as a result, there's the potential for race conditions. 
12  *
13  * TODO: fix the race when reading/writing.
14  *       keep a pool of both locks and reference counters around so that
15  *       we can save on mallocs. we should also use a tree to keep things
16  *       sorted.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif /* HAVE_CONFIG_H */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif /* HAVE_UNISTD_H */
29 #ifdef HAVE_FCNTL_H
30 #include <fcntl.h>
31 #endif /* HAVE_FCNTL_H */
32 #include <errno.h>
33
34 #include <atalk/adouble.h>
35
36 #include "ad_private.h"
37
38 /* translate between ADLOCK styles and specific locking mechanisms */
39 #define XLATE_FLOCK(type) ((type) == ADLOCK_RD ? LOCK_SH : \
40 ((type) == ADLOCK_WR ? LOCK_EX : \
41  ((type) == ADLOCK_CLR ? LOCK_UN : -1)))
42
43 #define XLATE_FCNTL_LOCK(type) ((type) == ADLOCK_RD ? F_RDLCK : \
44 ((type) == ADLOCK_WR ? F_WRLCK : \
45  ((type) == ADLOCK_CLR ? F_UNLCK : -1))) 
46      
47 #define OVERLAP(a,alen,b,blen) ((!(alen) && (a) <= (b)) || \
48                                 (!(blen) && (b) <= (a)) || \
49                                 ((((a) + (alen)) > (b)) && \
50                                 (((b) + (blen)) > (a))))
51
52
53 /* allocation for lock regions. we allocate aggressively and shrink
54  * only in large chunks. */
55 #define ARRAY_BLOCK_SIZE 10 
56 #define ARRAY_FREE_DELTA 100
57
58 /* remove a lock and compact space if necessary */
59 static __inline__ void adf_freelock(struct ad_fd *ad, const int i)
60 {
61     adf_lock_t *lock = ad->adf_lock + i;
62
63     if (--(*lock->refcount) < 1) {
64         free(lock->refcount); 
65         lock->lock.l_type = F_UNLCK;
66         fcntl(ad->adf_fd, F_SETLK, &lock->lock); /* unlock */
67     }
68
69     ad->adf_lockcount--;
70
71     /* move another lock into the empty space */
72     if (i < ad->adf_lockcount) {
73         memcpy(lock, lock + ad->adf_lockcount - i, sizeof(adf_lock_t));
74     }
75
76     /* free extra cruft if we go past a boundary. we always want to
77      * keep at least some stuff around for allocations. this wastes
78      * a bit of space to save time on reallocations. */
79     if ((ad->adf_lockmax > ARRAY_FREE_DELTA) &&
80         (ad->adf_lockcount + ARRAY_FREE_DELTA < ad->adf_lockmax)) {
81             struct adf_lock_t *tmp;
82
83             tmp = (struct adf_lock_t *) 
84                     realloc(ad->adf_lock, sizeof(adf_lock_t)*
85                             (ad->adf_lockcount + ARRAY_FREE_DELTA));
86             if (tmp) {
87                 ad->adf_lock = tmp;
88                 ad->adf_lockmax = ad->adf_lockcount + ARRAY_FREE_DELTA;
89             }
90     }
91 }
92
93
94 /* this needs to deal with the following cases:
95  * 1) user is the only user of the lock 
96  * 2) user shares a read lock with another user
97  *
98  * i converted to using arrays of locks. everytime a lock
99  * gets removed, we shift all of the locks down.
100  */
101 static __inline__ void adf_unlock(struct ad_fd *ad, int fd, const int user)
102 {
103     adf_lock_t *lock = ad->adf_lock;
104     int i;
105
106     for (i = 0; i < ad->adf_lockcount; i++) {
107       if (lock[i].user == user) {
108         /* we're really going to delete this lock. note: read locks
109            are the only ones that allow refcounts > 1 */
110          adf_freelock(ad, i);
111          i--; /* we shifted things down, so we need to backtrack */
112        }
113     }
114 }
115
116 /* relock any byte lock that overlaps off/len. unlock everything
117  * else. */
118 static __inline__ void adf_relockrange(struct ad_fd *ad, int fd,
119                                        const off_t off, const size_t len)
120 {
121     adf_lock_t *lock = ad->adf_lock;
122     int i;
123     
124     for (i = 0; i < ad->adf_lockcount; i++) {
125       if (OVERLAP(off, len, lock[i].lock.l_start, lock[i].lock.l_len)) 
126         fcntl(fd, F_SETLK, &lock[i].lock);
127     }
128 }
129
130
131 /* find a byte lock that overlaps off/len for a particular user */
132 static __inline__ int adf_findlock(struct ad_fd *ad,
133                                    const int user, const int type,
134                                    const off_t off,
135                                    const size_t len)
136 {
137   adf_lock_t *lock = ad->adf_lock;
138   int i;
139   
140   for (i = 0; i < ad->adf_lockcount; i++) {
141     if ((((type & ADLOCK_RD) && (lock[i].lock.l_type == F_RDLCK)) ||
142         ((type & ADLOCK_WR) && (lock[i].lock.l_type == F_WRLCK))) &&
143         (lock[i].user == user) && 
144         OVERLAP(off, len, lock[i].lock.l_start, lock[i].lock.l_len)) {
145       return i;
146     }
147   }
148
149   return -1;
150 }
151
152
153 /* search other user lock lists */
154 static __inline__  int adf_findxlock(struct ad_fd *ad, 
155                                      const int user, const int type,
156                                      const off_t off,
157                                      const size_t len)
158 {
159   adf_lock_t *lock = ad->adf_lock;
160   int i;
161   
162   for (i = 0; i < ad->adf_lockcount; i++) {
163     if ((((type & ADLOCK_RD) && (lock[i].lock.l_type == F_RDLCK)) ||
164          ((type & ADLOCK_WR) && (lock[i].lock.l_type == F_WRLCK))) &&
165         (lock[i].user != user) && 
166         OVERLAP(off, len, lock[i].lock.l_start, lock[i].lock.l_len)) 
167             return i;
168   } 
169   return -1;
170 }
171
172 /* okay, this needs to do the following:
173  * 1) check current list of locks. error on conflict.
174  * 2) apply the lock. error on conflict with another process.
175  * 3) update the list of locks this file has.
176  * 
177  * NOTE: this treats synchronization locks a little differently. we
178  *       do the following things for those:
179  *       1) if the header file exists, all the locks go in the beginning
180  *          of that.
181  *       2) if the header file doesn't exist, we stick the locks
182  *          in the locations specified by AD_FILELOCK_RD/WR.
183  */
184 #define LOCK_RSRC_RD (0)
185 #define LOCK_RSRC_WR (1)
186 #define LOCK_DATA_RD (2)
187 #define LOCK_DATA_WR (3)
188
189 #define LOCK_RSRC_DRD (4)
190 #define LOCK_RSRC_DWR (5)
191 #define LOCK_DATA_DRD (6)
192 #define LOCK_DATA_DWR (7)
193
194 #define LOCK_RSRC_NONE (8)
195 #define LOCK_DATA_NONE (9)
196
197 /* -------------- 
198         translate a data fork lock to an offset
199 */
200
201 static int df2off(int off)
202 {
203 int start = off;
204         if (off == AD_FILELOCK_OPEN_WR)
205                 start = LOCK_DATA_WR;
206         else if (off == AD_FILELOCK_OPEN_RD)
207                 start = LOCK_DATA_RD;
208     else if (off == AD_FILELOCK_DENY_RD)
209                 start = LOCK_DATA_DRD;
210         else if (off == AD_FILELOCK_DENY_WR)
211                 start = LOCK_DATA_DWR;
212         else if (off == AD_FILELOCK_OPEN_NONE)
213                 start = LOCK_DATA_NONE;
214         return start;
215 }
216
217 /* -------------- 
218         translate a resource fork lock to an offset
219 */
220
221 static int hf2off(int off)
222 {
223 int start = off;
224         if (off == AD_FILELOCK_OPEN_WR)
225                 start = LOCK_RSRC_WR;
226         else if (off == AD_FILELOCK_OPEN_RD)
227                 start = LOCK_RSRC_RD;
228     else if (off == AD_FILELOCK_DENY_RD)
229                 start = LOCK_RSRC_DRD;
230         else if (off == AD_FILELOCK_DENY_WR)
231                 start = LOCK_RSRC_DWR;
232         else if (off == AD_FILELOCK_OPEN_NONE)
233                 start = LOCK_RSRC_NONE;
234         return start;
235 }
236
237 int ad_fcntl_lock(struct adouble *ad, const u_int32_t eid, const int type,
238                   const off_t off, const size_t len, const int user)
239 {
240   struct flock lock;
241   struct ad_fd *adf;
242   adf_lock_t *adflock, *oldlock;
243   int i;
244   
245   lock.l_start = off;
246   if (eid == ADEID_DFORK) {
247     adf = &ad->ad_df;
248     if ((type & ADLOCK_FILELOCK)) {
249         if (ad_hfileno(ad) != -1) {
250                 lock.l_start = df2off(off);
251             adf = &ad->ad_hf;
252         }
253     }
254   } else { /* rfork */
255     adf = &ad->ad_hf;
256     if (type & ADLOCK_FILELOCK) 
257       lock.l_start = hf2off(off);
258     else
259       lock.l_start += ad_getentryoff(ad, eid);
260   }
261
262   lock.l_type = XLATE_FCNTL_LOCK(type & ADLOCK_MASK);
263
264   /* see if it's locked by another user. 
265    * NOTE: this guarantees that any existing locks must be at most
266    * read locks. we use ADLOCK_WR/RD because F_RD/WRLCK aren't
267    * guaranteed to be ORable. */
268   if (adf_findxlock(adf, user, ADLOCK_WR | 
269                     ((type & ADLOCK_WR) ? ADLOCK_RD : 0), 
270                     lock.l_start, len) > -1) {
271     errno = EACCES;
272     return -1;
273   }
274   
275   /* look for any existing lock that we may have */
276   i = adf_findlock(adf, user, ADLOCK_RD | ADLOCK_WR, lock.l_start, len);
277   adflock = (i < 0) ? NULL : adf->adf_lock + i;
278
279   /* here's what we check for:
280      1) we're trying to re-lock a lock, but we didn't specify an update.
281      2) we're trying to free only part of a lock. 
282      3) we're trying to free a non-existent lock. */
283   if ((!adflock && (lock.l_type == F_UNLCK)) ||
284       (adflock && !(type & ADLOCK_UPGRADE) && 
285        ((lock.l_type != F_UNLCK) || (adflock->lock.l_start != lock.l_start) ||
286         (adflock->lock.l_len != len)))) {
287     errno = EINVAL;
288     return -1;
289   }
290
291   lock.l_whence = SEEK_SET;
292   lock.l_len = len;
293
294   /* now, update our list of locks */
295   /* clear the lock */
296   if (lock.l_type == F_UNLCK) { 
297     adf_freelock(adf, i);
298     return 0;
299   }
300
301   /* attempt to lock the file. */
302   if (fcntl(adf->adf_fd, F_SETLK, &lock) < 0) 
303     return -1;
304
305   /* we upgraded this lock. */
306   if (adflock && (type & ADLOCK_UPGRADE)) {
307     memcpy(&adflock->lock, &lock, sizeof(lock));
308     return 0;
309   } 
310
311   /* it wasn't an upgrade */
312   oldlock = NULL;
313   if ((lock.l_type = F_RDLCK) &&
314       ((i = adf_findxlock(adf, user, ADLOCK_RD, lock.l_start, len)) > -1)) {
315     oldlock = adf->adf_lock + i;
316   } 
317     
318   /* no more space. this will also happen if lockmax == lockcount == 0 */
319   if (adf->adf_lockmax == adf->adf_lockcount) { 
320     adf_lock_t *tmp = (adf_lock_t *) 
321             realloc(adf->adf_lock, sizeof(adf_lock_t)*
322                     (adf->adf_lockmax + ARRAY_BLOCK_SIZE));
323     if (!tmp)
324       goto fcntl_lock_err;
325     adf->adf_lock = tmp;
326     adf->adf_lockmax += ARRAY_BLOCK_SIZE;
327   } 
328   adflock = adf->adf_lock + adf->adf_lockcount;
329
330   /* fill in fields */
331   memcpy(&adflock->lock, &lock, sizeof(lock));
332   adflock->user = user;
333   if (oldlock)
334     adflock->refcount = oldlock->refcount;
335   else if ((adflock->refcount = calloc(1, sizeof(int))) == NULL) {
336     goto fcntl_lock_err;
337   }
338   
339   (*adflock->refcount)++;
340   adf->adf_lockcount++;
341   return 0;
342
343 fcntl_lock_err:
344   lock.l_type = F_UNLCK;
345   fcntl(adf->adf_fd, F_SETLK, &lock);
346   return -1;
347 }
348
349 /* -------------------------
350    we are using lock as tristate variable
351    
352    we have a lock ==> 1
353    no             ==> 0
354    error          ==> -1
355       
356 */
357 int ad_testlock(struct adouble *ad, int eid, const off_t off)
358 {
359   struct flock lock;
360   struct ad_fd *adf;
361   adf_lock_t *plock;
362   int i;
363
364   lock.l_start = off;
365   if (eid == ADEID_DFORK) {
366     adf = &ad->ad_df;
367     if ((ad_hfileno(ad) != -1)) {
368         adf = &ad->ad_hf;
369         lock.l_start = df2off(off);
370         }
371   } else { /* rfork */
372         adf = &ad->ad_hf;
373     lock.l_start = hf2off(off);
374   }
375
376   plock = adf->adf_lock;
377   /* Does we have a lock? */
378   lock.l_whence = SEEK_SET;
379   lock.l_len = 1;
380   for (i = 0; i < adf->adf_lockcount; i++) {
381     if (OVERLAP(lock.l_start, 1, plock[i].lock.l_start, plock[i].lock.l_len)) 
382         return 1;   /* */
383   }
384   /* Does another process have a lock? 
385      FIXME F_GETLK ?
386   */
387   lock.l_type = (ad_getoflags(ad, eid) & O_RDWR) ?F_WRLCK : F_RDLCK;                                           
388
389   if (fcntl(adf->adf_fd, F_SETLK, &lock) < 0) {
390     return (errno == EACCES || errno == EAGAIN)?1:-1;
391   }
392   
393   lock.l_type = F_UNLCK;
394   return fcntl(adf->adf_fd, F_SETLK, &lock);
395 }
396
397 /* -------------------------
398 */
399 /* with temp locks, we don't need to distinguish within the same
400  * process as everything is single-threaded. in addition, if
401  * multi-threading gets added, it will only be in a few areas. */
402 int ad_fcntl_tmplock(struct adouble *ad, const u_int32_t eid, const int type,
403                      const off_t off, const size_t len)
404 {
405   struct flock lock;
406   struct ad_fd *adf;
407   int err;
408
409   lock.l_start = off;
410   if (eid == ADEID_DFORK) {
411     adf = &ad->ad_df;
412   } else {
413     adf = &ad->ad_hf;
414     /* if ADLOCK_FILELOCK we want a lock from offset 0
415      * it's used when deleting a file:
416      * in open we put read locks on meta datas
417      * in delete a write locks on the whole file
418      * so if the file is open by somebody else it fails
419     */
420     if (!(type & ADLOCK_FILELOCK))
421         lock.l_start += ad_getentryoff(ad, eid);
422   }
423   lock.l_type = XLATE_FCNTL_LOCK(type & ADLOCK_MASK);
424   lock.l_whence = SEEK_SET;
425   lock.l_len = len;
426
427   /* okay, we might have ranges byte-locked. we need to make sure that
428    * we restore the appropriate ranges once we're done. so, we check
429    * for overlap on an unlock and relock. 
430    * XXX: in the future, all the byte locks will be sorted and contiguous.
431    *      we just want to upgrade all the locks and then downgrade them
432    *      here. */
433   err = fcntl(adf->adf_fd, F_SETLK, &lock);
434   if (!err && (lock.l_type == F_UNLCK))
435     adf_relockrange(adf, adf->adf_fd, lock.l_start, len);
436
437   return err;
438 }
439
440
441 void ad_fcntl_unlock(struct adouble *ad, const int user)
442 {
443   if (ad->ad_df.adf_fd != -1) {
444     adf_unlock(&ad->ad_df, ad->ad_df.adf_fd, user);
445   }
446   if (ad->ad_hf.adf_fd != -1) {
447     adf_unlock(&ad->ad_hf, ad->ad_hf.adf_fd, user);
448   }
449 }