]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_fs_base / bdb / strings-table.c
1 /* strings-table.c : operations on the `strings' 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 #include "svn_fs.h"
25 #include "svn_pools.h"
26 #include "../fs.h"
27 #include "../err.h"
28 #include "dbt.h"
29 #include "../trail.h"
30 #include "../key-gen.h"
31 #include "../../libsvn_fs/fs-loader.h"
32 #include "bdb-err.h"
33 #include "strings-table.h"
34
35 #include "svn_private_config.h"
36
37 \f
38 /*** Creating and opening the strings table. ***/
39
40 int
41 svn_fs_bdb__open_strings_table(DB **strings_p,
42                                DB_ENV *env,
43                                svn_boolean_t create)
44 {
45   const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
46   DB *strings;
47
48   BDB_ERR(svn_fs_bdb__check_version());
49   BDB_ERR(db_create(&strings, env, 0));
50
51   /* Enable duplicate keys. This allows the data to be spread out across
52      multiple records. Note: this must occur before ->open().  */
53   BDB_ERR(strings->set_flags(strings, DB_DUP));
54
55   BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL),
56                           "strings", 0, DB_BTREE,
57                           open_flags, 0666));
58
59   if (create)
60     {
61       DBT key, value;
62
63       /* Create the `next-key' table entry.  */
64       BDB_ERR(strings->put
65               (strings, 0,
66                svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
67                svn_fs_base__str_to_dbt(&value, "0"), 0));
68     }
69
70   *strings_p = strings;
71   return 0;
72 }
73
74
75 \f
76 /*** Storing and retrieving strings.  ***/
77
78 /* Allocate *CURSOR and advance it to first row in the set of rows
79    whose key is defined by QUERY.  Set *LENGTH to the size of that
80    first row.  */
81 static svn_error_t *
82 locate_key(apr_size_t *length,
83            DBC **cursor,
84            DBT *query,
85            svn_fs_t *fs,
86            trail_t *trail,
87            apr_pool_t *pool)
88 {
89   base_fs_data_t *bfd = fs->fsap_data;
90   int db_err;
91   DBT result;
92
93   svn_fs_base__trail_debug(trail, "strings", "cursor");
94   SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
95                    bfd->strings->cursor(bfd->strings, trail->db_txn,
96                                         cursor, 0)));
97
98   /* Set up the DBT for reading the length of the record. */
99   svn_fs_base__clear_dbt(&result);
100   result.ulen = 0;
101   result.flags |= DB_DBT_USERMEM;
102
103   /* Advance the cursor to the key that we're looking for. */
104   db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET);
105
106   /* We don't need to svn_fs_base__track_dbt() the result, because nothing
107      was allocated in it. */
108
109   /* If there's no such node, return an appropriately specific error.  */
110   if (db_err == DB_NOTFOUND)
111     {
112       svn_bdb_dbc_close(*cursor);
113       return svn_error_createf
114         (SVN_ERR_FS_NO_SUCH_STRING, 0,
115          "No such string '%s'", (const char *)query->data);
116     }
117   if (db_err)
118     {
119       DBT rerun;
120
121       if (db_err != SVN_BDB_DB_BUFFER_SMALL)
122         {
123           svn_bdb_dbc_close(*cursor);
124           return BDB_WRAP(fs, N_("moving cursor"), db_err);
125         }
126
127       /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
128          zero length buf), so we need to re-run the operation to make
129          it happen. */
130       svn_fs_base__clear_dbt(&rerun);
131       rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
132       db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET);
133       if (db_err)
134         {
135           svn_bdb_dbc_close(*cursor);
136           return BDB_WRAP(fs, N_("rerunning cursor move"), db_err);
137         }
138     }
139
140   /* ### this cast might not be safe? */
141   *length = (apr_size_t) result.size;
142
143   return SVN_NO_ERROR;
144 }
145
146
147 /* Advance CURSOR by a single row in the set of rows whose keys match
148    CURSOR's current location.  Set *LENGTH to the size of that next
149    row.  If any error occurs, CURSOR will be destroyed.  */
150 static int
151 get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
152 {
153   DBT result;
154   int db_err;
155
156   /* Set up the DBT for reading the length of the record. */
157   svn_fs_base__clear_dbt(&result);
158   result.ulen = 0;
159   result.flags |= DB_DBT_USERMEM;
160
161   /* Note: this may change the QUERY DBT, but that's okay: we're going
162      to be sticking with the same key anyways.  */
163   db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP);
164
165   /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
166   if (db_err)
167     {
168       DBT rerun;
169
170       if (db_err != SVN_BDB_DB_BUFFER_SMALL)
171         {
172           svn_bdb_dbc_close(cursor);
173           return db_err;
174         }
175
176       /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
177          zero length buf), so we need to re-run the operation to make
178          it happen. */
179       svn_fs_base__clear_dbt(&rerun);
180       rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
181       db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP);
182       if (db_err)
183         svn_bdb_dbc_close(cursor);
184     }
185
186   /* ### this cast might not be safe? */
187   *length = (apr_size_t) result.size;
188   return db_err;
189 }
190
191
192 svn_error_t *
193 svn_fs_bdb__string_read(svn_fs_t *fs,
194                         const char *key,
195                         char *buf,
196                         svn_filesize_t offset,
197                         apr_size_t *len,
198                         trail_t *trail,
199                         apr_pool_t *pool)
200 {
201   int db_err;
202   DBT query, result;
203   DBC *cursor;
204   apr_size_t length, bytes_read = 0;
205
206   svn_fs_base__str_to_dbt(&query, key);
207
208   SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
209
210   /* Seek through the records for this key, trying to find the record that
211      includes OFFSET. Note that we don't require reading from more than
212      one record since we're allowed to return partial reads.  */
213   while (length <= offset)
214     {
215       offset -= length;
216
217       /* Remember, if any error happens, our cursor has been closed
218          for us. */
219       db_err = get_next_length(&length, cursor, &query);
220
221       /* No more records? They tried to read past the end. */
222       if (db_err == DB_NOTFOUND)
223         {
224           *len = 0;
225           return SVN_NO_ERROR;
226         }
227       if (db_err)
228         return BDB_WRAP(fs, N_("reading string"), db_err);
229     }
230
231   /* The current record contains OFFSET. Fetch the contents now. Note that
232      OFFSET has been moved to be relative to this record. The length could
233      quite easily extend past this record, so we use DB_DBT_PARTIAL and
234      read successive records until we've filled the request.  */
235   while (1)
236     {
237       svn_fs_base__clear_dbt(&result);
238       result.data = buf + bytes_read;
239       result.ulen = *len - bytes_read;
240       result.doff = (u_int32_t)offset;
241       result.dlen = *len - bytes_read;
242       result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
243       db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT);
244       if (db_err)
245         {
246           svn_bdb_dbc_close(cursor);
247           return BDB_WRAP(fs, N_("reading string"), db_err);
248         }
249
250       bytes_read += result.size;
251       if (bytes_read == *len)
252         {
253           /* Done with the cursor. */
254           SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"),
255                            svn_bdb_dbc_close(cursor)));
256           break;
257         }
258
259       /* Remember, if any error happens, our cursor has been closed
260          for us. */
261       db_err = get_next_length(&length, cursor, &query);
262       if (db_err == DB_NOTFOUND)
263         break;
264       if (db_err)
265         return BDB_WRAP(fs, N_("reading string"), db_err);
266
267       /* We'll be reading from the beginning of the next record */
268       offset = 0;
269     }
270
271   *len = bytes_read;
272   return SVN_NO_ERROR;
273 }
274
275
276 /* Get the current 'next-key' value and bump the record. */
277 static svn_error_t *
278 get_key_and_bump(svn_fs_t *fs,
279                  const char **key,
280                  trail_t *trail,
281                  apr_pool_t *pool)
282 {
283   base_fs_data_t *bfd = fs->fsap_data;
284   DBC *cursor;
285   char next_key[MAX_KEY_SIZE];
286   apr_size_t key_len;
287   int db_err;
288   DBT query;
289   DBT result;
290
291   /* ### todo: see issue #409 for why bumping the key as part of this
292      trail is problematic. */
293
294   /* Open a cursor and move it to the 'next-key' value. We can then fetch
295      the contents and use the cursor to overwrite those contents. Since
296      this database allows duplicates, we can't do an arbitrary 'put' to
297      write the new value -- that would append, not overwrite.  */
298
299   svn_fs_base__trail_debug(trail, "strings", "cursor");
300   SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
301                    bfd->strings->cursor(bfd->strings, trail->db_txn,
302                                         &cursor, 0)));
303
304   /* Advance the cursor to 'next-key' and read it. */
305
306   db_err = svn_bdb_dbc_get(cursor,
307                            svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
308                            svn_fs_base__result_dbt(&result),
309                            DB_SET);
310   if (db_err)
311     {
312       svn_bdb_dbc_close(cursor);
313       return BDB_WRAP(fs, N_("getting next-key value"), db_err);
314     }
315
316   svn_fs_base__track_dbt(&result, pool);
317   *key = apr_pstrmemdup(pool, result.data, result.size);
318
319   /* Bump to future key. */
320   key_len = result.size;
321   svn_fs_base__next_key(result.data, &key_len, next_key);
322
323   /* Shove the new key back into the database, at the cursor position. */
324   db_err = svn_bdb_dbc_put(cursor, &query,
325                            svn_fs_base__str_to_dbt(&result, next_key),
326                            DB_CURRENT);
327   if (db_err)
328     {
329       svn_bdb_dbc_close(cursor); /* ignore the error, the original is
330                                     more important. */
331       return BDB_WRAP(fs, N_("bumping next string key"), db_err);
332     }
333
334   return BDB_WRAP(fs, N_("closing string-reading cursor"),
335                   svn_bdb_dbc_close(cursor));
336 }
337
338 svn_error_t *
339 svn_fs_bdb__string_append(svn_fs_t *fs,
340                           const char **key,
341                           apr_size_t len,
342                           const char *buf,
343                           trail_t *trail,
344                           apr_pool_t *pool)
345 {
346   base_fs_data_t *bfd = fs->fsap_data;
347   DBT query, result;
348
349   /* If the passed-in key is NULL, we graciously generate a new string
350      using the value of the `next-key' record in the strings table. */
351   if (*key == NULL)
352     {
353       SVN_ERR(get_key_and_bump(fs, key, trail, pool));
354     }
355
356   /* Store a new record into the database. */
357   svn_fs_base__trail_debug(trail, "strings", "put");
358   return BDB_WRAP(fs, N_("appending string"),
359                   bfd->strings->put
360                   (bfd->strings, trail->db_txn,
361                    svn_fs_base__str_to_dbt(&query, *key),
362                    svn_fs_base__set_dbt(&result, buf, len),
363                    0));
364 }
365
366
367 svn_error_t *
368 svn_fs_bdb__string_clear(svn_fs_t *fs,
369                          const char *key,
370                          trail_t *trail,
371                          apr_pool_t *pool)
372 {
373   base_fs_data_t *bfd = fs->fsap_data;
374   int db_err;
375   DBT query, result;
376
377   svn_fs_base__str_to_dbt(&query, key);
378
379   /* Torch the prior contents */
380   svn_fs_base__trail_debug(trail, "strings", "del");
381   db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0);
382
383   /* If there's no such node, return an appropriately specific error.  */
384   if (db_err == DB_NOTFOUND)
385     return svn_error_createf
386       (SVN_ERR_FS_NO_SUCH_STRING, 0,
387        "No such string '%s'", key);
388
389   /* Handle any other error conditions.  */
390   SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err));
391
392   /* Shove empty data back in for this key. */
393   svn_fs_base__clear_dbt(&result);
394   result.data = 0;
395   result.size = 0;
396   result.flags |= DB_DBT_USERMEM;
397
398   svn_fs_base__trail_debug(trail, "strings", "put");
399   return BDB_WRAP(fs, N_("storing empty contents"),
400                   bfd->strings->put(bfd->strings, trail->db_txn,
401                                     &query, &result, 0));
402 }
403
404
405 svn_error_t *
406 svn_fs_bdb__string_size(svn_filesize_t *size,
407                         svn_fs_t *fs,
408                         const char *key,
409                         trail_t *trail,
410                         apr_pool_t *pool)
411 {
412   int db_err;
413   DBT query;
414   DBC *cursor;
415   apr_size_t length;
416   svn_filesize_t total;
417
418   svn_fs_base__str_to_dbt(&query, key);
419
420   SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
421
422   total = length;
423   while (1)
424     {
425       /* Remember, if any error happens, our cursor has been closed
426          for us. */
427       db_err = get_next_length(&length, cursor, &query);
428
429       /* No more records? Then return the total length. */
430       if (db_err == DB_NOTFOUND)
431         {
432           *size = total;
433           return SVN_NO_ERROR;
434         }
435       if (db_err)
436         return BDB_WRAP(fs, N_("fetching string length"), db_err);
437
438       total += length;
439     }
440
441   /* NOTREACHED */
442 }
443
444
445 svn_error_t *
446 svn_fs_bdb__string_delete(svn_fs_t *fs,
447                           const char *key,
448                           trail_t *trail,
449                           apr_pool_t *pool)
450 {
451   base_fs_data_t *bfd = fs->fsap_data;
452   int db_err;
453   DBT query;
454
455   svn_fs_base__trail_debug(trail, "strings", "del");
456   db_err = bfd->strings->del(bfd->strings, trail->db_txn,
457                              svn_fs_base__str_to_dbt(&query, key), 0);
458
459   /* If there's no such node, return an appropriately specific error.  */
460   if (db_err == DB_NOTFOUND)
461     return svn_error_createf
462       (SVN_ERR_FS_NO_SUCH_STRING, 0,
463        "No such string '%s'", key);
464
465   /* Handle any other error conditions.  */
466   return BDB_WRAP(fs, N_("deleting string"), db_err);
467 }
468
469
470 svn_error_t *
471 svn_fs_bdb__string_copy(svn_fs_t *fs,
472                         const char **new_key,
473                         const char *key,
474                         trail_t *trail,
475                         apr_pool_t *pool)
476 {
477   base_fs_data_t *bfd = fs->fsap_data;
478   DBT query;
479   DBT result;
480   DBT copykey;
481   DBC *cursor;
482   int db_err;
483
484   /* Copy off the old key in case the caller is sharing storage
485      between the old and new keys. */
486   const char *old_key = apr_pstrdup(pool, key);
487
488   SVN_ERR(get_key_and_bump(fs, new_key, trail, pool));
489
490   svn_fs_base__trail_debug(trail, "strings", "cursor");
491   SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
492                    bfd->strings->cursor(bfd->strings, trail->db_txn,
493                                         &cursor, 0)));
494
495   svn_fs_base__str_to_dbt(&query, old_key);
496   svn_fs_base__str_to_dbt(&copykey, *new_key);
497
498   svn_fs_base__clear_dbt(&result);
499
500   /* Move to the first record and fetch its data (under BDB's mem mgmt). */
501   db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
502   if (db_err)
503     {
504       svn_bdb_dbc_close(cursor);
505       return BDB_WRAP(fs, N_("getting next-key value"), db_err);
506     }
507
508   while (1)
509     {
510       /* ### can we pass a BDB-provided buffer to another BDB function?
511          ### they are supposed to have a duration up to certain points
512          ### of calling back into BDB, but I'm not sure what the exact
513          ### rules are. it is definitely nicer to use BDB buffers here
514          ### to simplify things and reduce copies, but... hrm.
515       */
516
517       /* Write the data to the database */
518       svn_fs_base__trail_debug(trail, "strings", "put");
519       db_err = bfd->strings->put(bfd->strings, trail->db_txn,
520                                  &copykey, &result, 0);
521       if (db_err)
522         {
523           svn_bdb_dbc_close(cursor);
524           return BDB_WRAP(fs, N_("writing copied data"), db_err);
525         }
526
527       /* Read the next chunk. Terminate loop if we're done. */
528       svn_fs_base__clear_dbt(&result);
529       db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
530       if (db_err == DB_NOTFOUND)
531         break;
532       if (db_err)
533         {
534           svn_bdb_dbc_close(cursor);
535           return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err);
536         }
537     }
538
539   return BDB_WRAP(fs, N_("closing string-reading cursor"),
540                   svn_bdb_dbc_close(cursor));
541 }