1/* dag.c : DAG-like interface filesystem, private to libsvn_fs
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include <string.h>
24
25#include "svn_path.h"
26#include "svn_time.h"
27#include "svn_error.h"
28#include "svn_fs.h"
29#include "svn_hash.h"
30#include "svn_props.h"
31#include "svn_pools.h"
32
33#include "dag.h"
34#include "err.h"
35#include "fs.h"
36#include "key-gen.h"
37#include "node-rev.h"
38#include "trail.h"
39#include "reps-strings.h"
40#include "revs-txns.h"
41#include "id.h"
42
43#include "util/fs_skels.h"
44
45#include "bdb/txn-table.h"
46#include "bdb/rev-table.h"
47#include "bdb/nodes-table.h"
48#include "bdb/copies-table.h"
49#include "bdb/reps-table.h"
50#include "bdb/strings-table.h"
51#include "bdb/checksum-reps-table.h"
52#include "bdb/changes-table.h"
53#include "bdb/node-origins-table.h"
54
55#include "private/svn_skel.h"
56#include "private/svn_fs_util.h"
57#include "private/svn_fspath.h"
58#include "../libsvn_fs/fs-loader.h"
59
60#include "svn_private_config.h"
61
62
63/* Initializing a filesystem.  */
64
65struct dag_node_t
66{
67  /*** NOTE: Keeping in-memory representations of disk data that can
68       be changed by other accessors is a nasty business.  Such
69       representations are basically a cache with some pretty complex
70       invalidation rules.  For example, the "node revision"
71       associated with a DAG node ID can look completely different to
72       a process that has modified that information as part of a
73       Berkeley DB transaction than it does to some other process.
74       That said, there are some aspects of a "node revision" which
75       never change, like its 'id' or 'kind'.  Our best bet is to
76       limit ourselves to exposing outside of this interface only
77       those immutable aspects of a DAG node representation.  ***/
78
79  /* The filesystem this dag node came from. */
80  svn_fs_t *fs;
81
82  /* The node revision ID for this dag node. */
83  svn_fs_id_t *id;
84
85  /* The node's type (file, dir, etc.) */
86  svn_node_kind_t kind;
87
88  /* the path at which this node was created. */
89  const char *created_path;
90};
91
92
93
94/* Trivial helper/accessor functions. */
95svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
96{
97  return node->kind;
98}
99
100
101const svn_fs_id_t *
102svn_fs_base__dag_get_id(dag_node_t *node)
103{
104  return node->id;
105}
106
107
108const char *
109svn_fs_base__dag_get_created_path(dag_node_t *node)
110{
111  return node->created_path;
112}
113
114
115svn_fs_t *
116svn_fs_base__dag_get_fs(dag_node_t *node)
117{
118  return node->fs;
119}
120
121
122svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
123                                             const char *txn_id)
124{
125  return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
126                 txn_id) == 0);
127}
128
129
130svn_error_t *
131svn_fs_base__dag_get_node(dag_node_t **node,
132                          svn_fs_t *fs,
133                          const svn_fs_id_t *id,
134                          trail_t *trail,
135                          apr_pool_t *pool)
136{
137  dag_node_t *new_node;
138  node_revision_t *noderev;
139
140  /* Construct the node. */
141  new_node = apr_pcalloc(pool, sizeof(*new_node));
142  new_node->fs = fs;
143  new_node->id = svn_fs_base__id_copy(id, pool);
144
145  /* Grab the contents so we can cache some of the immutable parts of it. */
146  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
147
148  /* Initialize the KIND and CREATED_PATH attributes */
149  new_node->kind = noderev->kind;
150  new_node->created_path = noderev->created_path;
151
152  /* Return a fresh new node */
153  *node = new_node;
154  return SVN_NO_ERROR;
155}
156
157
158svn_error_t *
159svn_fs_base__dag_get_revision(svn_revnum_t *rev,
160                              dag_node_t *node,
161                              trail_t *trail,
162                              apr_pool_t *pool)
163{
164  /* Use the txn ID from the NODE's id to look up the transaction and
165     get its revision number.  */
166  return svn_fs_base__txn_get_revision
167    (rev, svn_fs_base__dag_get_fs(node),
168     svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
169}
170
171
172svn_error_t *
173svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
174                                    dag_node_t *node,
175                                    trail_t *trail,
176                                    apr_pool_t *pool)
177{
178  node_revision_t *noderev;
179
180  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
181                                        trail, pool));
182  *id_p = noderev->predecessor_id;
183  return SVN_NO_ERROR;
184}
185
186
187svn_error_t *
188svn_fs_base__dag_get_predecessor_count(int *count,
189                                       dag_node_t *node,
190                                       trail_t *trail,
191                                       apr_pool_t *pool)
192{
193  node_revision_t *noderev;
194
195  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
196                                        trail, pool));
197  *count = noderev->predecessor_count;
198  return SVN_NO_ERROR;
199}
200
201
202/* Trail body for svn_fs_base__dag_init_fs. */
203static svn_error_t *
204txn_body_dag_init_fs(void *baton,
205                     trail_t *trail)
206{
207  node_revision_t noderev;
208  revision_t revision;
209  svn_revnum_t rev = SVN_INVALID_REVNUM;
210  svn_fs_t *fs = trail->fs;
211  svn_string_t date;
212  const char *txn_id;
213  const char *copy_id;
214  svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
215
216  /* Create empty root directory with node revision 0.0.0. */
217  memset(&noderev, 0, sizeof(noderev));
218  noderev.kind = svn_node_dir;
219  noderev.created_path = "/";
220  SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
221                                        trail, trail->pool));
222
223  /* Create a new transaction (better have an id of "0") */
224  SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
225  if (strcmp(txn_id, "0"))
226    return svn_error_createf
227      (SVN_ERR_FS_CORRUPT, 0,
228       _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
229       fs->path);
230
231  /* Create a default copy (better have an id of "0") */
232  SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, trail->pool));
233  if (strcmp(copy_id, "0"))
234    return svn_error_createf
235      (SVN_ERR_FS_CORRUPT, 0,
236       _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
237  SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
238                                  copy_kind_real, trail, trail->pool));
239
240  /* Link it into filesystem revision 0. */
241  revision.txn_id = txn_id;
242  SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
243  if (rev != 0)
244    return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
245                             _("Corrupt DB: initial revision number "
246                               "is not '0' in filesystem '%s'"), fs->path);
247
248  /* Promote our transaction to a "committed" transaction. */
249  SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
250                                          trail, trail->pool));
251
252  /* Set a date on revision 0. */
253  date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
254  date.len = strlen(date.data);
255  return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
256                                   trail, trail->pool);
257}
258
259
260svn_error_t *
261svn_fs_base__dag_init_fs(svn_fs_t *fs)
262{
263  return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
264                                TRUE, fs->pool);
265}
266
267
268
269/*** Directory node functions ***/
270
271/* Some of these are helpers for functions outside this section. */
272
273/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
274   hash, as part of TRAIL, or to NULL if NODEREV has no entries.  The
275   entries list will be allocated in POOL, and the entries in that
276   list will not have interesting value in their 'kind' fields.  If
277   NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
278static svn_error_t *
279get_dir_entries(apr_hash_t **entries_p,
280                svn_fs_t *fs,
281                node_revision_t *noderev,
282                trail_t *trail,
283                apr_pool_t *pool)
284{
285  apr_hash_t *entries = NULL;
286  apr_hash_index_t *hi;
287  svn_string_t entries_raw;
288  svn_skel_t *entries_skel;
289
290  /* Error if this is not a directory. */
291  if (noderev->kind != svn_node_dir)
292    return svn_error_create
293      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
294       _("Attempted to get entries of a non-directory node"));
295
296  /* If there's a DATA-KEY, there might be entries to fetch. */
297  if (noderev->data_key)
298    {
299      /* Now we have a rep, follow through to get the entries. */
300      SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
301                                        trail, pool));
302      entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
303
304      /* Were there entries?  Make a hash from them. */
305      if (entries_skel)
306        SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
307                                                pool));
308    }
309
310  /* No hash?  No problem.  */
311  *entries_p = NULL;
312  if (! entries)
313    return SVN_NO_ERROR;
314
315  /* Else, convert the hash from a name->id mapping to a name->dirent one.  */
316  *entries_p = apr_hash_make(pool);
317  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
318    {
319      const void *key;
320      apr_ssize_t klen;
321      void *val;
322      svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
323
324      /* KEY will be the entry name in ancestor, VAL the id.  */
325      apr_hash_this(hi, &key, &klen, &val);
326      dirent->name = key;
327      dirent->id = val;
328      dirent->kind = svn_node_unknown;
329      apr_hash_set(*entries_p, key, klen, dirent);
330    }
331
332  /* Return our findings. */
333  return SVN_NO_ERROR;
334}
335
336
337/* Set *ID_P to the node-id for entry NAME in PARENT, as part of
338   TRAIL.  If no such entry, set *ID_P to NULL but do not error.  The
339   entry is allocated in POOL or in the same pool as PARENT;
340   the caller should copy if it cares.  */
341static svn_error_t *
342dir_entry_id_from_node(const svn_fs_id_t **id_p,
343                       dag_node_t *parent,
344                       const char *name,
345                       trail_t *trail,
346                       apr_pool_t *pool)
347{
348  apr_hash_t *entries;
349  svn_fs_dirent_t *dirent;
350
351  SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
352  if (entries)
353    dirent = svn_hash_gets(entries, name);
354  else
355    dirent = NULL;
356
357  *id_p = dirent ? dirent->id : NULL;
358  return SVN_NO_ERROR;
359}
360
361
362/* Add or set in PARENT a directory entry NAME pointing to ID.
363   Allocations are done in TRAIL.
364
365   Assumptions:
366   - PARENT is a mutable directory.
367   - ID does not refer to an ancestor of parent
368   - NAME is a single path component
369*/
370static svn_error_t *
371set_entry(dag_node_t *parent,
372          const char *name,
373          const svn_fs_id_t *id,
374          const char *txn_id,
375          trail_t *trail,
376          apr_pool_t *pool)
377{
378  node_revision_t *parent_noderev;
379  const char *rep_key, *mutable_rep_key;
380  apr_hash_t *entries = NULL;
381  svn_stream_t *wstream;
382  apr_size_t len;
383  svn_string_t raw_entries;
384  svn_stringbuf_t *raw_entries_buf;
385  svn_skel_t *entries_skel;
386  svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
387
388  /* Get the parent's node-revision. */
389  SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
390                                        trail, pool));
391  rep_key = parent_noderev->data_key;
392  SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
393                                       fs, txn_id, trail, pool));
394
395  /* If the parent node already pointed at a mutable representation,
396     we don't need to do anything.  But if it didn't, either because
397     the parent didn't refer to any rep yet or because it referred to
398     an immutable one, we must make the parent refer to the mutable
399     rep we just created. */
400  if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
401    {
402      parent_noderev->data_key = mutable_rep_key;
403      SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
404                                            trail, pool));
405    }
406
407  /* If the new representation inherited nothing, start a new entries
408     list for it.  Else, go read its existing entries list. */
409  if (rep_key)
410    {
411      SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
412                                        trail, pool));
413      entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
414      if (entries_skel)
415        SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
416                                                pool));
417    }
418
419  /* If we still have no ENTRIES hash, make one here.  */
420  if (! entries)
421    entries = apr_hash_make(pool);
422
423  /* Now, add our new entry to the entries list. */
424  svn_hash_sets(entries, name, id);
425
426  /* Finally, replace the old entries list with the new one. */
427  SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
428                                            pool));
429  raw_entries_buf = svn_skel__unparse(entries_skel, pool);
430  SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
431                                                 mutable_rep_key, txn_id,
432                                                 TRUE, trail, pool));
433  len = raw_entries_buf->len;
434  SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
435  return svn_stream_close(wstream);
436}
437
438
439/* Make a new entry named NAME in PARENT, as part of TRAIL.  If IS_DIR
440   is true, then the node revision the new entry points to will be a
441   directory, else it will be a file.  The new node will be allocated
442   in POOL.  PARENT must be mutable, and must not have an entry
443   named NAME.  */
444static svn_error_t *
445make_entry(dag_node_t **child_p,
446           dag_node_t *parent,
447           const char *parent_path,
448           const char *name,
449           svn_boolean_t is_dir,
450           const char *txn_id,
451           trail_t *trail,
452           apr_pool_t *pool)
453{
454  const svn_fs_id_t *new_node_id;
455  node_revision_t new_noderev;
456
457  /* Make sure that NAME is a single path component. */
458  if (! svn_path_is_single_path_component(name))
459    return svn_error_createf
460      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
461       _("Attempted to create a node with an illegal name '%s'"), name);
462
463  /* Make sure that parent is a directory */
464  if (parent->kind != svn_node_dir)
465    return svn_error_create
466      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
467       _("Attempted to create entry in non-directory parent"));
468
469  /* Check that the parent is mutable. */
470  if (! svn_fs_base__dag_check_mutable(parent, txn_id))
471    return svn_error_createf
472      (SVN_ERR_FS_NOT_MUTABLE, NULL,
473       _("Attempted to clone child of non-mutable node"));
474
475  /* Check that parent does not already have an entry named NAME. */
476  SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
477  if (new_node_id)
478    return svn_error_createf
479      (SVN_ERR_FS_ALREADY_EXISTS, NULL,
480       _("Attempted to create entry that already exists"));
481
482  /* Create the new node's NODE-REVISION */
483  memset(&new_noderev, 0, sizeof(new_noderev));
484  new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
485  new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
486  SVN_ERR(svn_fs_base__create_node
487          (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
488           svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
489           txn_id, trail, pool));
490
491  /* Create a new dag_node_t for our new node */
492  SVN_ERR(svn_fs_base__dag_get_node(child_p,
493                                    svn_fs_base__dag_get_fs(parent),
494                                    new_node_id, trail, pool));
495
496  /* We can safely call set_entry because we already know that
497     PARENT is mutable, and we just created CHILD, so we know it has
498     no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
499  return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p),
500                   txn_id, trail, pool);
501}
502
503
504svn_error_t *
505svn_fs_base__dag_dir_entries(apr_hash_t **entries,
506                             dag_node_t *node,
507                             trail_t *trail,
508                             apr_pool_t *pool)
509{
510  node_revision_t *noderev;
511  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
512                                        trail, pool));
513  return get_dir_entries(entries, node->fs, noderev, trail, pool);
514}
515
516
517svn_error_t *
518svn_fs_base__dag_set_entry(dag_node_t *node,
519                           const char *entry_name,
520                           const svn_fs_id_t *id,
521                           const char *txn_id,
522                           trail_t *trail,
523                           apr_pool_t *pool)
524{
525  /* Check it's a directory. */
526  if (node->kind != svn_node_dir)
527    return svn_error_create
528      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
529       _("Attempted to set entry in non-directory node"));
530
531  /* Check it's mutable. */
532  if (! svn_fs_base__dag_check_mutable(node, txn_id))
533    return svn_error_create
534      (SVN_ERR_FS_NOT_MUTABLE, NULL,
535       _("Attempted to set entry in immutable node"));
536
537  return set_entry(node, entry_name, id, txn_id, trail, pool);
538}
539
540
541
542/*** Proplists. ***/
543
544svn_error_t *
545svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
546                              dag_node_t *node,
547                              trail_t *trail,
548                              apr_pool_t *pool)
549{
550  node_revision_t *noderev;
551  apr_hash_t *proplist = NULL;
552  svn_string_t raw_proplist;
553  svn_skel_t *proplist_skel;
554
555  /* Go get a fresh NODE-REVISION for this node. */
556  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
557                                        trail, pool));
558
559  /* Get property key (returning early if there isn't one) . */
560  if (! noderev->prop_key)
561    {
562      *proplist_p = NULL;
563      return SVN_NO_ERROR;
564    }
565
566  /* Get the string associated with the property rep, parsing it as a
567     skel, and then attempt to parse *that* into a property hash.  */
568  SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
569                                    svn_fs_base__dag_get_fs(node),
570                                    noderev->prop_key, trail, pool));
571  proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
572  if (proplist_skel)
573    SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
574
575  *proplist_p = proplist;
576  return SVN_NO_ERROR;
577}
578
579
580svn_error_t *
581svn_fs_base__dag_set_proplist(dag_node_t *node,
582                              const apr_hash_t *proplist,
583                              const char *txn_id,
584                              trail_t *trail,
585                              apr_pool_t *pool)
586{
587  node_revision_t *noderev;
588  const char *rep_key, *mutable_rep_key;
589  svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
590  svn_stream_t *wstream;
591  apr_size_t len;
592  svn_skel_t *proplist_skel;
593  svn_stringbuf_t *raw_proplist_buf;
594  base_fs_data_t *bfd = fs->fsap_data;
595
596  /* Sanity check: this node better be mutable! */
597  if (! svn_fs_base__dag_check_mutable(node, txn_id))
598    {
599      svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool);
600      return svn_error_createf
601        (SVN_ERR_FS_NOT_MUTABLE, NULL,
602         _("Can't set proplist on *immutable* node-revision %s"),
603         idstr->data);
604    }
605
606  /* Go get a fresh NODE-REVISION for this node. */
607  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
608                                        trail, pool));
609  rep_key = noderev->prop_key;
610
611  /* Flatten the proplist into a string. */
612  SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
613  raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
614
615  /* If this repository supports representation sharing, and the
616     resulting property list is exactly the same as another string in
617     the database, just use the previously existing string and get
618     outta here. */
619  if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
620    {
621      svn_error_t *err;
622      const char *dup_rep_key;
623      svn_checksum_t *checksum;
624
625      SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
626                           raw_proplist_buf->len, pool));
627
628      err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
629                                         trail, pool);
630      if (! err)
631        {
632          if (noderev->prop_key)
633            SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
634                                                       txn_id, trail, pool));
635          noderev->prop_key = dup_rep_key;
636          return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
637                                               trail, pool);
638        }
639      else if (err)
640        {
641          if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
642            return svn_error_trace(err);
643
644          svn_error_clear(err);
645          err = SVN_NO_ERROR;
646        }
647    }
648
649  /* Get a mutable version of this rep (updating the node revision if
650     this isn't a NOOP)  */
651  SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
652                                       fs, txn_id, trail, pool));
653  if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
654    {
655      noderev->prop_key = mutable_rep_key;
656      SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
657                                            trail, pool));
658    }
659
660  /* Replace the old property list with the new one. */
661  SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
662                                                 mutable_rep_key, txn_id,
663                                                 TRUE, trail, pool));
664  len = raw_proplist_buf->len;
665  SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
666  SVN_ERR(svn_stream_close(wstream));
667
668  return SVN_NO_ERROR;
669}
670
671
672
673/*** Roots. ***/
674
675svn_error_t *
676svn_fs_base__dag_revision_root(dag_node_t **node_p,
677                               svn_fs_t *fs,
678                               svn_revnum_t rev,
679                               trail_t *trail,
680                               apr_pool_t *pool)
681{
682  const svn_fs_id_t *root_id;
683
684  SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
685  return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
686}
687
688
689svn_error_t *
690svn_fs_base__dag_txn_root(dag_node_t **node_p,
691                          svn_fs_t *fs,
692                          const char *txn_id,
693                          trail_t *trail,
694                          apr_pool_t *pool)
695{
696  const svn_fs_id_t *root_id, *ignored;
697
698  SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
699                                   trail, pool));
700  return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
701}
702
703
704svn_error_t *
705svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
706                               svn_fs_t *fs,
707                               const char *txn_id,
708                               trail_t *trail,
709                               apr_pool_t *pool)
710{
711  const svn_fs_id_t *base_root_id, *ignored;
712
713  SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
714                                   trail, pool));
715  return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
716}
717
718
719svn_error_t *
720svn_fs_base__dag_clone_child(dag_node_t **child_p,
721                             dag_node_t *parent,
722                             const char *parent_path,
723                             const char *name,
724                             const char *copy_id,
725                             const char *txn_id,
726                             trail_t *trail,
727                             apr_pool_t *pool)
728{
729  dag_node_t *cur_entry; /* parent's current entry named NAME */
730  const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
731  svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
732
733  /* First check that the parent is mutable. */
734  if (! svn_fs_base__dag_check_mutable(parent, txn_id))
735    return svn_error_createf
736      (SVN_ERR_FS_NOT_MUTABLE, NULL,
737       _("Attempted to clone child of non-mutable node"));
738
739  /* Make sure that NAME is a single path component. */
740  if (! svn_path_is_single_path_component(name))
741    return svn_error_createf
742      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
743       _("Attempted to make a child clone with an illegal name '%s'"), name);
744
745  /* Find the node named NAME in PARENT's entries list if it exists. */
746  SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool));
747
748  /* Check for mutability in the node we found.  If it's mutable, we
749     don't need to clone it. */
750  if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
751    {
752      /* This has already been cloned */
753      new_node_id = cur_entry->id;
754    }
755  else
756    {
757      node_revision_t *noderev;
758
759      /* Go get a fresh NODE-REVISION for current child node. */
760      SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
761                                            trail, pool));
762
763      /* Do the clone thingy here. */
764      noderev->predecessor_id = cur_entry->id;
765      if (noderev->predecessor_count != -1)
766        noderev->predecessor_count++;
767      noderev->created_path = svn_fspath__join(parent_path, name, pool);
768      SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
769                                            noderev, copy_id, txn_id,
770                                            trail, pool));
771
772      /* Replace the ID in the parent's ENTRY list with the ID which
773         refers to the mutable clone of this child. */
774      SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool));
775    }
776
777  /* Initialize the youngster. */
778  return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
779}
780
781
782
783svn_error_t *
784svn_fs_base__dag_clone_root(dag_node_t **root_p,
785                            svn_fs_t *fs,
786                            const char *txn_id,
787                            trail_t *trail,
788                            apr_pool_t *pool)
789{
790  const svn_fs_id_t *base_root_id, *root_id;
791  node_revision_t *noderev;
792
793  /* Get the node ID's of the root directories of the transaction and
794     its base revision.  */
795  SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
796                                   trail, pool));
797
798  /* Oh, give me a clone...
799     (If they're the same, we haven't cloned the transaction's root
800     directory yet.)  */
801  if (svn_fs_base__id_eq(root_id, base_root_id))
802    {
803      const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
804
805      /* Of my own flesh and bone...
806         (Get the NODE-REVISION for the base node, and then write
807         it back out as the clone.) */
808      SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
809                                            trail, pool));
810
811      /* With its Y-chromosome changed to X...
812         (Store it with an updated predecessor count.) */
813      /* ### TODO: Does it even makes sense to have a different copy id for
814         the root node?  That is, does this function need a copy_id
815         passed in?  */
816      noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
817      if (noderev->predecessor_count != -1)
818        noderev->predecessor_count++;
819      SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
820                                            noderev, base_copy_id,
821                                            txn_id, trail, pool));
822
823      /* ... And when it is grown
824       *      Then my own little clone
825       *        Will be of the opposite sex!
826       */
827      SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
828    }
829
830  /*
831   * (Sung to the tune of "Home, Home on the Range", with thanks to
832   * Randall Garrett and Isaac Asimov.)
833   */
834
835  /* One way or another, root_id now identifies a cloned root node. */
836  return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
837}
838
839
840svn_error_t *
841svn_fs_base__dag_delete(dag_node_t *parent,
842                        const char *name,
843                        const char *txn_id,
844                        trail_t *trail,
845                        apr_pool_t *pool)
846{
847  node_revision_t *parent_noderev;
848  const char *rep_key, *mutable_rep_key;
849  apr_hash_t *entries = NULL;
850  svn_skel_t *entries_skel;
851  svn_fs_t *fs = parent->fs;
852  svn_string_t str;
853  svn_fs_id_t *id = NULL;
854  dag_node_t *node;
855
856  /* Make sure parent is a directory. */
857  if (parent->kind != svn_node_dir)
858    return svn_error_createf
859      (SVN_ERR_FS_NOT_DIRECTORY, NULL,
860       _("Attempted to delete entry '%s' from *non*-directory node"), name);
861
862  /* Make sure parent is mutable. */
863  if (! svn_fs_base__dag_check_mutable(parent, txn_id))
864    return svn_error_createf
865      (SVN_ERR_FS_NOT_MUTABLE, NULL,
866       _("Attempted to delete entry '%s' from immutable directory node"),
867       name);
868
869  /* Make sure that NAME is a single path component. */
870  if (! svn_path_is_single_path_component(name))
871    return svn_error_createf
872      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
873       _("Attempted to delete a node with an illegal name '%s'"), name);
874
875  /* Get a fresh NODE-REVISION for the parent node. */
876  SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
877                                        trail, pool));
878
879  /* Get the key for the parent's entries list (data) representation. */
880  rep_key = parent_noderev->data_key;
881
882  /* No REP_KEY means no representation, and no representation means
883     no data, and no data means no entries...there's nothing here to
884     delete! */
885  if (! rep_key)
886    return svn_error_createf
887      (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
888       _("Delete failed: directory has no entry '%s'"), name);
889
890  /* Ensure we have a key to a mutable representation of the entries
891     list.  We'll have to update the NODE-REVISION if it points to an
892     immutable version.  */
893  SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
894                                       fs, txn_id, trail, pool));
895  if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
896    {
897      parent_noderev->data_key = mutable_rep_key;
898      SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
899                                            trail, pool));
900    }
901
902  /* Read the representation, then use it to get the string that holds
903     the entries list.  Parse that list into a skel, and parse *that*
904     into a hash. */
905
906  SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
907  entries_skel = svn_skel__parse(str.data, str.len, pool);
908  if (entries_skel)
909    SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
910
911  /* Find NAME in the ENTRIES skel.  */
912  if (entries)
913    id = svn_hash_gets(entries, name);
914
915  /* If we never found ID in ENTRIES (perhaps because there are no
916     ENTRIES, perhaps because ID just isn't in the existing ENTRIES
917     ... it doesn't matter), return an error.  */
918  if (! id)
919    return svn_error_createf
920      (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
921       _("Delete failed: directory has no entry '%s'"), name);
922
923  /* Use the ID of this ENTRY to get the entry's node.  */
924  SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
925                                    id, trail, pool));
926
927  /* If mutable, remove it and any mutable children from db. */
928  SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
929                                             trail, pool));
930
931  /* Remove this entry from its parent's entries list. */
932  svn_hash_sets(entries, name, NULL);
933
934  /* Replace the old entries list with the new one. */
935  {
936    svn_stream_t *ws;
937    svn_stringbuf_t *unparsed_entries;
938    apr_size_t len;
939
940    SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
941    unparsed_entries = svn_skel__unparse(entries_skel, pool);
942    SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
943                                                   txn_id, TRUE, trail,
944                                                   pool));
945    len = unparsed_entries->len;
946    SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
947    SVN_ERR(svn_stream_close(ws));
948  }
949
950  return SVN_NO_ERROR;
951}
952
953
954svn_error_t *
955svn_fs_base__dag_remove_node(svn_fs_t *fs,
956                             const svn_fs_id_t *id,
957                             const char *txn_id,
958                             trail_t *trail,
959                             apr_pool_t *pool)
960{
961  dag_node_t *node;
962  node_revision_t *noderev;
963
964  /* Fetch the node. */
965  SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
966
967  /* If immutable, do nothing and return immediately. */
968  if (! svn_fs_base__dag_check_mutable(node, txn_id))
969    return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
970                             _("Attempted removal of immutable node"));
971
972  /* Get a fresh node-revision. */
973  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
974
975  /* Delete any mutable property representation. */
976  if (noderev->prop_key)
977    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
978                                               txn_id, trail, pool));
979
980  /* Delete any mutable data representation. */
981  if (noderev->data_key)
982    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
983                                               txn_id, trail, pool));
984
985  /* Delete any mutable edit representation (files only). */
986  if (noderev->edit_key)
987    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
988                                               txn_id, trail, pool));
989
990  /* Delete the node revision itself. */
991  return svn_fs_base__delete_node_revision(fs, id,
992                                           noderev->predecessor_id == NULL,
993                                           trail, pool);
994}
995
996
997svn_error_t *
998svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
999                                   const svn_fs_id_t *id,
1000                                   const char *txn_id,
1001                                   trail_t *trail,
1002                                   apr_pool_t *pool)
1003{
1004  dag_node_t *node;
1005
1006  /* Get the node. */
1007  SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
1008
1009  /* If immutable, do nothing and return immediately. */
1010  if (! svn_fs_base__dag_check_mutable(node, txn_id))
1011    return SVN_NO_ERROR;
1012
1013  /* Else it's mutable.  Recurse on directories... */
1014  if (node->kind == svn_node_dir)
1015    {
1016      apr_hash_t *entries;
1017      apr_hash_index_t *hi;
1018
1019      /* Loop over hash entries */
1020      SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
1021      if (entries)
1022        {
1023          apr_pool_t *subpool = svn_pool_create(pool);
1024          for (hi = apr_hash_first(pool, entries);
1025               hi;
1026               hi = apr_hash_next(hi))
1027            {
1028              void *val;
1029              svn_fs_dirent_t *dirent;
1030
1031              apr_hash_this(hi, NULL, NULL, &val);
1032              dirent = val;
1033              SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
1034                                                         txn_id, trail,
1035                                                         subpool));
1036            }
1037        }
1038    }
1039
1040  /* ... then delete the node itself, any mutable representations and
1041     strings it points to, and possibly its node-origins record. */
1042  return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
1043}
1044
1045
1046svn_error_t *
1047svn_fs_base__dag_make_file(dag_node_t **child_p,
1048                           dag_node_t *parent,
1049                           const char *parent_path,
1050                           const char *name,
1051                           const char *txn_id,
1052                           trail_t *trail,
1053                           apr_pool_t *pool)
1054{
1055  /* Call our little helper function */
1056  return make_entry(child_p, parent, parent_path, name, FALSE,
1057                    txn_id, trail, pool);
1058}
1059
1060
1061svn_error_t *
1062svn_fs_base__dag_make_dir(dag_node_t **child_p,
1063                          dag_node_t *parent,
1064                          const char *parent_path,
1065                          const char *name,
1066                          const char *txn_id,
1067                          trail_t *trail,
1068                          apr_pool_t *pool)
1069{
1070  /* Call our little helper function */
1071  return make_entry(child_p, parent, parent_path, name, TRUE,
1072                    txn_id, trail, pool);
1073}
1074
1075
1076svn_error_t *
1077svn_fs_base__dag_get_contents(svn_stream_t **contents,
1078                              dag_node_t *file,
1079                              trail_t *trail,
1080                              apr_pool_t *pool)
1081{
1082  node_revision_t *noderev;
1083
1084  /* Make sure our node is a file. */
1085  if (file->kind != svn_node_file)
1086    return svn_error_createf
1087      (SVN_ERR_FS_NOT_FILE, NULL,
1088       _("Attempted to get textual contents of a *non*-file node"));
1089
1090  /* Go get a fresh node-revision for FILE. */
1091  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1092                                        trail, pool));
1093
1094  /* Our job is to _return_ a stream on the file's contents, so the
1095     stream has to be trail-independent.  Here, we pass NULL to tell
1096     the stream that we're not providing it a trail that lives across
1097     reads.  This means the stream will do each read in a one-off,
1098     temporary trail.  */
1099  return svn_fs_base__rep_contents_read_stream(contents, file->fs,
1100                                               noderev->data_key,
1101                                               FALSE, trail, pool);
1102
1103  /* Note that we're not registering any `close' func, because there's
1104     nothing to cleanup outside of our trail.  When the trail is
1105     freed, the stream/baton will be too. */
1106}
1107
1108
1109svn_error_t *
1110svn_fs_base__dag_file_length(svn_filesize_t *length,
1111                             dag_node_t *file,
1112                             trail_t *trail,
1113                             apr_pool_t *pool)
1114{
1115  node_revision_t *noderev;
1116
1117  /* Make sure our node is a file. */
1118  if (file->kind != svn_node_file)
1119    return svn_error_createf
1120      (SVN_ERR_FS_NOT_FILE, NULL,
1121       _("Attempted to get length of a *non*-file node"));
1122
1123  /* Go get a fresh node-revision for FILE, and . */
1124  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1125                                        trail, pool));
1126  if (noderev->data_key)
1127    SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
1128                                           noderev->data_key, trail, pool));
1129  else
1130    *length = 0;
1131
1132  return SVN_NO_ERROR;
1133}
1134
1135
1136svn_error_t *
1137svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
1138                               svn_checksum_kind_t checksum_kind,
1139                               dag_node_t *file,
1140                               trail_t *trail,
1141                               apr_pool_t *pool)
1142{
1143  node_revision_t *noderev;
1144
1145  if (file->kind != svn_node_file)
1146    return svn_error_createf
1147      (SVN_ERR_FS_NOT_FILE, NULL,
1148       _("Attempted to get checksum of a *non*-file node"));
1149
1150  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
1151                                        trail, pool));
1152  if (! noderev->data_key)
1153    {
1154      *checksum = NULL;
1155      return SVN_NO_ERROR;
1156    }
1157
1158  if (checksum_kind == svn_checksum_md5)
1159    return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
1160                                               noderev->data_key,
1161                                               trail, pool);
1162  else if (checksum_kind == svn_checksum_sha1)
1163    return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
1164                                               noderev->data_key,
1165                                               trail, pool);
1166  else
1167    return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1168}
1169
1170
1171svn_error_t *
1172svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
1173                                 dag_node_t *file,
1174                                 const char *txn_id,
1175                                 trail_t *trail,
1176                                 apr_pool_t *pool)
1177{
1178  svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1179  node_revision_t *noderev;
1180  const char *mutable_rep_key;
1181  svn_stream_t *ws;
1182
1183  /* Make sure our node is a file. */
1184  if (file->kind != svn_node_file)
1185    return svn_error_createf
1186      (SVN_ERR_FS_NOT_FILE, NULL,
1187       _("Attempted to set textual contents of a *non*-file node"));
1188
1189  /* Make sure our node is mutable. */
1190  if (! svn_fs_base__dag_check_mutable(file, txn_id))
1191    return svn_error_createf
1192      (SVN_ERR_FS_NOT_MUTABLE, NULL,
1193       _("Attempted to set textual contents of an immutable node"));
1194
1195  /* Get the node revision. */
1196  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1197                                        trail, pool));
1198
1199  /* If this node already has an EDIT-DATA-KEY, destroy the data
1200     associated with that key.  */
1201  if (noderev->edit_key)
1202    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
1203                                               txn_id, trail, pool));
1204
1205  /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
1206     use. */
1207  SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
1208                                       txn_id, trail, pool));
1209
1210  /* We made a new rep, so update the node revision. */
1211  noderev->edit_key = mutable_rep_key;
1212  SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
1213                                        trail, pool));
1214
1215  /* Return a writable stream with which to set new contents. */
1216  SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
1217                                                 txn_id, FALSE, trail,
1218                                                 pool));
1219  *contents = ws;
1220
1221  return SVN_NO_ERROR;
1222}
1223
1224
1225
1226svn_error_t *
1227svn_fs_base__dag_finalize_edits(dag_node_t *file,
1228                                const svn_checksum_t *checksum,
1229                                const char *txn_id,
1230                                trail_t *trail,
1231                                apr_pool_t *pool)
1232{
1233  svn_fs_t *fs = file->fs;   /* just for nicer indentation */
1234  node_revision_t *noderev;
1235  const char *old_data_key, *new_data_key, *useless_data_key = NULL;
1236  const char *data_key_uniquifier = NULL;
1237  svn_checksum_t *md5_checksum, *sha1_checksum;
1238  base_fs_data_t *bfd = fs->fsap_data;
1239
1240  /* Make sure our node is a file. */
1241  if (file->kind != svn_node_file)
1242    return svn_error_createf
1243      (SVN_ERR_FS_NOT_FILE, NULL,
1244       _("Attempted to set textual contents of a *non*-file node"));
1245
1246  /* Make sure our node is mutable. */
1247  if (! svn_fs_base__dag_check_mutable(file, txn_id))
1248    return svn_error_createf
1249      (SVN_ERR_FS_NOT_MUTABLE, NULL,
1250       _("Attempted to set textual contents of an immutable node"));
1251
1252  /* Get the node revision. */
1253  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
1254                                        trail, pool));
1255
1256  /* If this node has no EDIT-DATA-KEY, this is a no-op. */
1257  if (! noderev->edit_key)
1258    return SVN_NO_ERROR;
1259
1260  /* Get our representation's checksums. */
1261  SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
1262                                              fs, noderev->edit_key,
1263                                              trail, pool));
1264
1265  /* If our caller provided a checksum of the right kind to compare, do so. */
1266  if (checksum)
1267    {
1268      svn_checksum_t *test_checksum;
1269
1270      if (checksum->kind == svn_checksum_md5)
1271        test_checksum = md5_checksum;
1272      else if (checksum->kind == svn_checksum_sha1)
1273        test_checksum = sha1_checksum;
1274      else
1275        return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
1276
1277      if (! svn_checksum_match(checksum, test_checksum))
1278        return svn_checksum_mismatch_err(checksum, test_checksum, pool,
1279                        _("Checksum mismatch on representation '%s'"),
1280                        noderev->edit_key);
1281    }
1282
1283  /* Now, we want to delete the old representation and replace it with
1284     the new.  Of course, we don't actually delete anything until
1285     everything is being properly referred to by the node-revision
1286     skel.
1287
1288     Now, if the result of all this editing is that we've created a
1289     representation that describes content already represented
1290     immutably in our database, we don't even need to keep these edits.
1291     We can simply point our data_key at that pre-existing
1292     representation and throw away our work!  In this situation,
1293     though, we'll need a unique ID to help other code distinguish
1294     between "the contents weren't touched" and "the contents were
1295     touched but still look the same" (to state it oversimply).  */
1296  old_data_key = noderev->data_key;
1297  if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
1298    {
1299      svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
1300                                                      sha1_checksum,
1301                                                      trail, pool);
1302      if (! err)
1303        {
1304          useless_data_key = noderev->edit_key;
1305          err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
1306                                                 trail->fs, trail, pool);
1307        }
1308      else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
1309        {
1310          svn_error_clear(err);
1311          err = SVN_NO_ERROR;
1312          new_data_key = noderev->edit_key;
1313        }
1314      SVN_ERR(err);
1315    }
1316  else
1317    {
1318      new_data_key = noderev->edit_key;
1319    }
1320
1321  noderev->data_key = new_data_key;
1322  noderev->data_key_uniquifier = data_key_uniquifier;
1323  noderev->edit_key = NULL;
1324
1325  SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
1326
1327  /* Only *now* can we safely destroy the old representation (if it
1328     even existed in the first place). */
1329  if (old_data_key)
1330    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
1331                                               trail, pool));
1332
1333  /* If we've got a discardable rep (probably because we ended up
1334     re-using a preexisting one), throw out the discardable rep. */
1335  if (useless_data_key)
1336    SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
1337                                               txn_id, trail, pool));
1338
1339  return SVN_NO_ERROR;
1340}
1341
1342
1343
1344dag_node_t *
1345svn_fs_base__dag_dup(dag_node_t *node,
1346                     apr_pool_t *pool)
1347{
1348  /* Allocate our new node. */
1349  dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
1350
1351  new_node->fs = node->fs;
1352  new_node->id = svn_fs_base__id_copy(node->id, pool);
1353  new_node->kind = node->kind;
1354  new_node->created_path = apr_pstrdup(pool, node->created_path);
1355  return new_node;
1356}
1357
1358
1359svn_error_t *
1360svn_fs_base__dag_open(dag_node_t **child_p,
1361                      dag_node_t *parent,
1362                      const char *name,
1363                      trail_t *trail,
1364                      apr_pool_t *pool)
1365{
1366  const svn_fs_id_t *node_id;
1367
1368  /* Ensure that NAME exists in PARENT's entry list. */
1369  SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
1370  if (! node_id)
1371    return svn_error_createf
1372      (SVN_ERR_FS_NOT_FOUND, NULL,
1373       _("Attempted to open non-existent child node '%s'"), name);
1374
1375  /* Make sure that NAME is a single path component. */
1376  if (! svn_path_is_single_path_component(name))
1377    return svn_error_createf
1378      (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
1379       _("Attempted to open node with an illegal name '%s'"), name);
1380
1381  /* Now get the node that was requested. */
1382  return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
1383                                   node_id, trail, pool);
1384}
1385
1386
1387svn_error_t *
1388svn_fs_base__dag_copy(dag_node_t *to_node,
1389                      const char *entry,
1390                      dag_node_t *from_node,
1391                      svn_boolean_t preserve_history,
1392                      svn_revnum_t from_rev,
1393                      const char *from_path,
1394                      const char *txn_id,
1395                      trail_t *trail,
1396                      apr_pool_t *pool)
1397{
1398  const svn_fs_id_t *id;
1399
1400  if (preserve_history)
1401    {
1402      node_revision_t *noderev;
1403      const char *copy_id;
1404      svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
1405      const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
1406      const char *from_txn_id = NULL;
1407
1408      /* Make a copy of the original node revision. */
1409      SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
1410                                            trail, pool));
1411
1412      /* Reserve a copy ID for this new copy. */
1413      SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
1414
1415      /* Create a successor with its predecessor pointing at the copy
1416         source. */
1417      noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
1418      if (noderev->predecessor_count != -1)
1419        noderev->predecessor_count++;
1420      noderev->created_path = svn_fspath__join
1421        (svn_fs_base__dag_get_created_path(to_node), entry, pool);
1422      SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
1423                                            copy_id, txn_id, trail, pool));
1424
1425      /* Translate FROM_REV into a transaction ID. */
1426      SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
1427                                          trail, pool));
1428
1429      /* Now that we've done the copy, we need to add the information
1430         about the copy to the `copies' table, using the COPY_ID we
1431         reserved above.  */
1432      SVN_ERR(svn_fs_bdb__create_copy
1433              (fs, copy_id,
1434               svn_fs__canonicalize_abspath(from_path, pool),
1435               from_txn_id, id, copy_kind_real, trail, pool));
1436
1437      /* Finally, add the COPY_ID to the transaction's list of copies
1438         so that, if this transaction is aborted, the `copies' table
1439         entry we added above will be cleaned up. */
1440      SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
1441    }
1442  else  /* don't preserve history */
1443    {
1444      id = svn_fs_base__dag_get_id(from_node);
1445    }
1446
1447  /* Set the entry in to_node to the new id. */
1448  return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
1449                                    trail, pool);
1450}
1451
1452
1453
1454/*** Deltification ***/
1455
1456/* Maybe change the representation identified by TARGET_REP_KEY to be
1457   a delta against the representation identified by SOURCE_REP_KEY.
1458   Some reasons why we wouldn't include:
1459
1460      - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
1461
1462      - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
1463        TXN_ID is non-NULL).
1464
1465      - The delta provides less space savings that a fulltext (this is
1466        a detail handled by lower logic layers, not this function).
1467
1468   Do this work in TRAIL, using POOL for necessary allocations.
1469*/
1470static svn_error_t *
1471maybe_deltify_mutable_rep(const char *target_rep_key,
1472                          const char *source_rep_key,
1473                          const char *txn_id,
1474                          trail_t *trail,
1475                          apr_pool_t *pool)
1476{
1477  if (! (target_rep_key && source_rep_key
1478         && (strcmp(target_rep_key, source_rep_key) != 0)))
1479    return SVN_NO_ERROR;
1480
1481  if (txn_id)
1482    {
1483      representation_t *target_rep;
1484      SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
1485                                   trail, pool));
1486      if (strcmp(target_rep->txn_id, txn_id) != 0)
1487        return SVN_NO_ERROR;
1488    }
1489
1490  return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
1491                                  trail, pool);
1492}
1493
1494
1495svn_error_t *
1496svn_fs_base__dag_deltify(dag_node_t *target,
1497                         dag_node_t *source,
1498                         svn_boolean_t props_only,
1499                         const char *txn_id,
1500                         trail_t *trail,
1501                         apr_pool_t *pool)
1502{
1503  node_revision_t *source_nr, *target_nr;
1504  svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
1505
1506  /* Get node revisions for the two nodes.  */
1507  SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
1508                                        trail, pool));
1509  SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
1510                                        trail, pool));
1511
1512  /* If TARGET and SOURCE both have properties, and are not sharing a
1513     property key, deltify TARGET's properties.  */
1514  SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
1515                                    txn_id, trail, pool));
1516
1517  /* If we are not only attending to properties, and if TARGET and
1518     SOURCE both have data, and are not sharing a data key, deltify
1519     TARGET's data.  */
1520  if (! props_only)
1521    SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
1522                                      txn_id, trail, pool));
1523
1524  return SVN_NO_ERROR;
1525}
1526
1527/* Maybe store a `checksum-reps' index record for the representation whose
1528   key is REP.  (If there's already a rep for this checksum, we don't
1529   bother overwriting it.)  */
1530static svn_error_t *
1531maybe_store_checksum_rep(const char *rep,
1532                         trail_t *trail,
1533                         apr_pool_t *pool)
1534{
1535  svn_error_t *err = SVN_NO_ERROR;
1536  svn_fs_t *fs = trail->fs;
1537  svn_checksum_t *sha1_checksum;
1538
1539  /* We want the SHA1 checksum, if any. */
1540  SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
1541                                              fs, rep, trail, pool));
1542  if (sha1_checksum)
1543    {
1544      err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
1545      if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1546        {
1547          svn_error_clear(err);
1548          err = SVN_NO_ERROR;
1549        }
1550    }
1551  return svn_error_trace(err);
1552}
1553
1554svn_error_t *
1555svn_fs_base__dag_index_checksums(dag_node_t *node,
1556                                 trail_t *trail,
1557                                 apr_pool_t *pool)
1558{
1559  node_revision_t *node_rev;
1560
1561  SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
1562                                        trail, pool));
1563  if ((node_rev->kind == svn_node_file) && node_rev->data_key)
1564    SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
1565  if (node_rev->prop_key)
1566    SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
1567
1568  return SVN_NO_ERROR;
1569}
1570
1571
1572
1573/*** Committing ***/
1574
1575svn_error_t *
1576svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
1577                            svn_fs_txn_t *txn,
1578                            trail_t *trail,
1579                            apr_pool_t *pool)
1580{
1581  revision_t revision;
1582  svn_string_t date;
1583  apr_hash_t *txnprops;
1584  svn_fs_t *fs = txn->fs;
1585  const char *txn_id = txn->id;
1586
1587  /* Remove any temporary transaction properties initially created by
1588     begin_txn().  */
1589  SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
1590
1591  /* Add new revision entry to `revisions' table. */
1592  revision.txn_id = txn_id;
1593  *new_rev = SVN_INVALID_REVNUM;
1594  SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
1595
1596  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
1597    SVN_ERR(svn_fs_base__set_txn_prop
1598            (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
1599
1600  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
1601    SVN_ERR(svn_fs_base__set_txn_prop
1602            (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
1603
1604  /* Promote the unfinished transaction to a committed one. */
1605  SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
1606                                          trail, pool));
1607
1608  /* Set a date on the commit.  We wait until now to fetch the date,
1609     so it's definitely newer than any previous revision's date. */
1610  date.data = svn_time_to_cstring(apr_time_now(), pool);
1611  date.len = strlen(date.data);
1612  return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
1613                                   NULL, &date, trail, pool);
1614}
1615
1616
1617/*** Comparison. ***/
1618
1619svn_error_t *
1620svn_fs_base__things_different(svn_boolean_t *props_changed,
1621                              svn_boolean_t *contents_changed,
1622                              dag_node_t *node1,
1623                              dag_node_t *node2,
1624                              trail_t *trail,
1625                              apr_pool_t *pool)
1626{
1627  node_revision_t *noderev1, *noderev2;
1628
1629  /* If we have no place to store our results, don't bother doing
1630     anything. */
1631  if (! props_changed && ! contents_changed)
1632    return SVN_NO_ERROR;
1633
1634  /* The node revision skels for these two nodes. */
1635  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
1636                                        trail, pool));
1637  SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
1638                                        trail, pool));
1639
1640  /* Compare property keys. */
1641  if (props_changed != NULL)
1642    *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
1643                                               noderev2->prop_key));
1644
1645  /* Compare contents keys and their (optional) uniquifiers. */
1646  if (contents_changed != NULL)
1647    *contents_changed =
1648      (! (svn_fs_base__same_keys(noderev1->data_key,
1649                                 noderev2->data_key)
1650          /* Technically, these uniquifiers aren't used and "keys",
1651             but keys are base-36 stringified numbers, so we'll take
1652             this liberty. */
1653          && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
1654                                     noderev2->data_key_uniquifier))));
1655
1656  return SVN_NO_ERROR;
1657}
1658
1659
1660
1661/*** Mergeinfo tracking stuff ***/
1662
1663svn_error_t *
1664svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
1665                                     apr_int64_t *count,
1666                                     dag_node_t *node,
1667                                     trail_t *trail,
1668                                     apr_pool_t *pool)
1669{
1670  node_revision_t *node_rev;
1671  svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1672  const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1673
1674  SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1675  if (has_mergeinfo)
1676    *has_mergeinfo = node_rev->has_mergeinfo;
1677  if (count)
1678    *count = node_rev->mergeinfo_count;
1679  return SVN_NO_ERROR;
1680}
1681
1682
1683svn_error_t *
1684svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
1685                                   svn_boolean_t has_mergeinfo,
1686                                   svn_boolean_t *had_mergeinfo,
1687                                   const char *txn_id,
1688                                   trail_t *trail,
1689                                   apr_pool_t *pool)
1690{
1691  node_revision_t *node_rev;
1692  svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1693  const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1694
1695  SVN_ERR(svn_fs_base__test_required_feature_format
1696          (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1697
1698  if (! svn_fs_base__dag_check_mutable(node, txn_id))
1699    return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1700                             _("Attempted merge tracking info change on "
1701                               "immutable node"));
1702
1703  SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1704  *had_mergeinfo = node_rev->has_mergeinfo;
1705
1706  /* Are we changing the node? */
1707  if ((! has_mergeinfo) != (! *had_mergeinfo))
1708    {
1709      /* Note the new has-mergeinfo state. */
1710      node_rev->has_mergeinfo = has_mergeinfo;
1711
1712      /* Increment or decrement the mergeinfo count as necessary. */
1713      if (has_mergeinfo)
1714        node_rev->mergeinfo_count++;
1715      else
1716        node_rev->mergeinfo_count--;
1717
1718      SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
1719    }
1720  return SVN_NO_ERROR;
1721}
1722
1723
1724svn_error_t *
1725svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
1726                                        apr_int64_t count_delta,
1727                                        const char *txn_id,
1728                                        trail_t *trail,
1729                                        apr_pool_t *pool)
1730{
1731  node_revision_t *node_rev;
1732  svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
1733  const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
1734
1735  SVN_ERR(svn_fs_base__test_required_feature_format
1736          (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
1737
1738  if (! svn_fs_base__dag_check_mutable(node, txn_id))
1739    return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
1740                             _("Attempted mergeinfo count change on "
1741                               "immutable node"));
1742
1743  if (count_delta == 0)
1744    return SVN_NO_ERROR;
1745
1746  SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
1747  node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
1748  if ((node_rev->mergeinfo_count < 0)
1749      || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
1750    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1751                             apr_psprintf(pool,
1752                                          _("Invalid value (%%%s) for node "
1753                                            "revision mergeinfo count"),
1754                                          APR_INT64_T_FMT),
1755                             node_rev->mergeinfo_count);
1756
1757  return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
1758}
1759