]> git.p6c8.net - selfforum.git/blob - selfforum-cgi/shared/Lock.pm
3f01861712c1a2c562af21ac2a83b9288f9967e7
[selfforum.git] / selfforum-cgi / shared / Lock.pm
1 package Lock;
2
3 ################################################################################
4 # #
5 # File: shared/Lock.pm #
6 # #
7 # Authors: Andre Malo <nd@o3media.de>, 2001-04-01 #
8 # #
9 # Description: file locking #
10 # #
11 ################################################################################
12
13 use strict;
14 use vars qw(
15 @EXPORT_OK
16 %EXPORT_TAGS
17 %LOCKED
18 $Timeout
19 $violentTimeout
20 $masterTimeout
21 $iAmMaster
22 );
23
24 use Carp;
25 use Fcntl;
26
27 ################################################################################
28 #
29 # Export
30 #
31 use base qw(Exporter);
32
33 @EXPORT_OK = qw(
34 lock_file
35 unlock_file
36 write_lock_file
37 write_unlock_file
38 violent_unlock_file
39 set_master_lock
40 release_file
41 );
42
43 %EXPORT_TAGS = (
44 READ => [qw(
45 lock_file
46 unlock_file
47 violent_unlock_file
48 )],
49 WRITE => [qw(
50 write_lock_file
51 write_unlock_file
52 violent_unlock_file
53 )],
54 ALL => \@EXPORT_OK
55 );
56
57 ### sub ~file ($) ##############################################################
58 #
59 # create lock file names
60 #
61 sub reffile ($) {
62 return $_[0].'.lock.ref';
63 }
64 sub lockfile ($) {
65 return $_[0].'.lock';
66 }
67 sub masterfile ($) {
68 return $_[0].'.master';
69 }
70 sub masterlockfile ($) {
71 return lockfile(masterfile $_[0]);
72 }
73
74 ################################################################################
75 #
76 # Windows section (no symlinks)
77 #
78
79 ### sub w_lock_file ($;$) ######################################################
80 #
81 # set read lock (shared lock)
82 # (for no-symlink-systems)
83 #
84 # Params: $filename - file to lock
85 # $timeout - Lock Timeout (sec.)
86 #
87 # Return: Status Code (Bool)
88 #
89 sub w_lock_file ($;$) {
90 my $filename = shift;
91 my $timeout = +shift || $Timeout;
92
93 unless ($LOCKED{$filename}) {
94 if (-f masterlockfile($filename)) {
95 for (1..$timeout) {
96
97 # try to increment the reference counter
98 #
99 if (set_ref($filename,1,$timeout)) {
100 $LOCKED{$filename}=1;
101 return 1;
102 }
103 sleep (1);
104 }
105 }
106 else {
107 # master lock is set or file has not been released yet
108 return;
109 }
110 }
111
112 # time out
113 # maybe the system is occupied
114 0;
115 }
116
117 ### sub w_unlock_file ($;$) ####################################################
118 #
119 # remove read lock (shared lock)
120 # (for no-symlink-systems)
121 #
122 # Params: $filename - locked file
123 # $timeout - timeout (sec.)
124 #
125 # Return: Status Code (Bool)
126 #
127 sub w_unlock_file ($;$) {
128 my $filename = shift;
129 my $timeout = shift || $Timeout;
130
131 if ($LOCKED{$filename}) {
132 if ($LOCKED{$filename} == 3) {
133 return unless write_unlock_file($filename, $timeout);
134 $LOCKED{$filename} = 1;
135 }
136 if ($LOCKED{$filename} == 1) {
137 if (-f masterlockfile($filename)) {
138
139 # try do decrement the reference counter
140 #
141 if (set_ref($filename,-1,$timeout)) {
142 delete $LOCKED{$filename};
143 return 1;
144 }
145 }
146 }
147 }
148
149 # time out
150 return;
151 }
152
153 ### sub w_write_lock_file ($;$) ################################################
154 #
155 # set write lock (exclusive lock)
156 # (for no-symlink-systems)
157 #
158 # Params: $filename - file to lock
159 # $timeout - timeout (sec.)
160 #
161 # Return: Status Code (Bool)
162 #
163 sub w_write_lock_file ($;$) {
164 my $filename=shift;
165 my $timeout= shift || $Timeout;
166 my $rest = ($LOCKED{$filename} and $LOCKED{$filename} == 1) ? 1 : 0;
167
168 if (-f masterlockfile($filename) or $iAmMaster) {
169
170 # announce the write lock
171 # and wait $timeout seconds for
172 # references == 0 (no shared locks set)
173 #
174 simple_lock ($filename,$timeout) or return 0;
175 for (1..$timeout) {
176 # lock reference counter
177 # or fail
178 #
179 unless (simple_lock (reffile($filename),$timeout)) {
180 simple_unlock($filename,$timeout);
181 return 0;
182 }
183
184 # ready if we have no shared locks
185 #
186 if (get_ref ($filename) == $rest) {
187 $LOCKED{$filename} = 2 | ($rest ? 1 : 0);
188 return 1;
189 };
190
191 # release reference counter
192 # shared locks get the chance to be removed
193 #
194 unless (simple_unlock (reffile($filename),$timeout)) {
195 simple_unlock($filename,$timeout);
196 return 0;
197 }
198 sleep(1);
199 }
200
201 # write lock failed
202 # remove the announcement
203 #
204 simple_unlock ($filename);
205 }
206
207 else {
208 # master lock is set or file has not been released yet
209 return;
210 }
211
212 # time out
213 0;
214 }
215
216 ### sub w_write_unlock_file ($;$) ##############################################
217 #
218 # remove write lock (exclusive lock)
219 # (for no-symlink-systems)
220 #
221 # Params: $filename - locked file
222 # $timeout - timeout (sec.)
223 #
224 # Return: Status Code (Bool)
225 #
226 sub w_write_unlock_file ($;$) {
227 my $filename = shift;
228 my $timeout = shift || $Timeout;
229
230 if (-f masterlockfile($filename) or $iAmMaster) {
231
232 # remove reference counter lock
233 #
234 simple_unlock (reffile($filename),$timeout) or return;
235
236 # remove the write lock announce
237 #
238 simple_unlock ($filename,$timeout) or return;
239 }
240
241 # done
242 delete $LOCKED{$filename};
243 1;
244 }
245
246 ### sub w_violent_unlock_file ($) ##############################################
247 #
248 # remove any lock violent (excl. master lock)
249 # (for no-symlink-systems)
250 #
251 # Params: $filename - locked file
252 #
253 # Return: -none- (the success is not defined)
254 #
255 sub w_violent_unlock_file ($) {
256 my $filename = shift;
257
258 if (-f masterlockfile($filename)) {
259
260 # find out last modification time
261 # and do nothing unless 'violent-timout' is over
262 #
263 my $reffile;
264 if (-f ($reffile = $filename) or -f ($reffile = lockfile($filename))) {
265 my $time = (stat $reffile)[9];
266 (time - $time) >= $violentTimeout or return;
267 }
268
269 write_lock_file ($filename,1); # last try, to set an exclusive lock on $filename
270 unlink (reffile($filename)); # reference counter = 0
271 simple_unlock (reffile($filename)); # release reference counter file
272 simple_unlock ($filename);} # release file
273 delete $LOCKED{$filename};
274
275 return;
276 }
277
278 ### sub w_set_master_lock ($;$) ################################################
279 #
280 # set master lock
281 # (for no-symlink-systems)
282 #
283 # Params: $filename - file to lock
284 # $timeout - timeout (sec.)
285 #
286 # Return: Status Code (Bool)
287 #
288 sub w_set_master_lock ($;$) {
289 my $filename = shift;
290 my $timeout = shift || $masterTimeout;
291
292 # set exclusive lock or fail
293 #
294 return unless (write_lock_file ($filename,$timeout));
295
296 # set master lock
297 #
298 unlink masterlockfile($filename) and return 1;
299
300 # no chance (occupied?, master lock set yet?)
301 return;
302 }
303
304 ### sub w_release_file ($) #####################################################
305 #
306 # remove any locks (incl. master lock)
307 # (for no-symlink-systems)
308 #
309 # Params: $filename - file to lock
310 # $timeout - timeout (sec.)
311 #
312 # Return: Status Code (Bool)
313 #
314 sub w_release_file ($) {
315 my $filename=shift;
316
317 unlink (reffile($filename)); # reference counter = 0
318 return if (-f reffile($filename)); # really?
319 return unless (simple_unlock (reffile($filename))); # release reference counter
320 return unless (simple_unlock ($filename)); # remove any write lock announce
321 return unless (simple_unlock (masterfile($filename))); # remove master lock
322 delete $LOCKED{$filename};
323
324 # done
325 1;
326 }
327
328 ################################################################################
329 #
330 # *n*x section (symlinks possible)
331 #
332
333 ### sub x_lock_file ($;$) ######################################################
334 #
335 # set read lock (shared lock)
336 # (symlinks possible)
337 #
338 # Params: $filename - file to lock
339 # $timeout - Lock Timeout (sec.)
340 #
341 # Return: Status Code (Bool)
342 #
343 sub x_lock_file ($;$) {
344 my $filename = shift;
345 my $timeout = shift || $Timeout;
346
347 unless ($LOCKED{$filename}) {
348 unless (-l masterlockfile($filename)) {
349 for (1..$timeout) {
350
351 # try to increment the reference counter
352 #
353 if (set_ref($filename,1,$timeout)) {
354 $LOCKED{$filename} = 1;
355 return 1;
356 }
357 sleep (1);
358 }
359 }
360
361 else {
362 # master lock is set or file has not been realeased yet
363 return;
364 }
365 }
366
367 # time out
368 0;
369 }
370
371 ### sub x_unlock_file ($;$) ####################################################
372 #
373 # remove read lock (shared lock)
374 # (symlinks possible)
375 #
376 # Params: $filename - locked file
377 # $timeout - timeout (sec.)
378 #
379 # Return: Status Code (Bool)
380 #
381 sub x_unlock_file ($;$) {
382 my $filename=shift;
383 my ($timeout)=(shift (@_) or $Timeout);
384
385 if ($LOCKED{$filename}) {
386 if ($LOCKED{$filename} == 3) {
387 return unless write_unlock_file($filename, $timeout);
388 $LOCKED{$filename} = 1;
389 }
390 if ($LOCKED{$filename} == 1) {
391 unless (-l masterlockfile($filename)) {
392 # try to decrement the reference counter
393 #
394 set_ref($filename,-1,$timeout) and do {
395 delete $LOCKED{$filename};
396 return 1;
397 }
398 }
399
400 # time out
401 return;
402 }
403 }
404 }
405
406 ### sub x_write_lock_file ($;$) ################################################
407 #
408 # set write lock (exclusive lock)
409 # (symlinks possible)
410 #
411 # Params: $filename - file to lock
412 # $timeout - timeout (sec.)
413 #
414 # Return: Status Code (Bool)
415 #
416 sub x_write_lock_file ($;$) {
417 my $filename = shift;
418 my $timeout = shift || $Timeout;
419 my $rest = ($LOCKED{$filename} and $LOCKED{$filename} == 1) ? 1 : 0;
420
421 unless (-l masterlockfile($filename) and not $iAmMaster) {
422 # announce the write lock
423 # and wait $timeout seconds for
424 # references == 0 (no shared locks set)
425 #
426 simple_lock ($filename,$timeout) or return 0;
427 for (1..$timeout) {
428
429 # lock reference counter
430 # or fail
431 #
432 unless (simple_lock (&reffile($filename),$timeout)) {
433 simple_unlock($filename,$timeout);
434 return 0;
435 }
436
437 # ready if we have no shared locks
438 #
439 if (get_ref ($filename) == $rest) {
440 $LOCKED{$filename} = 2 | ($rest ? 1 : 0);
441 return 1;
442 };
443
444 # release reference counter
445 # shared locks get the chance to be removed
446 #
447 unless (simple_unlock (&reffile($filename),$timeout)) {
448 simple_unlock($filename,$timeout);
449 return 0;
450 }
451 sleep(1);
452 }
453
454 # write lock failed
455 # remove the announcement
456 #
457 simple_unlock ($filename);
458 }
459
460 else {
461 # master lock is set
462 # or file has not been released yet
463 #
464 return;
465 }
466
467 # time out
468 # maybe the system is occupied
469 #
470 0;
471 }
472
473 ### sub x_write_unlock_file ($;$) ##############################################
474 #
475 # remove write lock (exclusive lock)
476 # (symlinks possible)
477 #
478 # Params: $filename - locked file
479 # $timeout - timeout (sec.)
480 #
481 # Return: Status Code (Bool)
482 #
483 sub x_write_unlock_file ($;$) {
484 my $filename = shift;
485 my $timeout = shift || $Timeout;
486
487 unless (-l &masterlockfile($filename) and not $iAmMaster) {
488 # remove reference counter lock
489 #
490 simple_unlock (reffile($filename),$timeout) or return;
491
492 # remove the write lock announce
493 #
494 simple_unlock ($filename,$timeout) or return;
495 }
496
497 # done
498 delete $LOCKED{$filename};
499 1;
500 }
501
502 ### sub x_violent_unlock_file ($) ##############################################
503 #
504 # remove any lock violent (excl. master lock)
505 # (symlinks possible)
506 #
507 # Params: $filename - locked file
508 #
509 # Return: -none- (the success is not defined)
510 #
511 sub x_violent_unlock_file ($) {
512 my $filename=shift;
513
514 unless (-l &masterlockfile($filename)) {
515
516 # find out last modification time
517 # and do nothing unless 'violent-timout' is over
518 #
519 my ($reffile,$time);
520
521 if (-f ($reffile = $filename)) {
522 $time = (stat $reffile)[9];}
523
524 elsif (-l ($reffile = lockfile($filename))) {
525 $time = (lstat $reffile)[9];}
526
527 if ($reffile) {
528 return if ((time - $time) < $violentTimeout);}
529
530 write_lock_file ($filename,1); # last try, to set an exclusive lock on $filename
531 unlink (reffile($filename)); # reference counter = 0
532 simple_unlock (reffile($filename)); # release reference counter file
533 simple_unlock ($filename);} # release file
534 delete $LOCKED{$filename};
535 }
536
537 ### sub x_set_master_lock ($;$) ################################################
538 #
539 # set master lock
540 # (symlinks possible)
541 #
542 # Params: $filename - file to lock
543 # $timeout - timeout (sec.)
544 #
545 # Return: Status Code (Bool)
546 #
547 sub x_set_master_lock ($;$) {
548 my $filename = shift;
549 my $timeout = shift || $masterTimeout;
550
551 # set exclusive lock or fail
552 #
553 return unless (write_lock_file ($filename,$timeout));
554
555 # set master lock
556 #
557 symlink $filename, masterlockfile($filename) and return 1;
558
559 # no chance (occupied?, master lock set yet?)
560 return;
561 }
562
563 ### sub x_release_file ($) #####################################################
564 #
565 # remove any locks (incl. master lock)
566 # (symlinks possible)
567 #
568 # Params: $filename - file to lock
569 # $timeout - timeout (sec.)
570 #
571 # Return: Status Code (Bool)
572 #
573 sub x_release_file ($) {
574 my $filename=shift;
575
576 unlink (reffile($filename)); # reference counter = 0
577 return if (-f reffile($filename)); # really?
578 return unless (simple_unlock (reffile($filename))); # release reference counter
579 return unless (simple_unlock ($filename)); # remove any write lock announce
580 return unless (simple_unlock (masterfile($filename))); # remove master lock
581 delete $LOCKED{$filename};
582
583 # done
584 1;
585 }
586
587 ### sub w_simple_lock ($;$) ####################################################
588 ### sub w_simple_unlock ($) ####################################################
589 #
590 # simple file lock/unlock
591 # (for no-symlink-systems: kill/create lockfile)
592 #
593 # Params: $filename - file to lock
594 # [ $timeout - Lock time out (sec.) ]
595 #
596 # Return: Status Code (Bool)
597 #
598 sub w_simple_lock ($;$) {
599 my $filename = shift;
600 my $timeout = shift || $Timeout;
601 my $lockfile = lockfile $filename;
602
603 for (1..$timeout) {
604 unlink $lockfile and return 1;
605 sleep(1);
606 }
607
608 # timeout
609 # occupied?
610 return;
611 }
612
613 sub w_simple_unlock ($) {
614 my $filename = shift;
615 my $lockfile = lockfile $filename;
616 local *LF;
617
618 if (sysopen(LF, $lockfile, O_WRONLY|O_CREAT|O_TRUNC)) {
619 return 1 if close (LF);
620 }
621
622 # not able to create lockfile, hmmm...
623 #
624 return;
625 }
626
627 ### sub x_simple_lock ($;$) ####################################################
628 ### sub x_simple_unlock ($) ####################################################
629 #
630 # simple file lock/unlock
631 # (symlinks possible: create/unlink symlink)
632 #
633 # Params: $filename - file to lock
634 # [ $timeout - Lock time out (sec.) ]
635 #
636 # Return: Status Code (Bool)
637 #
638 sub x_simple_lock ($;$) {
639 my $filename = shift;
640 my $timeout = shift || $Timeout;
641 my $lockfile = lockfile $filename;
642
643 for (1..$timeout) {
644 symlink $filename,$lockfile and return 1;
645 sleep(1);
646 }
647
648 # time out
649 return;
650 }
651
652 sub x_simple_unlock ($) {
653 my $filename=shift;
654
655 unlink (lockfile $filename) and return 1;
656
657 # not able to unlink symlink, hmmm...
658 #
659 return;
660 }
661
662 ### sub w_set_ref ($$$) ########################################################
663 #
664 # add $_[1] to reference counter
665 # (may be negative...)
666 # (for no-symlink-systems)
667 #
668 # Params: $filename - file, reference counter belongs to
669 # $z - value, added to reference counter
670 # $timeout - lock time out
671 #
672 # Return: Status Code (Bool)
673 #
674 sub w_set_ref ($$$) {
675 my $filename = shift;
676 my $z = shift;
677 my $timeout = shift || $Timeout;
678 my $reffile = reffile $filename;
679 local *REF;
680
681 # if write lock announced, only count down allowed
682 #
683 ($z < 0 or -f lockfile ($filename)) or return;
684
685 # lock reference counter file
686 #
687 simple_lock ($reffile,$timeout) or return;
688
689 # load reference counter
690 #
691 my $old = get_ref ($filename);
692
693 # compute and write new ref. counter
694 #
695 $old += $z;
696 $old = 0 if ($old < 0);
697
698 # kill reference counter file
699 # if ref. counter == 0
700 #
701 if ($old == 0) {
702 unlink $reffile or return;
703 }
704 else {
705 local $\="\n";
706 sysopen (REF, $reffile, O_WRONLY | O_TRUNC | O_CREAT) or return;
707 print REF $old or do {
708 close REF;
709 return
710 };
711 close REF or return;
712 }
713
714 # release ref. counter file
715 #
716 simple_unlock($reffile) or return;
717
718 # done
719 1;
720 }
721
722 ### sub x_set_ref ($$$) ########################################################
723 #
724 # add $_[1] to reference counter
725 # (may be negative...)
726 # (symlinks possible)
727 #
728 # Params: $filename - file, reference counter belongs to
729 # $z - value, added to reference counter
730 # $timeout - lock time out
731 #
732 # Return: Status Code (Bool)
733 #
734 sub x_set_ref ($$$) {
735 my $filename = shift;
736 my $z = shift;
737 my $timeout = shift || $Timeout;
738 my $reffile = reffile $filename;
739 local *REF;
740
741 # if write lock announced, only count down allowed
742 #
743 if ($z > 0) {
744 return if(-l lockfile($filename));
745 }
746
747 # lock reference counter file
748 #
749 return unless(simple_lock ($reffile,$timeout));
750
751 # load reference counter
752 #
753 my $old = get_ref ($filename);
754
755 # compute and write new ref. counter
756 #
757 $old += $z;
758 $old = 0 if ($old < 0);
759
760 if ($old == 0) {
761 unlink $reffile or return;
762 }
763 else {
764 local $\="\n";
765 sysopen (REF, $reffile, O_WRONLY | O_TRUNC | O_CREAT) or return;
766 print REF $old or do {
767 close REF;
768 return
769 };
770 close REF or return;
771 }
772
773 # release ref. counter file
774 #
775 simple_unlock($reffile) or return;
776
777 # done
778 1;
779 }
780
781 ### sub get_ref ($) ############################################################
782 #
783 # read out the reference counter
784 # (system independant)
785 # no locking here!
786 #
787 # Params: $filename - file, the ref. counter belongs to
788 #
789 # Return: reference counter
790 #
791 sub get_ref ($) {
792 my $filename = shift;
793 my $reffile = reffile $filename;
794 my $old;
795 local *REF;
796
797 if (sysopen (REF, $reffile, O_RDONLY)) {
798 local $/="\n";
799 read REF, $old, -s $reffile;
800 close REF;
801 chomp $old;
802 }
803
804 # return value
805 $old or 0;
806 }
807
808 ################################################################################
809 #
810 # initializing the module
811 #
812 BEGIN {
813 # global variables (time in seconds)
814 #
815 $Timeout = 10; # normal timeout
816 $violentTimeout = 600; # violent timeout (10 minutes)
817 $masterTimeout = 20; # master timeout
818
819 $iAmMaster = 0; # default: I am nobody
820
821 %LOCKED = ();
822
823 # assign the aliases to the needed functions
824 # (perldoc -f symlink)
825
826 if ( eval {local $SIG{__DIE__}; symlink('',''); 1 } ) {
827 *lock_file = \&x_lock_file;
828 *unlock_file = \&x_unlock_file;
829 *write_lock_file = \&x_write_lock_file;
830 *write_unlock_file = \&x_write_unlock_file;
831 *violent_unlock_file = \&x_violent_unlock_file;
832 *set_master_lock = \&x_set_master_lock;
833 *release_file = \&x_release_file;
834
835 *simple_lock = \&x_simple_lock;
836 *simple_unlock = \&x_simple_unlock;
837 *set_ref = \&x_set_ref;
838 }
839
840 else {
841 *lock_file = \&w_lock_file;
842 *unlock_file = \&w_unlock_file;
843 *write_lock_file = \&w_write_lock_file;
844 *write_unlock_file = \&w_write_unlock_file;
845 *violent_unlock_file = \&w_violent_unlock_file;
846 *set_master_lock = \&w_set_master_lock;
847 *release_file = \&w_release_file;
848
849 *simple_lock = \&w_simple_lock;
850 *simple_unlock = \&w_simple_unlock;
851 *set_ref = \&w_set_ref;
852 }
853 }
854
855 # keeping require happy
856 1;
857
858 #
859 #
860 ### end of Lock ################################################################

patrick-canterino.de