]> arthur.barton.de Git - netatalk.git/blob - libatalk/adouble/ad_lock.c
massive commenting/autoconf changes
[netatalk.git] / libatalk / adouble / ad_lock.c
1 /* 
2  * $Id: ad_lock.c,v 1.3 2001-06-29 14:14:46 rufustfirefly 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 int ad_fcntl_lock(struct adouble *ad, const u_int32_t eid, const int type,
189                   const off_t off, const size_t len, const int user)
190 {
191   struct flock lock;
192   struct ad_fd *adf;
193   adf_lock_t *adflock, *oldlock;
194   int i;
195   
196   lock.l_start = off;
197   if (eid == ADEID_DFORK) {
198     if ((type & ADLOCK_FILELOCK) && (ad_hfileno(ad) != -1)) {
199       adf = &ad->ad_hf;
200       if (off == AD_FILELOCK_WR)
201         lock.l_start = LOCK_DATA_WR;
202       else if (off == AD_FILELOCK_RD)
203         lock.l_start = LOCK_DATA_RD;
204     } else
205       adf = &ad->ad_df;
206
207   } else { /* rfork */
208     adf = &ad->ad_hf;
209     if (type & ADLOCK_FILELOCK) {
210       if (off == AD_FILELOCK_WR)
211         lock.l_start = LOCK_RSRC_WR;
212       else if (off == AD_FILELOCK_RD)
213         lock.l_start = LOCK_RSRC_RD;
214     } else
215       lock.l_start += ad_getentryoff(ad, eid);
216   }
217
218   lock.l_type = XLATE_FCNTL_LOCK(type & ADLOCK_MASK);
219
220   /* see if it's locked by another user. 
221    * NOTE: this guarantees that any existing locks must be at most
222    * read locks. we use ADLOCK_WR/RD because F_RD/WRLCK aren't
223    * guaranteed to be ORable. */
224   if (adf_findxlock(adf, user, ADLOCK_WR | 
225                     ((type & ADLOCK_WR) ? ADLOCK_RD : 0), 
226                     lock.l_start, len) > -1) {
227     errno = EACCES;
228     return -1;
229   }
230   
231   /* look for any existing lock that we may have */
232   i = adf_findlock(adf, user, ADLOCK_RD | ADLOCK_WR, lock.l_start, len);
233   adflock = (i < 0) ? NULL : adf->adf_lock + i;
234
235   /* here's what we check for:
236      1) we're trying to re-lock a lock, but we didn't specify an update.
237      2) we're trying to free only part of a lock. 
238      3) we're trying to free a non-existent lock. */
239   if ((!adflock && (lock.l_type == F_UNLCK)) ||
240       (adflock && !(type & ADLOCK_UPGRADE) && 
241        ((lock.l_type != F_UNLCK) || (adflock->lock.l_start != lock.l_start) ||
242         (adflock->lock.l_len != len)))) {
243     errno = EINVAL;
244     return -1;
245   }
246
247   lock.l_whence = SEEK_SET;
248   lock.l_len = len;
249
250   /* now, update our list of locks */
251   /* clear the lock */
252   if (lock.l_type == F_UNLCK) { 
253     adf_freelock(adf, i);
254     return 0;
255   }
256
257   /* attempt to lock the file. */
258   if (fcntl(adf->adf_fd, F_SETLK, &lock) < 0) 
259     return -1;
260
261   /* we upgraded this lock. */
262   if (adflock && (type & ADLOCK_UPGRADE)) {
263     memcpy(&adflock->lock, &lock, sizeof(lock));
264     return 0;
265   } 
266
267   /* it wasn't an upgrade */
268   oldlock = NULL;
269   if ((lock.l_type = F_RDLCK) &&
270       ((i = adf_findxlock(adf, user, ADLOCK_RD, lock.l_start, len)) > -1)) {
271     oldlock = adf->adf_lock + i;
272   } 
273     
274   /* no more space. this will also happen if lockmax == lockcount == 0 */
275   if (adf->adf_lockmax == adf->adf_lockcount) { 
276     adf_lock_t *tmp = (adf_lock_t *) 
277             realloc(adf->adf_lock, sizeof(adf_lock_t)*
278                     (adf->adf_lockmax + ARRAY_BLOCK_SIZE));
279     if (!tmp)
280       goto fcntl_lock_err;
281     adf->adf_lock = tmp;
282     adf->adf_lockmax += ARRAY_BLOCK_SIZE;
283   } 
284   adflock = adf->adf_lock + adf->adf_lockcount;
285
286   /* fill in fields */
287   memcpy(&adflock->lock, &lock, sizeof(lock));
288   adflock->user = user;
289   if (oldlock)
290     adflock->refcount = oldlock->refcount;
291   else if ((adflock->refcount = calloc(1, sizeof(int))) == NULL) {
292     goto fcntl_lock_err;
293   }
294   
295   (*adflock->refcount)++;
296   adf->adf_lockcount++;
297   return 0;
298
299 fcntl_lock_err:
300   lock.l_type = F_UNLCK;
301   fcntl(adf->adf_fd, F_SETLK, &lock);
302   return -1;
303 }
304
305
306 /* with temp locks, we don't need to distinguish within the same
307  * process as everything is single-threaded. in addition, if
308  * multi-threading gets added, it will only be in a few areas. */
309 int ad_fcntl_tmplock(struct adouble *ad, const u_int32_t eid, const int type,
310                      const off_t off, const size_t len)
311 {
312   struct flock lock;
313   struct ad_fd *adf;
314   int err;
315
316   lock.l_start = off;
317   if (eid == ADEID_DFORK) {
318     adf = &ad->ad_df;
319   } else {
320     adf = &ad->ad_hf;
321     lock.l_start += ad_getentryoff(ad, eid);
322   }
323   lock.l_type = XLATE_FCNTL_LOCK(type & ADLOCK_MASK);
324   lock.l_whence = SEEK_SET;
325   lock.l_len = len;
326
327   /* okay, we might have ranges byte-locked. we need to make sure that
328    * we restore the appropriate ranges once we're done. so, we check
329    * for overlap on an unlock and relock. 
330    * XXX: in the future, all the byte locks will be sorted and contiguous.
331    *      we just want to upgrade all the locks and then downgrade them
332    *      here. */
333   err = fcntl(adf->adf_fd, F_SETLK, &lock);
334   if (!err && (lock.l_type == F_UNLCK))
335     adf_relockrange(adf, adf->adf_fd, lock.l_start, len);
336
337   return err;
338 }
339
340
341 void ad_fcntl_unlock(struct adouble *ad, const int user)
342 {
343   if (ad->ad_df.adf_fd != -1) {
344     adf_unlock(&ad->ad_df, ad->ad_df.adf_fd, user);
345   }
346   if (ad->ad_hf.adf_fd != -1) {
347     adf_unlock(&ad->ad_hf, ad->ad_hf.adf_fd, user);
348   }
349 }
350
351 /* byte-range locks. ad_lock is used by afp_bytelock and afp_openfork
352  * to establish locks. both ad_lock and ad_tmplock take 0, 0, 0 to
353  * signify locking of the entire file. in the absence of working 
354  * byte-range locks, this will default to file-wide flock-style locks.
355  */
356 int ad_flock_lock(struct adouble *ad, const u_int32_t eid, const int type,
357                   const off_t off, const size_t len, const int user)
358 {
359   int err, lock_type;
360   
361   lock_type = XLATE_FLOCK(type & ADLOCK_MASK);
362   if (eid == ADEID_DFORK) {
363     if ((err = flock(ad_dfileno(ad), lock_type | LOCK_NB)) == 0)
364       ad->ad_df.adf_lockcount = lock_type;
365   } else if ((err = flock(ad_hfileno(ad), lock_type | LOCK_NB)) == 0)
366     ad->ad_hf.adf_lockcount = lock_type;
367
368   if (err) {
369     if ((EWOULDBLOCK != EAGAIN) && (errno == EWOULDBLOCK))
370       errno = EAGAIN;
371     return -1;
372   } 
373
374   return 0; 
375 }
376
377 /* ad_tmplock is used by afpd to lock actual read/write operations. 
378  * it saves the current lock state before attempting to lock to prevent
379  * mixups. if byte-locks don't exist, it will lock the entire file with
380  * an flock. we can be a little smart here by just upgrading/downgrading
381  * locks. */
382 int ad_flock_tmplock(struct adouble *ad, const u_int32_t eid, const int type,
383                      const off_t off, const size_t len) 
384 {
385   int fd, oldlock, lock_type;
386   
387   if (eid == ADEID_DFORK) {
388     oldlock = ad->ad_df.adf_lockcount;
389     fd = ad_dfileno(ad);
390   } else {
391     oldlock = ad->ad_hf.adf_lockcount;
392     fd = ad_hfileno(ad);
393   }
394
395   /* if we already have a write lock, we don't need to do anything */
396   if (oldlock == LOCK_EX) {
397     return 0;
398   }
399
400   /* if we have a read lock, upgrade it if necessary */
401   lock_type = XLATE_FLOCK(type & ADLOCK_MASK);
402   if (oldlock == LOCK_SH) {
403     if (lock_type == LOCK_EX) 
404       return flock(fd, LOCK_EX | LOCK_NB);
405     else if (lock_type == LOCK_UN) /* reset it */
406       return flock(fd, LOCK_SH | LOCK_NB);
407     else /* do nothing */
408       return 0;
409   }
410
411   /* if we don't already have a lock, just do it. */
412   return flock(fd, lock_type | LOCK_NB);
413 }
414
415