diff -urp --strip-trailing-cr ../cacherouter/engines/file.php ./engines/file.php
--- ../cacherouter/engines/file.php	2010-02-04 17:11:42.000000000 +0100
+++ ./engines/file.php	2010-05-23 20:09:58.000000000 +0200
@@ -31,20 +31,14 @@ class fileCacheRouterEngine extends Cach
   function get($key) {
     global $user, $conf;
 
-    if (isset($this->content[$key])) {
-      return $this->content[$key];
-    }
-
-    $cache_lifetime = variable_get('cache_lifetime', 0);
-
     //make sure fast cache is enabled (see CacheRouter function page_fast_cache)
     if ($this->page_fast_cache()) {
       $cache = NULL;
-      $cache_file = $this->key($key);
-      if (file_exists($cache_file)) {
-        if ($fp = fopen($cache_file, 'r')) {
+      $cache_file = glob($this->key($key) . '.*', GLOB_NOSORT);
+      if (isset($cache_file[0])) {    
+        if ($fp = @fopen($cache_file[0], 'r')) {
           if (flock($fp, LOCK_SH)) {
-            $data = @fread($fp, filesize($cache_file));
+            $data = @fread($fp, filesize($cache_file[0]));
             flock($fp, LOCK_UN);
             $cache = unserialize($data);
           }
@@ -52,29 +46,7 @@ class fileCacheRouterEngine extends Cach
         }
       }
       return $cache;
-
-    }
-    else {
-      // garbage collection necessary when enforcing a minimum cache lifetime
-      $cache_flush = variable_get('cache_flush_' . $this->name, 0);
-      if ($cache_flush && ($cache_flush + $cache_lifetime <= time())) {
-        variable_set('cache_flush_' . $this->name, 0);
-      }
-    }
-
-    if (isset($cache->data)) {
-      // If enforcing a minimum cache lifetime, validate that the data is
-      // currently valid for this user before we return it by making sure the
-      // cache entry was created before the timestamp in the current session's
-      // cache timer. The cache variable is loaded into the $user object by
-      // sess_read() in session.inc.
-      if ($user->cache > $cache->created) {
-        // This cache data is too old and thus not valid for us, ignore it.
-        return FALSE;
-      }
-      return $cache;
     }
-    return FALSE;
   }
 
   function set($key, $value, $expire = CACHE_PERMANENT, $headers = NULL) {
@@ -92,17 +64,46 @@ class fileCacheRouterEngine extends Cach
 
       $data = serialize($cache);
 
+      // Filename: cache_key-hash.expiration
+      if ($expire == CACHE_PERMANENT) {
+        $extension = '.perm';
+      }
+      else {
+        $extension = '.0' . ($expire == CACHE_TEMPORARY ? '' : $expire);
+      }
       $file = $this->key($key);
-      if ($fp = @fopen($file, 'w')) {
-        // only write to the cache file if we can obtain an exclusive lock
-        if (flock($fp, LOCK_EX)) {
-          fwrite($fp, $data);
-          flock($fp, LOCK_UN);
+
+      // Grab a lock. Since we're going to (potentially) rename files, we need
+      // an additional per-directory lock for writes, to avoid insertion of
+      // duplicate entries with different expiration timestamps in concurrent
+      // requests. This extra lock is kept on a dummy file, which is outside
+      // the actual directory to simplify mass deletion of data files.
+      $success = FALSE;
+      if ($lockfile = @fopen(dirname($file) . '.lock', 'w')) {
+        if (flock($lockfile, LOCK_EX)) {
+          // Remove any old entries
+          $this->_delete($file . '.*');
+
+          // Write fresh entry
+          $file .= $extension;
+          if ($fp = @fopen($file, 'w')) {
+            // only write to the cache file if we can obtain an exclusive lock.
+            // This lock defends against concurrent reading of this one file.
+            if (flock($fp, LOCK_EX)) {
+              fwrite($fp, $data);
+              flock($fp, LOCK_UN);
+            }
+            fclose($fp);
+            $success = TRUE;
+          }
+
+          flock($lockfile, LOCK_UN);
+          @chmod($file, 0664); // Necessary for non-webserver users.
         }
-        fclose($fp);
-        @chmod($file, 0664); // Necessary for non-webserver users.
+        fclose($lockfile);
       }
-      else {
+
+      if (!$success) {
         // t() may not be loaded
         if (function_exists('watchdog')) {
           watchdog('cache', strtr('Cache write error, failed to open file "%file"', array('%file' => $file)), WATCHDOG_ERROR);
@@ -121,97 +122,91 @@ class fileCacheRouterEngine extends Cach
 
     $filename = $this->key($key);
     if (is_dir($filename)) {
-      $fspath = $filename;
-      // Filename: abcdef12345verylongmd5code--content:123456:987654
-      $files = file_scan_directory($fspath, ".--.", array('.', '..', 'CVS'));
-      foreach ($files as $file) {
-        if (is_file($file->filename)) {
-          if ($fp = fopen($file->filename, 'w')) {
-            // only delete the cache file once we obtain an exclusive lock to prevent
-            // deleting a cache file that is currently being read.
-            if (flock($fp, LOCK_EX)) {
-              @unlink($file->filename);
-            }
-          }
-        }
-      }
-    }
+      // '*' => Delete all
+      $this->_delete($filename . '*/*.*');
+    } 
     elseif (strrpos($key, '*') !== FALSE) {
+      // 'part-of-key*' => Delete all matching entries
       $look_for = explode('*', $key);
-      $fspath = $this->fspath;
-      // Filename: abcdef12345verylongmd5code--content:123456:987654
-      $files = file_scan_directory($fspath, ".--{$look_for[0]}.*", array('.', '..', 'CVS'));
+      $this->_delete($this->key('*') . '*/' . $this->escape_key($look_for[0], '') . '*.*');
+    } 
+    else {
+      // 'full-key' => Delete single entry
+      $this->_delete($filename . '.*');
+    }
+  }
+
+  // Helper function to delete a set of files
+  function _delete($file_pattern) {
+    if ($files = glob($file_pattern, GLOB_NOSORT)) {
       foreach ($files as $file) {
-        if (is_file($file->filename)){
-          if ($fp = @fopen($file->filename, 'w')) {
-            // only delete the cache file once we obtain an exclusive lock to prevent
-            // deleting a cache file that is currently being read.
-            if (flock($fp, LOCK_EX)) {
-              @unlink($file->filename);
-            }
+        if ($fp = @fopen($file, 'w')) {
+          // Obtain an exclusive lock to ensure a successful removal of the
+          // file; unlink() fails on Windows in certain concurrency situations.
+          // Because another concurrent unlink() may be executed as we wait for
+          // the lock, we need to suppress errors, too.
+          if (flock($fp, LOCK_EX)) {
+            @unlink($file);
           }
         }
       }
     }
-    else {
-      if ($fp = @fopen($filename, 'w')) {
-        // only delete the cache file once we obtain an exclusive lock to prevent
-        // deleting a cache file that is currently being read.
-        if (flock($fp, LOCK_EX)) {
-          @unlink($filename);
-        }
-      }
-    }
   }
 
-
   function flush() {
-    global $user;
-
-    $table = $this->name;
-    $cache_lifetime = variable_get('cache_lifetime', 0);
-    $fspath = $this->fspath;
-
-    $ivalfrom = ord("a");
-    $ivalto = ord("f");
-
-    for ($i = $ivalfrom; $i <= $ivalto; $i++) {
-      $this->purge("$fspath/$table/". chr($i));
-    }
-
-    for ($i = 0; $i<10; $i++) {
-      $this->purge("$fspath/$table/". $i);
-    }
+    $this->purge($this->key('*'));
   }
 
   function key($key) {
     $table = $this->name;
     $fspath = $this->fspath;
     if ($key != '*') {
-      // Make sure we have a good filename when we concatentate $hash with $appendix:
-      // * Can't be over 255 bytes (ext2, 3, 4) or 255 characters (NTFS, FAT) as per
-      //  http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
-      // * Can't include "? * / \ : ; < >" in NTFS and FAT as per
-      //  http://technet.microsoft.com/en-us/library/cc722482.aspx
-      $hash = md5($key);
-      $appendix = str_replace(array('?', '*', '/', '\\', ':', ';', '<', '>'), '-', $key);
-      $filename = $hash . '--' . $appendix;
 
-      if (function_exists('mb_substr')) {
-        $filename = mb_substr($filename, 0, 255, '8bit');
-      }
-      else {
-        // We'll have to assume we're working with ASCII if mb_ extension isn't installed.
-        $filename = substr($filename, 0, 255);
-      }
+      $hash = md5($key);
+      $filename = $this->escape_key($key, '-' . $hash);
 
       $this->create_directory($fspath, $hash{0});
       return "$fspath/$table/" . $hash{0} . '/' . $filename;
     }
     else {
-      return  "$fspath/$table/";
+      return "$fspath/$table/";
     }
+  }
 
+  /**
+   * Escape cache key and limit its size, to obtain a consistent filename
+   * for all operations.
+   */
+  function escape_key($key, $appendix) {
+    // Make sure we have a good filename, by custom-escaping the cache key.
+    // Add md5 hash to ensure uniqueness on case-insensitive filesystems, and
+    // trim to maximum length, keeping space for 9 characters of an extension.
+    // * Can't be over 255 bytes (ext2, 3, 4) or 255 characters (NTFS, FAT) as per 
+    //  http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
+    // * Can't include "? * / \ : ; < >" in NTFS and FAT as per 
+    //  http://technet.microsoft.com/en-us/library/cc722482.aspx
+    // * Can't inlude dots, due to our handling of filename extensions.
+    $filename = strtr($key, array('!' => '!!', '.' => '!,',
+      '\x00' => '!0', '\x01' => '!1', '\x02' => '!2', '\x03' => '!3', '\x04' => '!4',
+      '\x05' => '!5', '\x06' => '!6', '\x07' => '!7', '\x08' => '!8', '\x09' => '!9',
+      '\x0A' => '!A', '\x0B' => '!B', '\x0C' => '!C', '\x0D' => '!D', '\x0E' => '!E',
+      '\x0F' => '!F', '\x10' => '!G', '\x11' => '!H', '\x12' => '!I', '\x13' => '!J',
+      '\x14' => '!K', '\x15' => '!L', '\x16' => '!M', '\x17' => '!N', '\x18' => '!O',
+      '\x19' => '!P', '\x1A' => '!Q', '\x1B' => '!R', '\x1C' => '!S', '\x1D' => '!T',
+      '\x1E' => '!U', '\x1F' => '!V',
+      '?' => '!W', '*' => '!X', '<' => '!Y', '>' => '!Z', ' ' => '!_', '/' => '!=',
+      '\\' => '!-', ':' => '!+', ';' => '!#'));
+    $filename .= $appendix;
+
+    if (function_exists('mb_substr')) {
+      $filename = mb_substr($filename, 0, 246, '8bit');
+    }
+    else {
+      // We'll have to assume we're working with ASCII if mb_ extension isn't installed.
+      $filename = substr($filename, 0, 246);
+    }
+
+    return $filename;
   }
 
   /**
@@ -246,31 +241,17 @@ class fileCacheRouterEngine extends Cach
   }
 
   function purge($dir) {
-    $cache_lifetime = variable_get('cache_lifetime', 0);
-    $files = file_scan_directory($dir, '.', array('.', '..', 'CVS'));
-    foreach ($files as $file) {
-      if ((filemtime($file->filename) < (time() - $cache_lifetime)) && $cache_lifetime > 0) {
-        if ($fp = fopen($file->filename, 'r')) {
-          // We need an exclusive lock, but don't block if we can't get it as
-          // we can simply try again next time cron is run.
-          if (flock($fp, LOCK_EX|LOCK_NB)) {
-            @unlink($file->filename);
-          }
-        }
-      }
-      else {
-        if (file_exists($file->filename)) {
-          if ($fp = @fopen($file->filename, 'r')) {
-            if (flock($fp, LOCK_SH)) {
-              $data = @fread($fp, filesize($file->filename));
-              flock($fp, LOCK_UN);
-              $cache = unserialize($data);
-            }
-            fclose($fp);
-            if ($cache->expire != CACHE_PERMANENT && $cache->expire <= time()) {
-              @unlink($file->filename);
-            }
-          }
+    // Get list of all files for this table, except permanent entries.
+    if ($files = glob($dir . '*/*.0*', GLOB_NOSORT)) {
+      foreach ($files as $file) {
+        // We have expiration timestamp in filename extension (0 for CACHE_TEMPORARY)
+        $expire = substr($file, strrpos($file, '.') + 1);
+        if ($expire <= time()) {
+          // No need to lock the file, because unlink() doesn't harm concurrent
+          // processes accessing the same file. If unlink() fails (on Windows,
+          // or when another concurrent unlink() happens), we just suppress the
+          // error, and try again on next cron run.
+          @unlink($file);
         }
       }
     }
