]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_base / bdb / changes-table.c
1 /* changes-table.c : operations on the `changes' table
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 "bdb_compat.h"
24
25 #include <apr_hash.h>
26 #include <apr_tables.h>
27
28 #include "svn_hash.h"
29 #include "svn_fs.h"
30 #include "svn_pools.h"
31 #include "svn_path.h"
32 #include "../fs.h"
33 #include "../err.h"
34 #include "../trail.h"
35 #include "../id.h"
36 #include "../util/fs_skels.h"
37 #include "../../libsvn_fs/fs-loader.h"
38 #include "bdb-err.h"
39 #include "dbt.h"
40 #include "changes-table.h"
41
42 #include "private/svn_fs_util.h"
43 #include "private/svn_fspath.h"
44 #include "svn_private_config.h"
45
46 \f
47 /*** Creating and opening the changes table. ***/
48
49 int
50 svn_fs_bdb__open_changes_table(DB **changes_p,
51                                DB_ENV *env,
52                                svn_boolean_t create)
53 {
54   const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
55   DB *changes;
56
57   BDB_ERR(svn_fs_bdb__check_version());
58   BDB_ERR(db_create(&changes, env, 0));
59
60   /* Enable duplicate keys. This allows us to store the changes
61      one-per-row.  Note: this must occur before ->open().  */
62   BDB_ERR(changes->set_flags(changes, DB_DUP));
63
64   BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL),
65                           "changes", 0, DB_BTREE,
66                           open_flags, 0666));
67
68   *changes_p = changes;
69   return 0;
70 }
71
72
73 \f
74 /*** Storing and retrieving changes.  ***/
75
76 svn_error_t *
77 svn_fs_bdb__changes_add(svn_fs_t *fs,
78                         const char *key,
79                         change_t *change,
80                         trail_t *trail,
81                         apr_pool_t *pool)
82 {
83   base_fs_data_t *bfd = fs->fsap_data;
84   DBT query, value;
85   svn_skel_t *skel;
86
87   /* Convert native type to skel. */
88   SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool));
89
90   /* Store a new record into the database. */
91   svn_fs_base__str_to_dbt(&query, key);
92   svn_fs_base__skel_to_dbt(&value, skel, pool);
93   svn_fs_base__trail_debug(trail, "changes", "put");
94   return BDB_WRAP(fs, N_("creating change"),
95                   bfd->changes->put(bfd->changes, trail->db_txn,
96                                     &query, &value, 0));
97 }
98
99
100 svn_error_t *
101 svn_fs_bdb__changes_delete(svn_fs_t *fs,
102                            const char *key,
103                            trail_t *trail,
104                            apr_pool_t *pool)
105 {
106   int db_err;
107   DBT query;
108   base_fs_data_t *bfd = fs->fsap_data;
109
110   svn_fs_base__trail_debug(trail, "changes", "del");
111   db_err = bfd->changes->del(bfd->changes, trail->db_txn,
112                              svn_fs_base__str_to_dbt(&query, key), 0);
113
114   /* If there're no changes for KEY, that is acceptable.  Any other
115      error should be propagated to the caller, though.  */
116   if ((db_err) && (db_err != DB_NOTFOUND))
117     {
118       SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err));
119     }
120
121   return SVN_NO_ERROR;
122 }
123
124
125 /* Merge the internal-use-only CHANGE into a hash of public-FS
126    svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
127    single succinct change per path. */
128 static svn_error_t *
129 fold_change(apr_hash_t *changes,
130             const change_t *change)
131 {
132   apr_pool_t *pool = apr_hash_pool_get(changes);
133   svn_fs_path_change2_t *old_change, *new_change;
134   const char *path;
135
136   if ((old_change = svn_hash_gets(changes, change->path)))
137     {
138       /* This path already exists in the hash, so we have to merge
139          this change into the already existing one. */
140
141       /* Since the path already exists in the hash, we don't have to
142          dup the allocation for the path itself. */
143       path = change->path;
144
145       /* Sanity check:  only allow NULL node revision ID in the
146          `reset' case. */
147       if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
148         return svn_error_create
149           (SVN_ERR_FS_CORRUPT, NULL,
150            _("Missing required node revision ID"));
151
152       /* Sanity check:  we should be talking about the same node
153          revision ID as our last change except where the last change
154          was a deletion. */
155       if (change->noderev_id
156           && (! svn_fs_base__id_eq(old_change->node_rev_id,
157                                    change->noderev_id))
158           && (old_change->change_kind != svn_fs_path_change_delete))
159         return svn_error_create
160           (SVN_ERR_FS_CORRUPT, NULL,
161            _("Invalid change ordering: new node revision ID without delete"));
162
163       /* Sanity check: an add, replacement, or reset must be the first
164          thing to follow a deletion. */
165       if ((old_change->change_kind == svn_fs_path_change_delete)
166           && (! ((change->kind == svn_fs_path_change_replace)
167                  || (change->kind == svn_fs_path_change_reset)
168                  || (change->kind == svn_fs_path_change_add))))
169         return svn_error_create
170           (SVN_ERR_FS_CORRUPT, NULL,
171            _("Invalid change ordering: non-add change on deleted path"));
172
173       /* Sanity check: an add can't follow anything except
174          a delete or reset.  */
175       if ((change->kind == svn_fs_path_change_add)
176           && (old_change->change_kind != svn_fs_path_change_delete)
177           && (old_change->change_kind != svn_fs_path_change_reset))
178         return svn_error_create
179           (SVN_ERR_FS_CORRUPT, NULL,
180            _("Invalid change ordering: add change on preexisting path"));
181
182       /* Now, merge that change in. */
183       switch (change->kind)
184         {
185         case svn_fs_path_change_reset:
186           /* A reset here will simply remove the path change from the
187              hash. */
188           old_change = NULL;
189           break;
190
191         case svn_fs_path_change_delete:
192           if (old_change->change_kind == svn_fs_path_change_add)
193             {
194               /* If the path was introduced in this transaction via an
195                  add, and we are deleting it, just remove the path
196                  altogether. */
197               old_change = NULL;
198             }
199           else
200             {
201               /* A deletion overrules all previous changes. */
202               old_change->change_kind = svn_fs_path_change_delete;
203               old_change->text_mod = change->text_mod;
204               old_change->prop_mod = change->prop_mod;
205             }
206           break;
207
208         case svn_fs_path_change_add:
209         case svn_fs_path_change_replace:
210           /* An add at this point must be following a previous delete,
211              so treat it just like a replace. */
212           old_change->change_kind = svn_fs_path_change_replace;
213           old_change->node_rev_id = svn_fs_base__id_copy(change->noderev_id,
214                                                          pool);
215           old_change->text_mod = change->text_mod;
216           old_change->prop_mod = change->prop_mod;
217           break;
218
219         case svn_fs_path_change_modify:
220         default:
221           if (change->text_mod)
222             old_change->text_mod = TRUE;
223           if (change->prop_mod)
224             old_change->prop_mod = TRUE;
225           break;
226         }
227
228       /* Point our new_change to our (possibly modified) old_change. */
229       new_change = old_change;
230     }
231   else
232     {
233       /* This change is new to the hash, so make a new public change
234          structure from the internal one (in the hash's pool), and dup
235          the path into the hash's pool, too. */
236       new_change = svn_fs__path_change_create_internal(
237                        svn_fs_base__id_copy(change->noderev_id, pool),
238                        change->kind,
239                        pool);
240       new_change->text_mod = change->text_mod;
241       new_change->prop_mod = change->prop_mod;
242       new_change->node_kind = svn_node_unknown;
243       new_change->copyfrom_known = FALSE;
244       path = apr_pstrdup(pool, change->path);
245     }
246
247   /* Add (or update) this path. */
248   svn_hash_sets(changes, path, new_change);
249
250   return SVN_NO_ERROR;
251 }
252
253
254 svn_error_t *
255 svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
256                           svn_fs_t *fs,
257                           const char *key,
258                           trail_t *trail,
259                           apr_pool_t *pool)
260 {
261   base_fs_data_t *bfd = fs->fsap_data;
262   DBC *cursor;
263   DBT query, result;
264   int db_err = 0, db_c_err = 0;
265   svn_error_t *err = SVN_NO_ERROR;
266   apr_hash_t *changes = apr_hash_make(pool);
267   apr_pool_t *subpool = svn_pool_create(pool);
268
269   /* Get a cursor on the first record matching KEY, and then loop over
270      the records, adding them to the return array. */
271   svn_fs_base__trail_debug(trail, "changes", "cursor");
272   SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
273                    bfd->changes->cursor(bfd->changes, trail->db_txn,
274                                         &cursor, 0)));
275
276   /* Advance the cursor to the key that we're looking for. */
277   svn_fs_base__str_to_dbt(&query, key);
278   svn_fs_base__result_dbt(&result);
279   db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
280   if (! db_err)
281     svn_fs_base__track_dbt(&result, pool);
282
283   while (! db_err)
284     {
285       change_t *change;
286       svn_skel_t *result_skel;
287
288       /* Clear the per-iteration subpool. */
289       svn_pool_clear(subpool);
290
291       /* RESULT now contains a change record associated with KEY.  We
292          need to parse that skel into an change_t structure ...  */
293       result_skel = svn_skel__parse(result.data, result.size, subpool);
294       if (! result_skel)
295         {
296           err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
297                                   _("Error reading changes for key '%s'"),
298                                   key);
299           goto cleanup;
300         }
301       err = svn_fs_base__parse_change_skel(&change, result_skel, subpool);
302       if (err)
303         goto cleanup;
304
305       /* ... and merge it with our return hash.  */
306       err = fold_change(changes, change);
307       if (err)
308         goto cleanup;
309
310       /* Now, if our change was a deletion or replacement, we have to
311          blow away any changes thus far on paths that are (or, were)
312          children of this path.
313          ### i won't bother with another iteration pool here -- at
314              most we talking about a few extra dups of paths into what
315              is already a temporary subpool.
316       */
317       if ((change->kind == svn_fs_path_change_delete)
318           || (change->kind == svn_fs_path_change_replace))
319         {
320           apr_hash_index_t *hi;
321
322           for (hi = apr_hash_first(subpool, changes);
323                hi;
324                hi = apr_hash_next(hi))
325             {
326               /* KEY is the path. */
327               const void *hashkey;
328               apr_ssize_t klen;
329               const char *child_relpath;
330
331               apr_hash_this(hi, &hashkey, &klen, NULL);
332
333               /* If we come across our own path, ignore it.
334                  If we come across a child of our path, remove it. */
335               child_relpath = svn_fspath__skip_ancestor(change->path, hashkey);
336               if (child_relpath && *child_relpath)
337                 apr_hash_set(changes, hashkey, klen, NULL);
338             }
339         }
340
341       /* Advance the cursor to the next record with this same KEY, and
342          fetch that record. */
343       svn_fs_base__result_dbt(&result);
344       db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
345       if (! db_err)
346         svn_fs_base__track_dbt(&result, pool);
347     }
348
349   /* Destroy the per-iteration subpool. */
350   svn_pool_destroy(subpool);
351
352   /* If there are no (more) change records for this KEY, we're
353      finished.  Just return the (possibly empty) array.  Any other
354      error, however, needs to get handled appropriately.  */
355   if (db_err && (db_err != DB_NOTFOUND))
356     err = BDB_WRAP(fs, N_("fetching changes"), db_err);
357
358  cleanup:
359   /* Close the cursor. */
360   db_c_err = svn_bdb_dbc_close(cursor);
361
362   /* If we had an error prior to closing the cursor, return the error. */
363   if (err)
364     return svn_error_trace(err);
365
366   /* If our only error thus far was when we closed the cursor, return
367      that error. */
368   if (db_c_err)
369     SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
370
371   /* Finally, set our return variable and get outta here. */
372   *changes_p = changes;
373   return SVN_NO_ERROR;
374 }
375
376
377 svn_error_t *
378 svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
379                               svn_fs_t *fs,
380                               const char *key,
381                               trail_t *trail,
382                               apr_pool_t *pool)
383 {
384   base_fs_data_t *bfd = fs->fsap_data;
385   DBC *cursor;
386   DBT query, result;
387   int db_err = 0, db_c_err = 0;
388   svn_error_t *err = SVN_NO_ERROR;
389   change_t *change;
390   apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change));
391
392   /* Get a cursor on the first record matching KEY, and then loop over
393      the records, adding them to the return array. */
394   svn_fs_base__trail_debug(trail, "changes", "cursor");
395   SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
396                    bfd->changes->cursor(bfd->changes, trail->db_txn,
397                                         &cursor, 0)));
398
399   /* Advance the cursor to the key that we're looking for. */
400   svn_fs_base__str_to_dbt(&query, key);
401   svn_fs_base__result_dbt(&result);
402   db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
403   if (! db_err)
404     svn_fs_base__track_dbt(&result, pool);
405
406   while (! db_err)
407     {
408       svn_skel_t *result_skel;
409
410       /* RESULT now contains a change record associated with KEY.  We
411          need to parse that skel into an change_t structure ...  */
412       result_skel = svn_skel__parse(result.data, result.size, pool);
413       if (! result_skel)
414         {
415           err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
416                                   _("Error reading changes for key '%s'"),
417                                   key);
418           goto cleanup;
419         }
420       err = svn_fs_base__parse_change_skel(&change, result_skel, pool);
421       if (err)
422         goto cleanup;
423
424       /* ... and add it to our return array.  */
425       APR_ARRAY_PUSH(changes, change_t *) = change;
426
427       /* Advance the cursor to the next record with this same KEY, and
428          fetch that record. */
429       svn_fs_base__result_dbt(&result);
430       db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
431       if (! db_err)
432         svn_fs_base__track_dbt(&result, pool);
433     }
434
435   /* If there are no (more) change records for this KEY, we're
436      finished.  Just return the (possibly empty) array.  Any other
437      error, however, needs to get handled appropriately.  */
438   if (db_err && (db_err != DB_NOTFOUND))
439     err = BDB_WRAP(fs, N_("fetching changes"), db_err);
440
441  cleanup:
442   /* Close the cursor. */
443   db_c_err = svn_bdb_dbc_close(cursor);
444
445   /* If we had an error prior to closing the cursor, return the error. */
446   if (err)
447     return svn_error_trace(err);
448
449   /* If our only error thus far was when we closed the cursor, return
450      that error. */
451   if (db_c_err)
452     SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
453
454   /* Finally, set our return variable and get outta here. */
455   *changes_p = changes;
456   return SVN_NO_ERROR;
457 }