2 * hash.c : dumping and reading hash tables to/from files.
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
24 #include <apr_version.h>
25 #include <apr_pools.h>
27 #include <apr_file_io.h>
28 #include "svn_types.h"
29 #include "svn_string.h"
30 #include "svn_error.h"
32 #include "svn_sorts.h"
34 #include "svn_pools.h"
35 #include "private/svn_dep_compat.h"
40 * The format of a dumped hash table is:
43 * name (a string of <nlength> bytes, followed by a newline)
45 * val (a string of <vlength> bytes, followed by a newline)
50 * (Yes, there is a newline after END.)
61 * A forthright entrance, yet coquettish on the tongue, its deceptively
62 * fruity exterior hides the warm mahagony undercurrent that is the
63 * hallmark of Chateau Fraisant-Pitre. Connoisseurs of the region will
64 * be pleased to note the familiar, subtle hints of mulberries and
65 * carburator fluid. Its confident finish is marred only by a barely
66 * detectable suggestion of rancid squid ink.
78 /*** Dumping and loading hash files. */
80 /* Implements svn_hash_read2 and svn_hash_read_incremental. */
82 hash_read(apr_hash_t
*hash
, svn_stream_t
*stream
, const char *terminator
,
83 svn_boolean_t incremental
, apr_pool_t
*pool
)
87 apr_size_t len
, keylen
, vallen
;
88 char c
, *end
, *keybuf
, *valbuf
;
92 /* Read a key length line. Might be END, though. */
93 SVN_ERR(svn_stream_readline(stream
, &buf
, "\n", &eof
, pool
));
95 /* Check for the end of the hash. */
96 if ((!terminator
&& eof
&& buf
->len
== 0)
97 || (terminator
&& (strcmp(buf
->data
, terminator
) == 0)))
100 /* Check for unexpected end of stream */
102 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
104 if ((buf
->len
>= 3) && (buf
->data
[0] == 'K') && (buf
->data
[1] == ' '))
106 /* Get the length of the key */
107 keylen
= (size_t) strtoul(buf
->data
+ 2, &end
, 10);
108 if (keylen
== (size_t) ULONG_MAX
|| *end
!= '\0')
109 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
111 /* Now read that much into a buffer. */
112 keybuf
= apr_palloc(pool
, keylen
+ 1);
113 SVN_ERR(svn_stream_read(stream
, keybuf
, &keylen
));
114 keybuf
[keylen
] = '\0';
116 /* Suck up extra newline after key data */
118 SVN_ERR(svn_stream_read(stream
, &c
, &len
));
120 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
122 /* Read a val length line */
123 SVN_ERR(svn_stream_readline(stream
, &buf
, "\n", &eof
, pool
));
125 if ((buf
->data
[0] == 'V') && (buf
->data
[1] == ' '))
127 vallen
= (size_t) strtoul(buf
->data
+ 2, &end
, 10);
128 if (vallen
== (size_t) ULONG_MAX
|| *end
!= '\0')
129 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
131 valbuf
= apr_palloc(pool
, vallen
+ 1);
132 SVN_ERR(svn_stream_read(stream
, valbuf
, &vallen
));
133 valbuf
[vallen
] = '\0';
135 /* Suck up extra newline after val data */
137 SVN_ERR(svn_stream_read(stream
, &c
, &len
));
139 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
141 /* Add a new hash entry. */
142 apr_hash_set(hash
, keybuf
, keylen
,
143 svn_string_ncreate(valbuf
, vallen
, pool
));
146 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
148 else if (incremental
&& (buf
->len
>= 3)
149 && (buf
->data
[0] == 'D') && (buf
->data
[1] == ' '))
151 /* Get the length of the key */
152 keylen
= (size_t) strtoul(buf
->data
+ 2, &end
, 10);
153 if (keylen
== (size_t) ULONG_MAX
|| *end
!= '\0')
154 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
156 /* Now read that much into a buffer. */
157 keybuf
= apr_palloc(pool
, keylen
+ 1);
158 SVN_ERR(svn_stream_read(stream
, keybuf
, &keylen
));
159 keybuf
[keylen
] = '\0';
161 /* Suck up extra newline after key data */
163 SVN_ERR(svn_stream_read(stream
, &c
, &len
));
165 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
167 /* Remove this hash entry. */
168 apr_hash_set(hash
, keybuf
, keylen
, NULL
);
171 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
176 /* Implements svn_hash_write2 and svn_hash_write_incremental. */
178 hash_write(apr_hash_t
*hash
, apr_hash_t
*oldhash
, svn_stream_t
*stream
,
179 const char *terminator
, apr_pool_t
*pool
)
183 apr_array_header_t
*list
;
186 subpool
= svn_pool_create(pool
);
188 list
= svn_sort__hash(hash
, svn_sort_compare_items_lexically
, pool
);
189 for (i
= 0; i
< list
->nelts
; i
++)
191 svn_sort__item_t
*item
= &APR_ARRAY_IDX(list
, i
, svn_sort__item_t
);
192 svn_string_t
*valstr
= item
->value
;
194 svn_pool_clear(subpool
);
196 /* Don't output entries equal to the ones in oldhash, if present. */
199 svn_string_t
*oldstr
= apr_hash_get(oldhash
, item
->key
, item
->klen
);
201 if (oldstr
&& svn_string_compare(valstr
, oldstr
))
206 SVN_ERR(svn_stream_printf(stream
, subpool
,
207 "K %" APR_SSIZE_T_FMT
"\n%s\n"
208 "V %" APR_SIZE_T_FMT
"\n",
209 item
->klen
, (const char *) item
->key
,
212 SVN_ERR(svn_stream_write(stream
, valstr
->data
, &len
));
213 SVN_ERR(svn_stream_printf(stream
, subpool
, "\n"));
218 /* Output a deletion entry for each property in oldhash but not hash. */
219 list
= svn_sort__hash(oldhash
, svn_sort_compare_items_lexically
,
221 for (i
= 0; i
< list
->nelts
; i
++)
223 svn_sort__item_t
*item
= &APR_ARRAY_IDX(list
, i
, svn_sort__item_t
);
225 svn_pool_clear(subpool
);
227 /* If it's not present in the new hash, write out a D entry. */
228 if (! apr_hash_get(hash
, item
->key
, item
->klen
))
229 SVN_ERR(svn_stream_printf(stream
, subpool
,
230 "D %" APR_SSIZE_T_FMT
"\n%s\n",
231 item
->klen
, (const char *) item
->key
));
236 SVN_ERR(svn_stream_printf(stream
, subpool
, "%s\n", terminator
));
238 svn_pool_destroy(subpool
);
243 svn_error_t
*svn_hash_read2(apr_hash_t
*hash
, svn_stream_t
*stream
,
244 const char *terminator
, apr_pool_t
*pool
)
246 return hash_read(hash
, stream
, terminator
, FALSE
, pool
);
250 svn_error_t
*svn_hash_read_incremental(apr_hash_t
*hash
,
251 svn_stream_t
*stream
,
252 const char *terminator
,
255 return hash_read(hash
, stream
, terminator
, TRUE
, pool
);
260 svn_hash_write2(apr_hash_t
*hash
, svn_stream_t
*stream
,
261 const char *terminator
, apr_pool_t
*pool
)
263 return hash_write(hash
, NULL
, stream
, terminator
, pool
);
268 svn_hash_write_incremental(apr_hash_t
*hash
, apr_hash_t
*oldhash
,
269 svn_stream_t
*stream
, const char *terminator
,
272 assert(oldhash
!= NULL
);
273 return hash_write(hash
, oldhash
, stream
, terminator
, pool
);
278 svn_hash_write(apr_hash_t
*hash
, apr_file_t
*destfile
, apr_pool_t
*pool
)
280 return hash_write(hash
, NULL
, svn_stream_from_aprfile(destfile
, pool
),
281 SVN_HASH_TERMINATOR
, pool
);
285 /* There are enough quirks in the deprecated svn_hash_read that we
286 should just preserve its implementation. */
288 svn_hash_read(apr_hash_t
*hash
,
293 char buf
[SVN_KEYLINE_MAXLEN
];
301 /* Read a key length line. Might be END, though. */
302 apr_size_t len
= sizeof(buf
);
304 err
= svn_io_read_length_line(srcfile
, buf
, &len
, pool
);
305 if (err
&& APR_STATUS_IS_EOF(err
->apr_err
) && first_time
)
307 /* We got an EOF on our very first attempt to read, which
308 means it's a zero-byte file. No problem, just go home. */
309 svn_error_clear(err
);
313 /* Any other circumstance is a genuine error. */
318 if (((len
== 3) && (buf
[0] == 'E') && (buf
[1] == 'N') && (buf
[2] == 'D'))
321 && (buf
[1] == 'R') /* We formerly used just "END" to */
322 && (buf
[2] == 'O') /* end a property hash, but later */
323 && (buf
[3] == 'P') /* we added "PROPS-END", so that */
324 && (buf
[4] == 'S') /* the fs dump format would be */
325 && (buf
[5] == '-') /* more human-readable. That's */
326 && (buf
[6] == 'E') /* why we accept either way here. */
330 /* We've reached the end of the dumped hash table, so leave. */
333 else if ((buf
[0] == 'K') && (buf
[1] == ' '))
335 /* Get the length of the key */
336 size_t keylen
= (size_t) atoi(buf
+ 2);
338 /* Now read that much into a buffer, + 1 byte for null terminator */
339 void *keybuf
= apr_palloc(pool
, keylen
+ 1);
340 SVN_ERR(svn_io_file_read_full(srcfile
,
341 keybuf
, keylen
, &num_read
, pool
));
342 ((char *) keybuf
)[keylen
] = '\0';
344 /* Suck up extra newline after key data */
345 SVN_ERR(svn_io_file_getc(&c
, srcfile
, pool
));
347 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
349 /* Read a val length line */
351 SVN_ERR(svn_io_read_length_line(srcfile
, buf
, &len
, pool
));
353 if ((buf
[0] == 'V') && (buf
[1] == ' '))
355 svn_string_t
*value
= apr_palloc(pool
, sizeof(*value
));
357 /* Get the length of the value */
358 apr_size_t vallen
= atoi(buf
+ 2);
360 /* Again, 1 extra byte for the null termination. */
361 void *valbuf
= apr_palloc(pool
, vallen
+ 1);
362 SVN_ERR(svn_io_file_read_full(srcfile
,
365 ((char *) valbuf
)[vallen
] = '\0';
367 /* Suck up extra newline after val data */
368 SVN_ERR(svn_io_file_getc(&c
, srcfile
, pool
));
370 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
372 value
->data
= valbuf
;
375 /* The Grand Moment: add a new hash entry! */
376 apr_hash_set(hash
, keybuf
, keylen
, value
);
380 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
385 return svn_error_create(SVN_ERR_MALFORMED_FILE
, NULL
, NULL
);
392 /*** Diffing hashes ***/
395 svn_hash_diff(apr_hash_t
*hash_a
,
397 svn_hash_diff_func_t diff_func
,
398 void *diff_func_baton
,
401 apr_hash_index_t
*hi
;
404 for (hi
= apr_hash_first(pool
, hash_a
); hi
; hi
= apr_hash_next(hi
))
409 apr_hash_this(hi
, &key
, &klen
, NULL
);
411 if (hash_b
&& (apr_hash_get(hash_b
, key
, klen
)))
412 SVN_ERR((*diff_func
)(key
, klen
, svn_hash_diff_key_both
,
415 SVN_ERR((*diff_func
)(key
, klen
, svn_hash_diff_key_a
,
420 for (hi
= apr_hash_first(pool
, hash_b
); hi
; hi
= apr_hash_next(hi
))
425 apr_hash_this(hi
, &key
, &klen
, NULL
);
427 if (! (hash_a
&& apr_hash_get(hash_a
, key
, klen
)))
428 SVN_ERR((*diff_func
)(key
, klen
, svn_hash_diff_key_b
,
436 /*** Misc. hash APIs ***/
439 svn_hash_keys(apr_array_header_t
**array
,
443 apr_hash_index_t
*hi
;
445 *array
= apr_array_make(pool
, apr_hash_count(hash
), sizeof(const char *));
447 for (hi
= apr_hash_first(pool
, hash
); hi
; hi
= apr_hash_next(hi
))
452 apr_hash_this(hi
, &key
, NULL
, NULL
);
455 APR_ARRAY_PUSH(*array
, const char *) = path
;
463 svn_hash_from_cstring_keys(apr_hash_t
**hash_p
,
464 const apr_array_header_t
*keys
,
468 apr_hash_t
*hash
= apr_hash_make(pool
);
469 for (i
= 0; i
< keys
->nelts
; i
++)
472 apr_pstrdup(pool
, APR_ARRAY_IDX(keys
, i
, const char *));
473 apr_hash_set(hash
, key
, APR_HASH_KEY_STRING
, key
);
481 svn_hash__clear(apr_hash_t
*hash
)
483 #if APR_VERSION_AT_LEAST(1, 3, 0)
484 apr_hash_clear(hash
);
486 apr_hash_index_t
*hi
;
490 for (hi
= apr_hash_first(NULL
, hash
); hi
; hi
= apr_hash_next(hi
))
492 apr_hash_this(hi
, &key
, &klen
, NULL
);
493 apr_hash_set(hash
, key
, klen
, NULL
);