1 /*
2  * Copyright (c) 2007, XenSource Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of XenSource Inc. nor the names of its contributors
13  *       may be used to endorse or promote products derived from this software
14  *       without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
20  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  * This module implements a "dot locking" style advisory file locking algorithm.
31  */
32 
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <time.h>
42 #include <dirent.h>
43 #include <limits.h>
44 #include "lock.h"
45 
46 #define unlikely(x) __builtin_expect(!!(x), 0)
47 
48 /* format: xenlk.hostname.uuid.<xf><rw>*/
49 #define LF_POSTFIX ".xenlk"
50 #define LFXL_FORMAT LF_POSTFIX ".%s.%s.x%s"
51 #define LFFL_FORMAT LF_POSTFIX ".%s.%s.f%s"
52 #define RETRY_MAX 16
53 
54 #if defined(LOGS)
55 #define LOG(format, args...) printf("%d: ", __LINE__); printf(format, ## args)
56 #else
57 #define LOG(format, args...)
58 #endif
59 
60 /* random wait - up to .5 seconds */
61 #define XSLEEP usleep(random() & 0x7ffff)
62 
63 typedef int (*eval_func)(char *name, int readonly);
64 
create_lockfn(char * fn_to_lock)65 static char *create_lockfn(char *fn_to_lock)
66 {
67         char *lockfn;
68 
69         /* allocate string to hold constructed lock file */
70         lockfn = malloc(strlen(fn_to_lock) + strlen(LF_POSTFIX) + 1);
71         if (unlikely(!lockfn)) {
72                 return 0;
73         }
74 
75         /* append postfix to file to lock */
76         strcpy(lockfn, fn_to_lock);
77         strcat(lockfn, LF_POSTFIX);
78 
79         return lockfn;
80 }
81 
create_lockfn_link(char * fn_to_lock,char * format,char * uuid,int readonly)82 static char *create_lockfn_link(char *fn_to_lock, char *format,
83                                 char *uuid, int readonly)
84 {
85         char hostname[128];
86         char *lockfn_link;
87         char *ptr;
88 
89         /* get hostname */
90         if (unlikely(gethostname(hostname, sizeof(hostname)) == -1)) {
91                 return 0;
92         }
93 
94         /* allocate string to hold constructed lock file link */
95         lockfn_link = malloc(strlen(fn_to_lock) + strlen(LF_POSTFIX) +
96                              strlen(hostname) + strlen(uuid) + 8);
97         if (unlikely(!lockfn_link)) {
98                 return 0;
99         }
100 
101         /* construct lock file link with specific format */
102         strcpy(lockfn_link, fn_to_lock);
103         ptr = lockfn_link + strlen(lockfn_link);
104         sprintf(ptr, format, hostname, uuid, readonly ? "r" : "w");
105 
106         return lockfn_link;
107 }
108 
NFSnormalizedStatTime(char * fn,struct stat * statnow,int * reterrno)109 static int NFSnormalizedStatTime(char *fn, struct stat *statnow, int *reterrno)
110 {
111         int result = LOCK_OK;
112         int uniq;
113         char *buf;
114         int fd;
115         int pid = (int)getpid();
116         int clstat;
117 
118         *reterrno = 0;
119 
120         /* create file to normalize time */
121         srandom((int)time(0) ^ pid);
122         uniq = random() % 0xffffff;
123         buf = malloc(strlen(fn) + 24);
124         if (unlikely(!buf)) { result = LOCK_ENOMEM; goto finish; }
125 
126         strcpy(buf, fn);
127         sprintf(buf + strlen(buf), ".xen%08d.tmp", uniq);
128 
129         fd = open(buf, O_WRONLY | O_CREAT, 0644);
130         if (fd == -1) { *reterrno = errno; result = LOCK_EOPEN; goto finish; }
131         clstat = close(fd);
132         if (unlikely(clstat == -1)) {
133                 LOG("fail on close\n");
134         }
135         if (lstat(buf, statnow) == -1) {
136                 unlink(buf);
137                 *reterrno = errno;
138                 result = LOCK_ESTAT;
139                 goto finish;
140         }
141         unlink(buf);
142 
143 finish:
144         return result;
145 }
146 
writer_eval(char * name,int readonly)147 static int writer_eval(char *name, int readonly)
148 {
149         return name[strlen(name)-1] == 'w';
150 }
151 
reader_eval(char * name,int readonly)152 static int reader_eval(char *name, int readonly)
153 {
154         return name[strlen(name)-1] == 'r' && !readonly;
155 }
156 
lock_holder(char * fn,char * lockfn,char * lockfn_link,int force,int readonly,int * stole,eval_func eval,int * elt,int * ioerror)157 static int lock_holder(char *fn, char *lockfn, char *lockfn_link,
158                        int force, int readonly, int *stole, eval_func eval,
159                        int *elt, int *ioerror)
160 {
161         int status = 0;
162         int ustat;
163         DIR *pd = 0;
164         struct dirent *dptr;
165         char *ptr;
166         char *dirname = malloc(strlen(lockfn));
167         char *uname = malloc(strlen(lockfn_link) + 8);
168         int elt_established = 0;
169         int fd;
170         char tmpbuf[4096];
171 
172         *stole = 0;
173         *ioerror = 0;
174         *elt = 0;
175 
176         if (!dirname) goto finish;
177         if (!uname) goto finish;
178 
179         /* get directory */
180         ptr = strrchr(lockfn, '/');
181         if (!ptr) {
182                 strcpy(dirname, ".");
183         } else {
184                 int numbytes = ptr - lockfn;
185                 strncpy(dirname, lockfn, numbytes);
186                 dirname[numbytes] = '\0';
187         }
188         pd = opendir(dirname);
189         if (!pd) {
190                 *ioerror = errno ? errno : EIO;
191                 goto finish;
192         }
193 
194         /*
195          * scan through directory entries and use eval function
196          * if we have a match (i.e. reader or writer lock) but
197          * note that if we are forcing, we will remove any and
198          * all locks that appear for target of our lock, regardless
199          * if it a reader/writer owns the lock.
200          */
201         errno = 0;
202         dptr = readdir(pd);
203         if (!dptr) {
204             *ioerror = EIO;
205         }
206         while (dptr) {
207                 char *p1 = strrchr(fn, '/');
208                 char *p2 = strrchr(lockfn, '/');
209                 char *p3 = strrchr(lockfn_link, '/');
210                 if (p1) p1+=1;
211                 if (p2) p2+=1;
212                 if (p3) p3+=1;
213                 if (strcmp(dptr->d_name, p1 ? p1 : fn) &&
214                     strcmp(dptr->d_name, p2 ? p2 : lockfn) &&
215                     strcmp(dptr->d_name, p3 ? p3 : lockfn_link) &&
216                     !strncmp(dptr->d_name, p1 ? p1 : fn, strlen(p1?p1:fn))) {
217                         strcpy(uname, dirname);
218                         strcat(uname, "/");
219                         strcat(uname, dptr->d_name);
220                         if (!elt_established) {
221                             /* read final lock file and extract lease time */
222                             fd = open(uname, O_RDONLY, 0644);
223                             memset(tmpbuf, 0, sizeof(tmpbuf));
224                             if (read(fd, tmpbuf, sizeof(tmpbuf)) < 0) {
225                                     *ioerror = errno;
226                                     status = 1;
227                                     close(fd);
228                                     goto finish;
229                             }
230                             close(fd);
231                             ptr = strrchr(tmpbuf, '.');
232                             if (ptr) {
233                                 *elt = atoi(ptr+1);
234                                 elt_established = 1;
235                             }
236                         }
237                         if (force) {
238                                 ustat = unlink(uname);
239                                 if (ustat == -1) {
240                                         LOG("failed to unlink %s\n", uname);
241                                 }
242                                 *stole = 1;
243                                 *elt = 0;
244                         } else {
245                                 if ((*eval)(dptr->d_name, readonly)) {
246                                         closedir(pd);
247                                         status = 1;
248                                         goto finish;
249                                 }
250                         }
251                 }
252                 dptr = readdir(pd);
253                 if (!dptr && errno) {
254                     *ioerror = EIO;
255                 }
256         }
257 
258         closedir(pd);
259 
260 finish:
261         free(dirname);
262         free(uname);
263 
264         /* if IO error, force a taken status */
265         return (*ioerror) ? 1 : status;
266 }
267 
lock(char * fn_to_lock,char * uuid,int force,int readonly,int * lease_time,int * retstatus)268 int lock(char *fn_to_lock, char *uuid, int force, int readonly, int *lease_time, int *retstatus)
269 {
270         char *lockfn = 0;
271         char *lockfn_xlink = 0;
272         char *lockfn_flink = 0;
273         char *buf = 0;
274         int fd;
275         int status = 0;
276         struct stat stat1, stat2;
277         int retry_attempts = 0;
278         int clstat;
279         int tmpstat;
280         int stealx = 0;
281         int stealw = 0;
282         int stealr = 0;
283         int established_lease_time = 0;
284         char tmpbuf[4096];
285         int ioerr;
286 
287         if (!fn_to_lock || !uuid) {
288                 *retstatus = LOCK_EBADPARM;
289                 return EINVAL;
290         }
291 
292         *retstatus = 0;
293 
294         /* seed random with time/pid combo */
295         srandom((int)time(0) ^ getpid());
296 
297         /* build lock file strings */
298         lockfn = create_lockfn(fn_to_lock);
299         if (unlikely(!lockfn)) { status = ENOMEM; *retstatus = LOCK_ENOMEM; goto finish; }
300 
301         lockfn_xlink = create_lockfn_link(fn_to_lock, LFXL_FORMAT,
302                                           uuid, readonly);
303         if (unlikely(!lockfn_xlink)) { status = ENOMEM; *retstatus = LOCK_ENOMEM; goto finish; }
304 
305         lockfn_flink = create_lockfn_link(fn_to_lock, LFFL_FORMAT, uuid,
306                                           readonly);
307         if (unlikely(!lockfn_flink)) { status = ENOMEM; *retstatus = LOCK_ENOMEM; goto finish; }
308 
309 try_again:
310         if (retry_attempts++ > RETRY_MAX) {
311                 if (*retstatus == LOCK_EXLOCK_OPEN) {
312                         struct stat statnow, stat_exlock;
313                         int diff;
314 
315                         if (lstat(lockfn, &stat_exlock) == -1) {
316                                 goto finish;
317                         }
318 
319                         if (NFSnormalizedStatTime(fn_to_lock, &statnow, &ioerr)) {
320                                 goto finish;
321                         }
322 
323                         diff = (int)statnow.st_mtime - (int)stat_exlock.st_mtime;
324                         if (diff > DEFAULT_LEASE_TIME_SECS) {
325                                 unlink(lockfn);
326                                 retry_attempts = 0;
327                                 goto try_again;
328                         }
329                 }
330                 goto finish;
331         }
332 
333         /* try to open exlusive lockfile */
334         fd = open(lockfn, O_WRONLY | O_CREAT | O_EXCL, 0644);
335         if (fd == -1) {
336                 LOG("Initial lockfile creation failed %s force=%d, errno=%d\n",
337                      lockfn, force, errno);
338                 if (errno == EIO) {
339                        *retstatus = LOCK_EXLOCK_OPEN;
340                        status = EIO;
341                        goto finish;
342                 }
343                 /* already owned? (hostname & uuid match, skip time bits) */
344                 errno = 0;
345                 fd = open(lockfn, O_RDWR, 0644);
346                 if (fd != -1) {
347                         buf = malloc(strlen(lockfn_xlink)+1);
348                         if (!buf) {
349                                 clstat = close(fd);
350                                 if (unlikely(clstat == -1)) {
351                                         LOG("fail on close\n");
352                                 }
353                                 *retstatus = LOCK_ENOMEM;
354                                 status = ENOMEM;
355                                 goto finish;
356                         }
357                         if (read(fd, buf, strlen(lockfn_xlink)) !=
358                            (strlen(lockfn_xlink))) {
359                                 clstat = close(fd);
360                                 if (unlikely(clstat == -1)) {
361                                         LOG("fail on close\n");
362                                 }
363                                 free(buf);
364                                 goto force_lock;
365                         }
366                         if (!strncmp(buf, lockfn_xlink, strlen(lockfn_xlink)-1)) {
367                                 LOG("lock owned by us, reasserting\n");
368                                 /* our lock, reassert by rewriting below */
369                                 if (lseek(fd, 0, SEEK_SET) == -1) {
370                                         clstat = close(fd);
371                                         if (unlikely(clstat == -1)) {
372                                                 LOG("fail on close\n");
373                                         }
374                                         goto force_lock;
375                                 }
376                                 free(buf);
377                                 goto skip;
378                         }
379                         free(buf);
380                         clstat = close(fd);
381                         if (unlikely(clstat == -1)) {
382                                 LOG("fail on close\n");
383                         }
384                 }
385 force_lock:
386                 if (errno == EIO) {
387                        *retstatus = LOCK_EXLOCK_OPEN;
388                        status = EIO;
389                        goto finish;
390                 }
391                 if (force) {
392                         /* remove lock file, we are forcing lock, try again */
393                         status = unlink(lockfn);
394                         if (unlikely(status == -1)) {
395                                 if (errno == EIO) {
396                                        *retstatus = LOCK_EXLOCK_OPEN;
397                                        status = EIO;
398                                        goto finish;
399                                 }
400                                 LOG("force removal of %s lockfile failed, "
401                                     "errno=%d, trying again\n", lockfn, errno);
402                         }
403                         stealx = 1;
404                 }
405                 XSLEEP;
406                 *retstatus = LOCK_EXLOCK_OPEN;
407                 goto try_again;
408         }
409 
410         LOG("lockfile created %s\n", lockfn);
411 
412 skip:
413         /*
414          * write into the temporary xlock
415          */
416         if (write(fd, lockfn_xlink, strlen(lockfn_xlink)) !=
417                 strlen(lockfn_xlink)) {
418                 if (errno == EIO) {
419                        *retstatus = LOCK_EXLOCK_WRITE;
420                        status = EIO;
421                        goto finish;
422                 }
423                 status = errno;
424                 clstat = close(fd);
425                 if (unlikely(clstat == -1)) {
426                         LOG("fail on close\n");
427                 }
428                 XSLEEP;
429                 *retstatus = LOCK_EXLOCK_WRITE;
430                 if (unlink(lockfn) == -1)  {
431                         LOG("removal of %s lockfile failed, "
432                             "errno=%d, trying again\n", lockfn, errno);
433                 }
434                 goto try_again;
435         }
436         clstat = close(fd);
437         if (unlikely(clstat == -1)) {
438                 LOG("fail on close\n");
439         }
440 
441         while (retry_attempts++ < RETRY_MAX) {
442                 tmpstat = link(lockfn, lockfn_xlink);
443                 LOG("linking %s and %s\n", lockfn, lockfn_xlink);
444                 if ((tmpstat == -1) && (errno != EEXIST)) {
445                         LOG("link status is %d, errno=%d\n", tmpstat, errno);
446                 }
447 
448                 if ((lstat(lockfn, &stat1) == -1) ||
449                     (lstat(lockfn_xlink, &stat2) == -1)) {
450                         /* try again, cleanup first */
451                         tmpstat = unlink(lockfn);
452                         if (unlikely(tmpstat == -1)) {
453                                 LOG("error removing lock file %s", lockfn);
454                         }
455                         tmpstat = unlink(lockfn_xlink);
456                         if (unlikely(tmpstat == -1)) {
457                                 LOG("error removing linked lock file %s",
458                                     lockfn_xlink);
459                         }
460                         XSLEEP;
461                         status = LOCK_ESTAT;
462                         goto finish;
463                 }
464 
465                 /* compare inodes */
466                 if (stat1.st_ino == stat2.st_ino) {
467                         /* success, inodes are the same */
468                         /* should we check that st_nlink's are also 2?? */
469                         *retstatus = LOCK_OK;
470                         status = 0;
471                         tmpstat = unlink(lockfn_xlink);
472                         if (unlikely(tmpstat == -1)) {
473                                 LOG("error removing linked lock file %s",
474                                     lockfn_xlink);
475                         }
476                         goto finish;
477                 } else {
478                        status = errno;
479                         /* try again, cleanup first */
480                         tmpstat = unlink(lockfn);
481                         if (unlikely(tmpstat == -1)) {
482                                 LOG("error removing lock file %s", lockfn);
483                         }
484                         tmpstat = unlink(lockfn_xlink);
485                         if (unlikely(tmpstat == -1)) {
486                                 LOG("error removing linked lock file %s",
487                                     lockfn_xlink);
488                         }
489                         XSLEEP;
490                         *retstatus = LOCK_EINODE;
491                         goto try_again;
492                 }
493         }
494 
495 finish:
496         if (!*retstatus) {
497 
498                 /* we have exclusive lock */
499 
500                 status = 0;
501 
502                 /* fast check, see if we own a final lock and are reasserting */
503                 if (!lstat(lockfn_flink, &stat1)) {
504                         char *ptr;
505 
506                         /* set the return value to notice this is a reassert */
507                         *retstatus = 1;
508 
509                         /* read existing lock file and extract
510                            established lease time */
511                         fd = open(lockfn_flink, O_RDONLY, 0644);
512                         memset(tmpbuf, 0, sizeof(tmpbuf));
513                         if (read(fd, tmpbuf, sizeof(tmpbuf)) < 0) {
514                                 if (errno == EIO) {
515                                         close(fd);
516                                         *retstatus = LOCK_EINODE;
517                                         status = EIO;
518                                         goto skip_scan;
519                                 }
520                         }
521                         close(fd);
522                         ptr = strrchr(tmpbuf, '.');
523                         if (ptr) {
524                             *lease_time = atoi(ptr+1);
525                         } else {
526                             *lease_time = 10; /* wkchack */
527                         }
528                         goto skip_scan;
529                 } else {
530                        if (errno == EIO) {
531                                *retstatus = LOCK_EINODE;
532                                status = EIO;
533                                goto skip_scan;
534                        }
535                 }
536 
537                 /* we allow exclusive writer, or multiple readers */
538                 if (lock_holder(fn_to_lock, lockfn, lockfn_flink, force,
539                                      readonly, &stealw, writer_eval,
540                                      &established_lease_time, &ioerr)) {
541                         if (ioerr) {
542                             *retstatus = LOCK_EREAD;
543                             status = ioerr;
544                             goto skip_scan;
545                         }
546                         *retstatus = LOCK_EHELD_WR;
547                 } else if (lock_holder(fn_to_lock, lockfn, lockfn_flink, force,
548                                      readonly, &stealr, reader_eval,
549                                      &established_lease_time, &ioerr)) {
550                         if (ioerr) {
551                             *retstatus = LOCK_EREAD;
552                             status = ioerr;
553                             goto skip_scan;
554                         }
555                         *retstatus = LOCK_EHELD_RD;
556                 }
557                 if (established_lease_time) *lease_time =
558                                                  established_lease_time;
559         }
560 
561 skip_scan:
562         if (*retstatus >= 0) {
563                 /* update file, changes last modify time */
564                 fd = open(lockfn_flink, O_WRONLY | O_CREAT, 0644);
565                 if (fd == -1) {
566                         *retstatus = LOCK_EOPEN;
567                         status = errno;
568                 } else {
569                         char tmpbuf[32];
570                         int failed_write;
571                         memset(tmpbuf, 0, sizeof(tmpbuf));
572                         sprintf(tmpbuf, ".%d", *lease_time);
573                         failed_write = write(fd, lockfn_flink,
574                                              strlen(lockfn_flink)) !=
575                                        strlen(lockfn_flink);
576                         if (failed_write) status = errno;
577                         failed_write |= write(fd, tmpbuf, strlen(tmpbuf)) !=
578                                        strlen(tmpbuf);
579                         if (failed_write) status = errno;
580                         if (failed_write) {
581                                 clstat = close(fd);
582                                 if (unlikely(clstat == -1)) {
583                                         LOG("fail on close\n");
584                                 }
585                                 XSLEEP;
586                                 *retstatus = LOCK_EUPDATE;
587                                 goto try_again;
588                         }
589                 }
590                 clstat = close(fd);
591                 if (unlikely(clstat == -1)) {
592                         LOG("fail on close\n");
593                 }
594         }
595 
596         if (!*retstatus && force && (stealx || stealw || stealr)) {
597                 struct timeval timeout;
598 
599                 /* enforce quiet time on steal */
600                 timeout.tv_sec = *lease_time;
601                 timeout.tv_usec = 0;
602                 select(0, 0, 0, 0, &timeout);
603         }
604 
605         /* remove exclusive lock, final read/write locks will hold */
606         tmpstat = unlink(lockfn);
607         if (unlikely(tmpstat == -1)) {
608                 LOG("error removing exclusive lock file %s",
609                     lockfn);
610         }
611 
612         free(lockfn);
613         free(lockfn_xlink);
614         free(lockfn_flink);
615 
616         /* set lease time to -1 if error, so no one is apt to use it */
617         if (*retstatus < 0) *lease_time = -1;
618 
619         LOG("returning status %d, errno=%d\n", status, errno);
620         return status;
621 }
622 
623 
unlock(char * fn_to_unlock,char * uuid,int readonly,int * status)624 int unlock(char *fn_to_unlock, char *uuid, int readonly, int *status)
625 {
626         char *lockfn_link = 0;
627         int reterrno = 0;
628 
629         if (!fn_to_unlock || !uuid) {
630                 *status = LOCK_EBADPARM;
631                 return 0;
632         }
633 
634         lockfn_link = create_lockfn_link(fn_to_unlock, LFFL_FORMAT, uuid,
635                                          readonly);
636         if (unlikely(!lockfn_link)) { *status = LOCK_ENOMEM; goto finish; }
637 
638         if (unlink(lockfn_link) == -1) {
639                 LOG("error removing linked lock file %s", lockfn_link);
640                 reterrno = errno;
641                 *status = LOCK_ENOLOCK;
642                 goto finish;
643         }
644 
645         *status = LOCK_OK;
646 
647 finish:
648         free(lockfn_link);
649         return reterrno;
650 }
651 
lock_delta(char * fn,int * ret_lease,int * max_lease)652 int lock_delta(char *fn, int *ret_lease, int *max_lease)
653 {
654         int reterrno = 0;
655         DIR *pd = 0;
656         struct dirent *dptr;
657         char *ptr;
658         int result = INT_MAX;
659         struct stat statbuf, statnow;
660         char *dirname = malloc(strlen(fn));
661         char *uname = malloc(strlen(fn) + 8);
662         int elt_established = 0;
663         char *dotptr;
664         char tmpbuf[4096];
665         int fd;
666 
667         if (!fn || !dirname || !uname) {
668                 *ret_lease = LOCK_EBADPARM;
669                 *max_lease = -1;
670                 return 0;
671         }
672 
673         if (NFSnormalizedStatTime(fn, &statnow, &reterrno)) {
674                 result = LOCK_ESTAT;
675                 goto finish;
676         }
677 
678         /* get directory */
679         ptr = strrchr(fn, '/');
680         if (!ptr) {
681                 strcpy(dirname, ".");
682                 ptr = fn;
683         } else {
684                 int numbytes = ptr - fn;
685                 strncpy(dirname, fn, numbytes);
686                 ptr += 1;
687         }
688         pd = opendir(dirname);
689         if (!pd) { reterrno = errno; goto finish; }
690 
691         dptr = readdir(pd);
692         while (dptr) {
693                 if (strcmp(dptr->d_name, ptr) &&
694                     !strncmp(dptr->d_name, ptr,  strlen(ptr))) {
695                         char *fpath = malloc(strlen(dptr->d_name) +
696                                              strlen(dirname) + 2);
697                         if (!fpath) {
698                             closedir(pd);
699                             result = LOCK_ENOMEM;
700                             goto finish;
701                         }
702                         strcpy(fpath, dirname);
703                         strcat(fpath, "/");
704                         strcat(fpath, dptr->d_name);
705                         if (lstat(fpath, &statbuf) != -1) {
706                                 int diff = (int)statnow.st_mtime -
707                                            (int)statbuf.st_mtime;
708                                 /* adjust diff if someone updated the lock
709                                    between now and when we created the "now"
710                                    file
711                                  */
712                                 diff = (diff < 0) ? 0 : diff;
713                                 result = diff < result ? diff : result;
714                         } else {
715                             closedir(pd);
716                             reterrno = errno;
717                             goto finish;
718                         }
719 
720                         if (!elt_established) {
721                             /* read final lock file and extract lease time */
722                             fd = open(fpath, O_RDONLY, 0644);
723                             memset(tmpbuf, 0, sizeof(tmpbuf));
724                             if (read(fd, tmpbuf, sizeof(tmpbuf)) < 0) {
725                                 /* error on read? */
726                             }
727                             close(fd);
728                             dotptr = strrchr(tmpbuf, '.');
729                             if (dotptr) {
730                                 *max_lease = atoi(dotptr+1);
731                                 elt_established = 1;
732                             }
733                         }
734 
735                         free(fpath);
736                 }
737                 dptr = readdir(pd);
738         }
739 
740         closedir(pd);
741 
742 finish:
743         free(dirname);
744         free(uname);
745 
746         /* returns smallest lock time, or error */
747         if (result == INT_MAX) result = LOCK_ENOLOCK;
748 
749         /* set lease time to -1 if error, so no one is apt to use it */
750         if ((result < 0) || reterrno) *max_lease = -1;
751         *ret_lease = result;
752         return reterrno;
753 }
754 
755 #if defined(TEST)
756 /*
757  * the following is for sanity testing.
758  */
759 
usage(char * prg)760 static void usage(char *prg)
761 {
762         printf("usage %s\n"
763                "    dtr <filename>]\n"
764                "    p <filename> [num iterations]\n"
765                "    u <filename> [0|1] [<uniqid>]\n"
766                "    l <filename> [0|1] [0|1] [<uniqid>] [<leasetime>]\n", prg);
767         printf("        p : perf test lock take and reassert\n");
768         printf("        d : delta lock time\n");
769         printf("        t : test the file (after random locks)\n");
770         printf("        r : random lock tests (must ^C)\n");
771         printf("        u : unlock, readonly? uniqID (default is PID)\n");
772         printf("        l : lock, readonly? force?, uniqID (default is PID), lease time\n");
773 }
774 
test_file(char * fn)775 static void test_file(char *fn)
776 {
777         FILE *fptr;
778         int prev_count = 0;
779         int count, pid, time;
780 
781         fptr = fopen(fn, "r");
782         if (!fptr) {
783                 LOG("ERROR on file %s open, errno=%d\n", fn, errno);
784                 return;
785         }
786 
787         while (!feof(fptr)) {
788                 fscanf(fptr, "%d %d %d\n", &count, &pid, &time);
789                 if (prev_count != count) {
790                         LOG("ERROR: prev_count=%d, count=%d, pid=%d, time=%d\n",
791                                     prev_count, count, pid, time);
792                 }
793                 prev_count = count + 1;
794         }
795 }
796 
random_locks(char * fn)797 static void random_locks(char *fn)
798 {
799         int pid = getpid();
800         int status;
801         char *filebuf = malloc(256);
802         int count = 0;
803         int dummy;
804         int clstat;
805         char uuid[12];
806         int readonly;
807         int lease = DEFAULT_LEASE_TIME_SECS;
808         int err;
809 
810         /* this will never return, kill to exit */
811 
812         srandom((int)time(0) ^ pid);
813 
814         LOG("pid: %d using file %s\n", pid, fn);
815         sprintf(uuid, "%08d", pid);
816 
817         while (1) {
818                 XSLEEP;
819                 readonly = random()  & 1;
820                 sysstatus = lock(fn, uuid, 0, readonly, &lease, status);
821                 if (status == LOCK_OK) {
822                         /* got lock, open, read, modify write close file */
823                         int fd = open(fn, O_RDWR, 0644);
824                         if (fd == -1) {
825                                 LOG("pid: %d ERROR on file %s open, errno=%d\n",
826                                     pid, fn, errno);
827                         } else {
828                             if (!readonly) {
829                                 /* ugly code to read data in test format */
830                                 /* format is "%d %d %d" 'count pid time' */
831                                 struct stat statbuf;
832                                 int bytes;
833                                 status = stat(fn, &statbuf);
834                                 if (status != -1) {
835                                         if (statbuf.st_size > 256) {
836                                                 lseek(fd, -256, SEEK_END);
837                                         }
838                                         memset(filebuf, 0, 256);
839                                         bytes = read(fd, filebuf, 256);
840                                         if (bytes) {
841                                                 int bw = bytes-2;
842                                                 while (bw && filebuf[bw]!='\n')
843                                                         bw--;
844                                                 if (!bw) bw = -1;
845                                                 sscanf(&filebuf[bw+1],
846                                                        "%d %d %d",
847                                                        &count, &dummy, &dummy);
848                                                 count += 1;
849                                         }
850                                         lseek(fd, 0, SEEK_END);
851                                         sprintf(filebuf, "%d %d %d\n",
852                                                 count, pid, (int)time(0));
853                                         write(fd, filebuf, strlen(filebuf));
854                                 } else {
855                                         LOG("pid: %d ERROR on file %s stat, "
856                                             "errno=%d\n", pid, fn, errno);
857                                 }
858                             }
859                             clstat = close(fd);
860                             if (unlikely(clstat == -1)) {
861                                     LOG("fail on close\n");
862                             }
863                         }
864                         XSLEEP;
865                         err = unlock(fn, uuid, readonly, &status);
866                         LOG("unlock status is %d (err=%d)\n", status, err);
867                 }
868         }
869 }
870 
perf_lock(char * fn,int loops)871 static void perf_lock(char *fn, int loops)
872 {
873     int sysstatus;
874     char buf[9];
875     int start = loops;
876     int lease = DEFAULT_LEASE_TIME_SECS;
877 
878     sprintf(buf, "%08d", getpid());
879 
880     while (loops--) {
881         sysstatus = lock(fn, buf, 0, 0, &lease, &status);
882         if (status < 0) {
883             printf("failed to get lock at iteration %d errno=%d\n",
884                    start - loops, errno);
885             return;
886         }
887     }
888     unlock(fn, buf, 0, &status);
889 }
890 
main(int argc,char * argv[])891 int main(int argc, char *argv[])
892 {
893         int status;
894         char *ptr;
895         char uuid[12];
896         int force;
897         int readonly;
898         int max_lease, cur_lease;
899         int intstatus;
900         int lease = DEFAULT_LEASE_TIME_SECS;
901 
902         if (argc < 3) {
903                 usage(argv[0]);
904                 return 0;
905         }
906 
907         sprintf(uuid, "%08d", getpid());
908         ptr = uuid;
909 
910         if (!strcmp(argv[1],"d")) {
911                 status = lock_delta(argv[2], &cur_lease, &max_lease);
912 
913                 printf("lock delta for %s is %d seconds, max lease is %d\n",
914                        argv[2], cur_lease, max_lease);
915         } else if (!strcmp(argv[1],"t")) {
916                 test_file(argv[2]);
917         } else if (!strcmp(argv[1],"r")) {
918                 random_locks(argv[2]);
919         } else if (!strcmp(argv[1],"p")) {
920                 perf_lock(argv[2], argc < 3 ? 100000 : atoi(argv[3]));
921         } else if (!strcmp(argv[1],"l")) {
922                 if (argc < 4) force = 0; else force = atoi(argv[3]);
923                 if (argc < 5) readonly = 0; else readonly = atoi(argv[4]);
924                 if (argc >= 6) ptr = argv[5];
925                 if (argc == 7) lease = atoi(argv[6]);
926                 status = lock(argv[2], ptr, readonly, force, &lease, &intstatus);
927                 printf("lock status = %d\n", status);
928         } else if (!strcmp(argv[1],"u") ) {
929                 if (argc < 5) readonly = 0; else readonly = atoi(argv[3]);
930                 if (argc == 5) ptr = argv[4];
931                 status = unlock(argv[2], ptr, readonly, &intstatus);
932                 printf("unlock status = %d\n", intstatus);
933         } else {
934                 usage(argv[0]);
935         }
936 
937         return status;
938 }
939 #elif defined(UTIL)
940 /*
941  * the following is used for non-libary, standalone
942  * program utility as a shell program
943  */
944 
usage(char * prg)945 static void usage(char *prg)
946 {
947         printf("usage %s\n"
948                "    delta <filename>\n"
949                "    unlock <filename> <r|w> <uniqid>\n"
950                "    lock <filename> <r|w> <0|1> <uniqid> <leasetime>\n", prg);
951         printf("        delta : get time since lock last refreshed\n");
952         printf("                returns delta time and max lease time in seconds\n");
953         printf("        unlock: unlock request filename, r|w,  uniqID\n");
954         printf("                returns status (success is 0)\n");
955         printf("        lock  : lock request filename,  r|w, force?, uniqID, lease time request\n");
956         printf("                returns status (success is 0) and established lease time in seconds\n");
957 }
958 
main(int argc,char * argv[])959 int main(int argc, char *argv[])
960 {
961         int status = 0;
962         int dlock;
963         char *ptr;
964         int force;
965         int readonly;
966         int cur_lease, max_lease, intstatus;
967         int lease = DEFAULT_LEASE_TIME_SECS;
968 
969         if (argc < 3) {
970                 if (argc == 2 && !strcmp(argv[1], "-h")) {
971                     usage(argv[0]);
972                 } else {
973                     printf("%d\n", LOCK_EUSAGE);
974                 }
975                 return 0;
976         }
977 
978         if (!strcmp(argv[1],"delta") && (argc == 3)) {
979                 status = lock_delta(argv[2], &cur_lease, &max_lease);
980                 printf("%d %d\n", cur_lease, max_lease);
981         } else if (!strcmp(argv[1],"lock") && (argc == 7)) {
982                 readonly = (strcmp(argv[3], "r") == 0) ? 1 : 0;
983                 force = atoi(argv[4]);
984                 ptr = argv[5];
985                 lease = atoi(argv[6]);
986                 status = lock(argv[2], ptr, force, readonly, &lease, &intstatus);
987                 printf("%d %d\n", intstatus, lease);
988         } else if (!strcmp(argv[1],"unlock") && (argc == 5)) {
989                 readonly = (strcmp(argv[3], "r") == 0) ? 1 : 0;
990                 ptr = argv[4];
991                 status = unlock(argv[2], ptr, readonly, &intstatus);
992                 printf("%d\n", intstatus);
993         } else {
994                 printf("%d\n", LOCK_EUSAGE);
995         }
996 
997         /* this is either 0 or a system defined errno */
998         return status;
999 }
1000 #endif
1001