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

patrick-canterino.de