chiark / gitweb /
git-cache-proxy: introduce lockfile
[chiark-utils.git] / scripts / git-cache-proxy
index 12a4f52cdff44f3f412b6ce0cc4ca32574609393..b4b23001cb3ed42686ccaf0c1392c7f4f0bc40c5 100755 (executable)
@@ -155,13 +155,32 @@ for (;;) {
 
 !@ARGV or fail "bad usage: no non-option arguments permitted";
 
-#---------- main program ----------
-
-chdir $cachedir or fail "chdir $cachedir: $!";
+#---------- utility functions ----------
 
-our ($service,$specpath,$spechost,$subdir);
-our ($tmpd,$gitd,$lock);
-our ($fetch,$url);
+sub lockfile ($$) {
+    my ($fh, $fn, $flockmode) = @_;
+    for (;;) {
+       close $fh;
+       open $fh, '+>', $fn or fail "open/create $fn for lock: $!";
+       if (!flock $fh, $lockmode) {
+           if ($lockmode & LOCK_NB and $! == EWOULDBLOCK) {
+               return 0; # ok then
+           }
+           fail "lock $fn".
+               (($flockmode & ~LOCK_NB) == LOCK_SH ? " (shared)" : "").
+               ": $!";
+       }
+       stat $fh or fail "stat opened $fn: $!";
+       my $fh_ino = ((stat _)[1]);
+       if (!stat $fn) {
+           $! == ENOENT or fail "stat $fn: $!";
+           next;
+       }
+       my $fn_ino = ((stat _)[1]);
+       return 1 if $fn_ino == $fh_ino;
+       # oh dear
+    }
+}
 
 sub xread {
     my $length = shift;
@@ -175,6 +194,14 @@ sub xread {
     return $buffer;
 }
 
+#---------- main program ----------
+
+chdir $cachedir or fail "chdir $cachedir: $!";
+
+our ($service,$specpath,$spechost,$subdir);
+our ($tmpd,$gitd,$lock);
+our ($fetch,$url);
+
 sub servinfo ($) {
     my ($msg) = @_;
     logm 'info', "service `$specpath': $msg";
@@ -234,11 +261,10 @@ sub readcommand () {
 }
 
 sub clonefetch () {
-    open LOCK, "+>", $lock or fail "open/create $lock: $!";
-    flock LOCK, LOCK_EX or fail "lock exclusive $lock: $!";
+    lockfile \*LOCK, $lock, LOCK_EX;
 
-    my $exists = stat $gitd;
-    $exists or $!==ENOENT or fail "stat $gitd: $!";
+    my $exists = lstat $gitd;
+    $exists or $!==ENOENT or fail "lstat $gitd: $!";
 
     our $fetchfail = '';
 
@@ -313,10 +339,7 @@ sub clonefetch () {
     }
 
     servinfo "sharing";
-    flock LOCK, LOCK_UN or fail "unlock $lock: $!";
-    flock LOCK, LOCK_SH or fail "lock shared $lock: $!";
-    # actually, just relocking as shared would have the same semantics
-    # but it's best to be explicit
+    lockfile \*LOCK, $lock, LOCK_SH; # NB releases and relocks
 
     if (chdir $gitd) {
        return 1;
@@ -331,7 +354,7 @@ sub clonefetch () {
 sub housekeeping () {
     foreach $lock (<[a-z]*\\.lock>) {
        if (!lstat $lock) {
-           $! == ENOENT or fail "housekeeping: $lock: stat: $!";
+           $! == ENOENT or fail "housekeeping: $lock: lstat: $!";
            next;
        }
        if (-M _ <= $treeexpiredays) {
@@ -354,10 +377,9 @@ sub housekeeping () {
 
 sub housekeepingcheck ($$) {
     my ($dofork, $force) = @_;
-    open HLOCK, "+>", "Housekeeping.lock" 
        or fail "open/create Housekeeping.lock: $!";
     if (!$force) {
-       if (flock HLOCK, LOCK_EX|LOCK_NB) {
+       if (!lockfile \*HLOCK, "Housekeeping.lock", LOCK_EX|LOCK_NB) {
            logm 'debug', "housekeeping lock taken, not running";
            close HLOCK;
            return 0;
@@ -366,7 +388,7 @@ sub housekeepingcheck ($$) {
     if ($force) {
        logm 'info', "housekeeping forced";
     } elsif (!lstat "Housekeeping.stamp") {
-       $! == ENOENT or fail "stat housekeeping.stamp: $!";
+       $! == ENOENT or fail "lstat Housekeeping.stamp: $!";
        logm 'info', "housekeeping stamp missing, will run";
     } elsif (-M _ <= $housekeepingthreshdays) {
        logm 'debug', "housekeeping done recently";