I'd like to draw attention to one aspect of the FS API that received a change
in Subversion 1.9.

We have 2 functions, svn_fs_change_txn_prop() and svn_fs_change_txn_props(),
that allow changing properties of the existing transactions in a filesystem.
I think we might want to revisit their behavior in terms of the transaction
properties that we allow to change and in terms of what we do when a caller
attempts to change an internal property like 'svn:check-locks' or
'svn:client-date':

1) Currently we allow changing 'svn:check-ood' and 'svn:check-locks'.  We also
   silently discard a property change whenever it targets 'svn:client-date'.
   I find the first part questionable, because we basically allow a caller to
   mess with our private implementation details.  Existence of the internal
   properties is a consequence of the fact that we use them to persist the
   transaction flags on the disk, but if, for example, we were using another
   way of storing these flags, I doubt that we'd expose an ability to change
   them through our API.

   Apart from what is stated above, I think that silently discarding changes
   to 'svn:client-date' is somewhat broken.  As a caller, I would expect the
   successive call to a function like svn_fs_change_txn_prop() to mean that the
   requested change was applied without any problems.  This is not true with
   'svn:client-date', because doing so doesn't result in an error, but also
   *doesn't change the property*.  In other words, the change goes straight to
   /dev/null, but the API says that the operation completed successfully.

   I propose that we solve this part of the problem with the attached patch.
   Please note that while preparing this patch, I also unveiled that the
   SVN_FS_TXN_CLIENT_DATE flag might not work as expected with BDB.
   I think we should fix this with a separate patch; see the comment in the
   new test_internal_txn_props() test.

   Log message:
[[[
Error out on attempts to change internal transaction properties like
'svn:check-locks' through the public FS API.

When creating a transaction, a caller can specify one or multiple flags like
SVN_FS_TXN_CHECK_LOCKS.  These flags affect how the transaction behaves
when being prepared or committed.  For example, a caller can tell us that
she/he doesn't want the back end to perform on-the-fly lock checking or might
want to specify the final 'svn:date' value for the transaction.  We want to
persist these flags even if a transaction is reopened — that's why we map
them into a set of internal transaction properties, set these properties when
a transaction is created, and drop them just before the commit.

Prior to this changeset we allowed changing these internal properties with
our API and were somewhat picky about 'svn:client-date', i.e., any attempts
to change it were silently discarded.  Instead of that, now we explicitly
prohibit changes for *all* known internal transaction properties.

We don't need this ability ourselves, and with this change we no longer allow
the callers to mess with our private implementation details.  Furthermore,
this makes the corresponding API calls do what they were told to do or fail.
This is a better choice compared to silently stripping out 'svn:client-date'
changes and saying that everything went fine.  As a caller, I would expect a
successive call to mean that the requested changes were applied without any
problems, and that's the behavior we get with this patch.

* subversion/libsvn_fs/fs-loader.c
  (is_internal_txn_prop): New helper function.
  (svn_fs_change_txn_prop): Error out on attempts to change internal
   transaction properties.
  (svn_fs_change_txn_props): Error out on attempts to change internal
   transaction properties.  Instead of silently skipping 'svn:client-date'
   changes, we now use "all-or-nothing" approach and can drop the property
   filtering code.

* subversion/tests/libsvn_fs/fs-test.c
  (commit_timestamp): Don't test what happens if we try to alter the
   'svn:client-date' property here.  This (and other possible scenarios)
   should now be covered by ...
  (test_internal_txn_props): ...this new test.
  (test_funcs): Reference new test.
]]]

2) These internal properties currently are a part of what svn_fs_txn_proplist()
   and svn_fs_txn_prop() return.  From my point of view, they are nothing more
   than an implementation detail and I don't think that something like that
   should ever reach the caller of a public API.  However, this is how things
   have been working for a long time, and I am not sure about changing it now,
   although it does make sense to me.

Thoughts?


Regards,
Evgeny Kotkov
Index: subversion/libsvn_fs/fs-loader.c
===================================================================
--- subversion/libsvn_fs/fs-loader.c    (revision 1663355)
+++ subversion/libsvn_fs/fs-loader.c    (working copy)
@@ -929,13 +929,22 @@ svn_fs_txn_proplist(apr_hash_t **table_p, svn_fs_t
   return svn_error_trace(txn->vtable->get_proplist(table_p, txn, pool));
 }
 
+static svn_boolean_t
+is_internal_txn_prop(const char *name)
+{
+  return strcmp(name, SVN_FS__PROP_TXN_CHECK_LOCKS) == 0 ||
+         strcmp(name, SVN_FS__PROP_TXN_CHECK_OOD) == 0 ||
+         strcmp(name, SVN_FS__PROP_TXN_CLIENT_DATE) == 0;
+}
+
 svn_error_t *
 svn_fs_change_txn_prop(svn_fs_txn_t *txn, const char *name,
                        const svn_string_t *value, apr_pool_t *pool)
 {
-  /* Silently drop attempts to modify the internal property. */
-  if (!strcmp(name, SVN_FS__PROP_TXN_CLIENT_DATE))
-    return SVN_NO_ERROR;
+  if (is_internal_txn_prop(name))
+    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                             _("Attempt to modify internal transaction "
+                               "property '%s'"), name);
 
   return svn_error_trace(txn->vtable->change_prop(txn, name, value, pool));
 }
@@ -946,25 +955,14 @@ svn_fs_change_txn_props(svn_fs_txn_t *txn, const a
 {
   int i;
 
-  /* Silently drop attempts to modify the internal property. */
   for (i = 0; i < props->nelts; ++i)
     {
       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
 
-      if (!strcmp(prop->name, SVN_FS__PROP_TXN_CLIENT_DATE))
-        {
-          apr_array_header_t *reduced_props
-            = apr_array_make(pool, props->nelts - 1, sizeof(svn_prop_t));
-
-          for (i = 0; i < props->nelts; ++i)
-            {
-              prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
-              if (strcmp(prop->name, SVN_FS__PROP_TXN_CLIENT_DATE))
-                APR_ARRAY_PUSH(reduced_props, svn_prop_t) = *prop;
-            }
-          props = reduced_props;
-          break;
-        }
+      if (is_internal_txn_prop(prop->name))
+        return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                                 _("Attempt to modify internal transaction "
+                                   "property '%s'"), prop->name);
     }
 
   return svn_error_trace(txn->vtable->change_props(txn, props, pool));
Index: subversion/tests/libsvn_fs/fs-test.c
===================================================================
--- subversion/tests/libsvn_fs/fs-test.c        (revision 1663355)
+++ subversion/tests/libsvn_fs/fs-test.c        (working copy)
@@ -5196,29 +5196,6 @@ commit_timestamp(const svn_test_opts_t *opts,
 
   /* Commit that overwrites the specified svn:date. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool));
-  {
-    /* Setting the internal property doesn't enable svn:date behaviour. */
-    apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
-    svn_prop_t prop, other_prop1, other_prop2;
-    svn_string_t *val;
-
-    prop.name = SVN_FS__PROP_TXN_CLIENT_DATE;
-    prop.value = svn_string_create("1", pool);
-    other_prop1.name = "foo";
-    other_prop1.value = svn_string_create("fooval", pool);
-    other_prop2.name = "bar";
-    other_prop2.value = svn_string_create("barval", pool);
-    APR_ARRAY_PUSH(props, svn_prop_t) = other_prop1;
-    APR_ARRAY_PUSH(props, svn_prop_t) = prop;
-    APR_ARRAY_PUSH(props, svn_prop_t) = other_prop2;
-    SVN_ERR(svn_fs_change_txn_props(txn, props, pool));
-    SVN_ERR(svn_fs_txn_prop(&val, txn, other_prop1.name, pool));
-    SVN_TEST_ASSERT(val && !strcmp(val->data, other_prop1.value->data));
-    SVN_ERR(svn_fs_txn_prop(&val, txn, other_prop2.name, pool));
-    SVN_TEST_ASSERT(val && !strcmp(val->data, other_prop2.value->data));
-
-    SVN_ERR(svn_fs_change_txn_prop(txn, prop.name, prop.value, pool));
-  }
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
   SVN_ERR(svn_fs_make_dir(txn_root, "/bar", pool));
   SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_DATE, date, pool));
@@ -6698,6 +6675,119 @@ test_prop_and_text_rep_sharing_collision(const svn
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+test_internal_txn_props(const svn_test_opts_t *opts,
+                        apr_pool_t *pool)
+{
+  svn_fs_t *fs;
+  svn_fs_txn_t *txn;
+  svn_string_t *val;
+  svn_prop_t prop;
+  svn_prop_t internal_prop;
+  apr_array_header_t *props;
+  apr_hash_t *actual_props;
+  svn_error_t *err;
+
+  SVN_ERR(svn_test__create_fs(&fs, "test-repo-internal-txn-props",
+                              opts, pool));
+  SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0,
+                            SVN_FS_TXN_CHECK_OOD |
+                            SVN_FS_TXN_CHECK_LOCKS |
+                            SVN_FS_TXN_CLIENT_DATE, pool));
+
+  val = svn_string_create("Ooops!", pool);
+
+  /* All attempts to modify internal txn properties should error out. */
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_OOD, val, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_OOD, NULL, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_LOCKS, val, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CLIENT_DATE, val, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+  err = svn_fs_change_txn_prop(txn, SVN_FS__PROP_TXN_CLIENT_DATE, NULL, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+
+  /* Try the same with svn_fs_change_txn_props(). */
+  prop.name = "foo";
+  prop.value = svn_string_create("bar", pool);
+  internal_prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
+  internal_prop.value = svn_string_create("Ooops!", pool);
+
+  props = apr_array_make(pool, 2, sizeof(svn_prop_t));
+  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+  APR_ARRAY_PUSH(props, svn_prop_t) = internal_prop;
+
+  err = svn_fs_change_txn_props(txn, props, pool);
+  SVN_TEST_ASSERT_ERROR(err, SVN_ERR_INCORRECT_PARAMS);
+
+  /* Ensure that the txn properties are left unchanged:
+
+     K 13
+     svn:check-ood
+     V 4
+     true
+     K 15
+     svn:check-locks
+     V 4
+     true
+     K 15
+     svn:client-date
+     V 1
+     0
+     K 8
+     svn:date
+     V 27
+     (timestamp like "2015-03-02T12:16:53.148931Z")
+     END
+  */
+  SVN_ERR(svn_fs_txn_proplist(&actual_props, txn, pool));
+
+  SVN_TEST_ASSERT(apr_hash_count(actual_props) == 4);
+
+  val = svn_hash_gets(actual_props, SVN_FS__PROP_TXN_CHECK_OOD);
+  SVN_TEST_ASSERT(val);
+  SVN_TEST_STRING_ASSERT(val->data, "true");
+
+  val = svn_hash_gets(actual_props, SVN_FS__PROP_TXN_CHECK_LOCKS);
+  SVN_TEST_ASSERT(val);
+  SVN_TEST_STRING_ASSERT(val->data, "true");
+
+  val = svn_hash_gets(actual_props, SVN_FS__PROP_TXN_CLIENT_DATE);
+  SVN_TEST_ASSERT(val);
+
+  if (strcmp(opts->fs_type, SVN_FS_TYPE_BDB) == 0)
+    {
+      /* ### Skip the svn:client-date check with BDB.  I think that we have a
+             bug in the BDB backend, because when SVN_FS_TXN_CLIENT_DATE is
+             set, a transaction created with svn_fs_begin_txn2() has the
+             svn:client-date property value = "1", even though no explicit
+             svn:date changes happened (it's "0" in FSFS).
+
+             This happens because svn_fs_base__begin_txn() sets svn:date in a
+             separate svn_fs_base__change_txn_prop() call when the initial
+             internal properties, including SVN_FS__PROP_TXN_CLIENT_DATE, are
+             already set.  As a consequence, we should have different bevavior
+             for transactions created with SVN_FS_TXN_CLIENT_DATE, and with no
+             explicit svn:date changes happening depending on the FS backend.
+             When such transaction is committed, FSFS is going to use the time
+             of the commit for the 'svn:date' value.  BDB, however, is going
+             to erroneously use the timestamp of the svn_fs_base__begin_txn()
+             call.
+      */
+    }
+  else
+    SVN_TEST_STRING_ASSERT(val->data, "0");
+
+  val = svn_hash_gets(actual_props, SVN_PROP_REVISION_DATE);
+  SVN_TEST_ASSERT(val);
+
+  return SVN_NO_ERROR;
+}
+
 /* ------------------------------------------------------------------------ */
 
 /* The test table.  */
@@ -6829,6 +6919,8 @@ static struct svn_test_descriptor_t test_funcs[] =
                        "test modify txn being written in FSFS"),
     SVN_TEST_OPTS_PASS(test_prop_and_text_rep_sharing_collision,
                        "test property and text rep-sharing collision"),
+    SVN_TEST_OPTS_PASS(test_internal_txn_props,
+                       "test attempts to change internal txn properties"),
     SVN_TEST_NULL
   };
 

Reply via email to