Locking branch: Implement locking in FSFS with a flat structure, using
filenames that are MD5 checksums of the absolute path.  To support prefix
lookups (i.e. "is a file under this directory locked?"), create files
representing directories.  Directory files are managed using reference counters
for their immediate children.

* subversion/libsvn_fs_fs/lock.c (REFCOUNT_KEY): New define.
  (abs_path_to_lock_file): Construct path using MD5 of absolute path.
  (write_hash): New function, factored out from write_lock_to_file.  Write
  hash atomically by moving it into place.
  (get_refcount, use_directory, unuse_directory): New functions.
  (write_lock_to_file): Call write_hash.
  (save_lock): Create locks for "directory files".
  (delete_lock): Remove locks on "directory files" instead of pruning real
  directories.
  (read_lock_from_file): Don't stat the file before opening it.


Index: subversion/libsvn_fs_fs/lock.c
===================================================================
--- subversion/libsvn_fs_fs/lock.c	(revision 12304)
+++ subversion/libsvn_fs_fs/lock.c	(arbetskopia)
@@ -25,6 +25,7 @@
 #include "svn_utf.h"
 
 #include "apr_uuid.h"
+#include "svn_md5.h"
 #include "apr_file_io.h"
 #include "apr_file_info.h"
 
@@ -47,6 +48,8 @@
 #define CREATION_DATE_KEY "creation_date"
 #define EXPIRATION_DATE_KEY "expiration_date"
 #define COMMENT_KEY "comment"
+/* Number of direct children of this directory. */
+#define REFCOUNT_KEY "ref_count"
 
 
 
@@ -74,9 +77,15 @@
                        const char *rel_path,
                        apr_pool_t *pool)
 {
+  unsigned char digest[APR_MD5_DIGESTSIZE];
+
+  /* ## Error check. */
+  apr_md5(digest, rel_path, strlen (rel_path));
+
   SVN_ERR (merge_paths (abs_path, fs->path, LOCK_ROOT_DIR, pool));
   SVN_ERR (merge_paths (abs_path, *abs_path, LOCK_LOCK_DIR, pool));
-  SVN_ERR (merge_paths (abs_path, *abs_path, (char *)rel_path, pool));
+  SVN_ERR (merge_paths (abs_path, *abs_path,
+                        svn_md5_digest_to_cstring(digest, pool), pool));
 
   return SVN_NO_ERROR;
 }
@@ -134,7 +143,156 @@
   return NULL;
 }
 
+static svn_error_t *
+write_hash (apr_hash_t *hash, const char *path, apr_pool_t *pool)
+{
+  apr_file_t *file;
+  const char *name;
+  svn_stream_t *stream;
 
+  SVN_ERR (svn_io_open_unique_file (&file, &name, path, ".tmp", FALSE, pool));
+
+  stream = svn_stream_from_aprfile (file, pool);
+
+  SVN_ERR_W (svn_hash_write2 (hash, stream, SVN_HASH_TERMINATOR, pool),
+             apr_psprintf (pool,
+                           _("Cannot write lock hash to '%s'"),
+                           svn_path_local_style (path, pool)));
+  SVN_ERR (svn_io_file_close (file, pool));
+  SVN_ERR (svn_io_file_rename (name, path, pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_refcount (unsigned long *refc, apr_hash_t *hash, const char *path,
+              apr_pool_t *pool)
+{
+  const char *refc_str;
+  char *endptr;
+
+  refc_str = hash_fetch (hash, REFCOUNT_KEY, pool);
+  if (! refc_str)
+    return svn_error_createf (SVN_ERR_FS_CORRUPT, NULL,
+                              _("Missing refcount in lock file for '%s'"),
+                                path);
+  *refc = strtoul (refc_str, &endptr, 10);
+  if (*refc == ULONG_MAX || *endptr != '\0' || *refc == 0)
+    return svn_error_createf (SVN_ERR_FS_CORRUPT, NULL,
+                              _("Invalid refcount in lock file for '%s'"),
+                              path);
+
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unuse_directory (const char *path, svn_fs_t *fs, apr_pool_t *pool);
+
+static svn_error_t *
+use_directory (const char *path, svn_fs_t *fs, apr_pool_t *pool)
+{
+  svn_error_t *err;
+  apr_status_t apr_err;
+  apr_hash_t *hash;
+  apr_file_t *lock_file;
+  char *lock_path;
+  svn_stream_t *stream;
+  unsigned long refc;
+  svn_boolean_t first_use;  /* Did we create our directory file? */
+
+  /* Check for the root directory. */
+  if (path[0] == '\0' || (path[0] == '/' && path[1] == '\0'))
+    return SVN_NO_ERROR;
+
+  SVN_ERR (abs_path_to_lock_file (&lock_path, fs, path, pool));
+
+  hash = apr_hash_make (pool);
+
+  /* Non-existent files are rather common in this situation, so we avoid
+     allocating lots of errors by using the APR function.  Also, the
+     filename is plain ASCII, so there are no UTF8 issues here. */
+  apr_err = apr_file_open (&lock_file, lock_path, APR_READ, APR_OS_DEFAULT,
+                           pool);
+  if (apr_err)
+    {
+      if (APR_STATUS_IS_ENOENT (apr_err))
+        {
+          /* Directory isn't in use yet, create a new. */
+          /* First increment refcount on our parent. */
+          SVN_ERR (use_directory (svn_path_dirname (path, pool), fs, pool));
+          hash_store (hash, PATH_KEY, path, pool);
+          hash_store (hash, REFCOUNT_KEY, "1", pool);
+          first_use = TRUE;
+        }
+      else
+        return svn_error_wrap_apr (apr_err,
+                                   _("Can't open lock file for '%s'"),
+                                   path);
+    }
+  else
+    {
+      /* Directory file exists, read it. */
+      stream = svn_stream_from_aprfile (lock_file, pool);
+      SVN_ERR (svn_hash_read2 (hash, stream, SVN_HASH_TERMINATOR, pool));
+      SVN_ERR (svn_io_file_close (lock_file, pool));
+
+      /* Increment the refcount. */
+      SVN_ERR (get_refcount (&refc, hash, path, pool));
+      hash_store (hash, REFCOUNT_KEY, apr_psprintf (pool, "%ld", refc + 1),
+                  pool);
+      first_use = FALSE;
+    }
+
+  /* Write the hash back. */
+  err = write_hash (hash, lock_path, pool);
+  /* On error, we can at least *try* to clean up. */
+  if (err && first_use)
+    svn_error_clear (unuse_directory (svn_path_dirname (path, pool), fs, pool));
+
+  return err;
+}
+
+svn_error_t *
+unuse_directory (const char *path, svn_fs_t *fs, apr_pool_t *pool)
+{
+  char *lock_path;
+  apr_file_t *lock_file;
+  svn_stream_t *stream;
+  apr_hash_t *hash;
+  unsigned long refc;
+
+  /* Check for the root directory. */
+  if (path[0] == '\0' || (path[0] == '/' && path[1] == '\0'))
+    return SVN_NO_ERROR;
+
+  hash = apr_hash_make (pool);
+
+  SVN_ERR (abs_path_to_lock_file (&lock_path, fs, path, pool));
+
+  SVN_ERR (svn_io_file_open (&lock_file, lock_path, APR_READ, APR_OS_DEFAULT,
+                             pool));
+
+  stream = svn_stream_from_aprfile (lock_file, pool);
+  SVN_ERR (svn_hash_read2 (hash, stream, SVN_HASH_TERMINATOR, pool));
+  SVN_ERR (get_refcount (&refc, hash, path, pool));
+
+  /* If refc is 1, the last user is gone. */
+  if (refc == 1)
+    {
+      SVN_ERR (svn_io_remove_file (lock_path, pool));
+      SVN_ERR (unuse_directory (svn_path_dirname (path, pool), fs, pool));
+    }
+  else
+    {
+      /* Decrement the reference count. */
+      hash_store (hash, REFCOUNT_KEY, apr_psprintf (pool, "%ld", refc - 1),
+                  pool);
+      SVN_ERR (write_hash (hash, lock_path, pool));
+    }
+  return SVN_NO_ERROR;
+}  
+
 /* Store the lock in the OS level filesystem in a tree under
    repos/db/locks/locks that reflects the location of lock->path in
    the repository. */
@@ -144,14 +302,10 @@
                     apr_pool_t *pool)
 {
   apr_hash_t *hash;
-  apr_file_t *fd;
-  svn_stream_t *stream;
   apr_status_t status = APR_SUCCESS;
   char *abs_path;
 
   char *dir;
-  /* ###file and pathnames will be limited by the native filesystem's
-     encoding--could that pose a problem? */
   SVN_ERR (abs_path_to_lock_file (&abs_path, fs, lock->path, pool));
 
   /* Make sure that the directory exists before we create the lock file. */
@@ -175,20 +329,8 @@
   hash_store (hash, EXPIRATION_DATE_KEY, 
               svn_time_to_cstring(lock->expiration_date, pool), pool);
 
+  SVN_ERR (write_hash (hash, abs_path, pool));
 
-  status = apr_file_open (&fd, abs_path, APR_WRITE | APR_CREATE, 
-                          APR_OS_DEFAULT, pool);
-  if (status && !APR_STATUS_IS_ENOENT (status))
-    return svn_error_wrap_apr (status, _("Can't open '%s' to write lock"),
-                               abs_path);
-
-  stream = svn_stream_from_aprfile (fd, pool);
-
-  SVN_ERR_W (svn_hash_write2 (hash, stream, SVN_HASH_TERMINATOR, pool),
-             apr_psprintf (pool,
-                           _("Cannot write lock hash to '%s'"),
-                           svn_path_local_style (abs_path, pool)));
-
   return SVN_NO_ERROR;
 }
 
@@ -228,18 +370,35 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+delete_lock (svn_fs_t *fs, 
+             svn_lock_t *lock,
+             apr_pool_t *pool);
 
 static svn_error_t *
 save_lock (svn_fs_t *fs,
            svn_lock_t *lock, 
            apr_pool_t *pool)
 {
+  svn_error_t *err;
 
-  SVN_ERR (write_lock_to_file (fs, lock, pool));
+  SVN_ERR (use_directory (svn_path_dirname (lock->path, pool), fs, pool));
 
-  SVN_ERR (write_lock_token_to_file (fs, lock, pool));
+  err = write_lock_to_file (fs, lock, pool);
 
-  return SVN_NO_ERROR;
+  if (err)
+    {
+      svn_error_clear (unuse_directory (svn_path_dirname (lock->path, pool),
+                                        fs, pool));
+      return err;
+    }
+
+  err = write_lock_token_to_file (fs, lock, pool);
+
+  if (err)
+    svn_error_clear (delete_lock (fs, lock, pool));
+
+  return err;
 }
 
 static svn_error_t *
@@ -247,21 +406,14 @@
              svn_lock_t *lock,
              apr_pool_t *pool)
 {
-  apr_status_t status = APR_SUCCESS;
   char *abs_path;
 
   /* Delete lock from locks area */
   SVN_ERR (abs_path_to_lock_file (&abs_path, fs, lock->path, pool));
   SVN_ERR (svn_io_remove_file (abs_path, pool));
 
-  /* Prune directories on the way back */
-  while (status == APR_SUCCESS)
-    {
-      abs_path = svn_path_dirname (abs_path, pool);
-      /* If this fails, status will != APR_SUCCESS, so we drop out of
-         the loop. */
-      status = apr_dir_remove (abs_path, pool);
-    }
+  /* Unref directory lock files. */
+  SVN_ERR (unuse_directory (svn_path_dirname (lock->path, pool), fs, pool));
 
   /* Delete lock from tokens area */
   SVN_ERR (abs_path_to_lock_token_file (&abs_path, fs, lock->token, pool));
@@ -342,23 +494,16 @@
   apr_status_t status;
   char *abs_path;
   const char *val;
-  apr_finfo_t finfo;
 
   SVN_ERR (abs_path_to_lock_file(&abs_path, fs, path, pool));
 
-  status = apr_stat (&finfo, abs_path, APR_FINFO_TYPE, pool);
-  /* If file doesn't exist, then there's no lock, so return immediately. */
-  if (APR_STATUS_IS_ENOENT (status))
+  status = apr_file_open (&fd, abs_path, APR_READ, APR_OS_DEFAULT, pool);
+  /* If the file doesn't exist, there is no lock. */
+  if (status && APR_STATUS_IS_ENOENT (status))
     {
       *lock_p = NULL;
       return svn_fs_fs__err_no_such_lock (fs, path);
-    }      
-
-  /* ###Is this necessary? */ 
-  if (status  && !APR_STATUS_IS_ENOENT (status))
-    return svn_error_wrap_apr (status, _("Can't stat '%s'"), abs_path);
-
-  status = apr_file_open (&fd, abs_path, APR_READ, APR_OS_DEFAULT, pool);
+    }
   if (status)
     return svn_error_wrap_apr (status, _("Can't open '%s' to read lock"),
                                abs_path);
