1 /*
2 * config.c : reading configuration information
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <assert.h>
27
28 #define APR_WANT_STRFUNC
29 #define APR_WANT_MEMFUNC
30 #include <apr_want.h>
31
32 #include <apr_general.h>
33 #include <apr_lib.h>
34 #include "svn_hash.h"
35 #include "svn_error.h"
36 #include "svn_string.h"
37 #include "svn_pools.h"
38 #include "config_impl.h"
39
40 #include "private/svn_dep_compat.h"
41 #include "private/svn_subr_private.h"
42 #include "private/svn_config_private.h"
43
44 #include "svn_private_config.h"
45
46
47
48
49 /* Section table entries. */
50 typedef struct cfg_section_t cfg_section_t;
51 struct cfg_section_t
52 {
53 /* The section name. */
54 const char *name;
55
56 /* Table of cfg_option_t's. */
57 apr_hash_t *options;
58 };
59
60
61 /* States that a config option value can assume. */
62 typedef enum option_state_t
63 {
64 /* Value still needs to be expanded.
65 This is the initial state for *all* values. */
66 option_state_needs_expanding,
67
68 /* Value is currently being expanded.
69 This transitional state allows for detecting cyclic dependencies. */
70 option_state_expanding,
71
72 /* Expanded value is available.
73 Values that never needed expanding directly go into that state
74 skipping option_state_expanding. */
75 option_state_expanded,
76
77 /* The value expansion is cyclic which results in "undefined" behavior.
78 This is to return a defined value ("") in that case. */
79 option_state_cyclic
80 } option_state_t;
81
82 /* Option table entries. */
83 typedef struct cfg_option_t cfg_option_t;
84 struct cfg_option_t
85 {
86 /* The option name. */
87 const char *name;
88
89 /* The option name, converted into a hash key. */
90 const char *hash_key;
91
92 /* The unexpanded option value. */
93 const char *value;
94
95 /* The expanded option value. */
96 const char *x_value;
97
98 /* Expansion state. If this is option_state_expanded, VALUE has already
99 been expanded. In this case, if x_value is NULL, no expansions were
100 necessary, and value should be used directly. */
101 option_state_t state;
102 };
103
104
105
106 svn_error_t *
svn_config_create2(svn_config_t ** cfgp,svn_boolean_t section_names_case_sensitive,svn_boolean_t option_names_case_sensitive,apr_pool_t * result_pool)107 svn_config_create2(svn_config_t **cfgp,
108 svn_boolean_t section_names_case_sensitive,
109 svn_boolean_t option_names_case_sensitive,
110 apr_pool_t *result_pool)
111 {
112 svn_config_t *cfg = apr_palloc(result_pool, sizeof(*cfg));
113
114 cfg->sections = svn_hash__make(result_pool);
115 cfg->pool = result_pool;
116 cfg->x_pool = svn_pool_create(result_pool);
117 cfg->x_values = FALSE;
118 cfg->tmp_key = svn_stringbuf_create_empty(result_pool);
119 cfg->tmp_value = svn_stringbuf_create_empty(result_pool);
120 cfg->section_names_case_sensitive = section_names_case_sensitive;
121 cfg->option_names_case_sensitive = option_names_case_sensitive;
122 cfg->read_only = FALSE;
123
124 *cfgp = cfg;
125 return SVN_NO_ERROR;
126 }
127
128 svn_error_t *
svn_config_read3(svn_config_t ** cfgp,const char * file,svn_boolean_t must_exist,svn_boolean_t section_names_case_sensitive,svn_boolean_t option_names_case_sensitive,apr_pool_t * result_pool)129 svn_config_read3(svn_config_t **cfgp, const char *file,
130 svn_boolean_t must_exist,
131 svn_boolean_t section_names_case_sensitive,
132 svn_boolean_t option_names_case_sensitive,
133 apr_pool_t *result_pool)
134 {
135 svn_config_t *cfg;
136 svn_error_t *err;
137
138 SVN_ERR(svn_config_create2(&cfg,
139 section_names_case_sensitive,
140 option_names_case_sensitive,
141 result_pool));
142
143 /* Yes, this is platform-specific code in Subversion, but there's no
144 practical way to migrate it into APR, as it's simultaneously
145 Subversion-specific and Windows-specific. Even if we eventually
146 want to have APR offer a generic config-reading interface, it
147 makes sense to test it here first and migrate it later. */
148 #ifdef WIN32
149 if (0 == strncmp(file, SVN_REGISTRY_PREFIX, SVN_REGISTRY_PREFIX_LEN))
150 err = svn_config__parse_registry(cfg, file + SVN_REGISTRY_PREFIX_LEN,
151 must_exist, result_pool);
152 else
153 #endif /* WIN32 */
154 err = svn_config__parse_file(cfg, file, must_exist, result_pool);
155
156 if (err != SVN_NO_ERROR)
157 return err;
158 else
159 *cfgp = cfg;
160
161 return SVN_NO_ERROR;
162 }
163
164 svn_error_t *
svn_config__default_add_value_fn(void * baton,svn_stringbuf_t * section,svn_stringbuf_t * option,svn_stringbuf_t * value)165 svn_config__default_add_value_fn(void *baton,
166 svn_stringbuf_t *section,
167 svn_stringbuf_t *option,
168 svn_stringbuf_t *value)
169 {
170 /* FIXME: We may as well propagate the known string sizes here. */
171 svn_config_set((svn_config_t *)baton, section->data,
172 option->data, value->data);
173 return SVN_NO_ERROR;
174 }
175
176 svn_error_t *
svn_config_parse(svn_config_t ** cfgp,svn_stream_t * stream,svn_boolean_t section_names_case_sensitive,svn_boolean_t option_names_case_sensitive,apr_pool_t * result_pool)177 svn_config_parse(svn_config_t **cfgp, svn_stream_t *stream,
178 svn_boolean_t section_names_case_sensitive,
179 svn_boolean_t option_names_case_sensitive,
180 apr_pool_t *result_pool)
181 {
182 svn_config_t *cfg;
183 svn_error_t *err;
184 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
185
186 err = svn_config_create2(&cfg,
187 section_names_case_sensitive,
188 option_names_case_sensitive,
189 result_pool);
190
191 if (err == SVN_NO_ERROR)
192 err = svn_config__parse_stream(stream,
193 svn_config__constructor_create(
194 NULL, NULL,
195 svn_config__default_add_value_fn,
196 scratch_pool),
197 cfg, scratch_pool);
198
199 if (err == SVN_NO_ERROR)
200 *cfgp = cfg;
201
202 svn_pool_destroy(scratch_pool);
203
204 return err;
205 }
206
207 /* Read various configuration sources into *CFGP, in this order, with
208 * later reads overriding the results of earlier ones:
209 *
210 * 1. SYS_REGISTRY_PATH (only on Win32, but ignored if NULL)
211 *
212 * 2. SYS_FILE_PATH (everywhere, but ignored if NULL)
213 *
214 * 3. USR_REGISTRY_PATH (only on Win32, but ignored if NULL)
215 *
216 * 4. USR_FILE_PATH (everywhere, but ignored if NULL)
217 *
218 * Allocate *CFGP in POOL. Even if no configurations are read,
219 * allocate an empty *CFGP.
220 */
221 static svn_error_t *
read_all(svn_config_t ** cfgp,const char * sys_registry_path,const char * usr_registry_path,const char * sys_file_path,const char * usr_file_path,apr_pool_t * pool)222 read_all(svn_config_t **cfgp,
223 const char *sys_registry_path,
224 const char *usr_registry_path,
225 const char *sys_file_path,
226 const char *usr_file_path,
227 apr_pool_t *pool)
228 {
229 svn_boolean_t red_config = FALSE; /* "red" is the past tense of "read" */
230
231 /*** Read system-wide configurations first... ***/
232
233 #ifdef WIN32
234 if (sys_registry_path)
235 {
236 SVN_ERR(svn_config_read3(cfgp, sys_registry_path, FALSE, FALSE, FALSE,
237 pool));
238 red_config = TRUE;
239 }
240 #endif /* WIN32 */
241
242 if (sys_file_path)
243 {
244 if (red_config)
245 SVN_ERR(svn_config_merge(*cfgp, sys_file_path, FALSE));
246 else
247 {
248 SVN_ERR(svn_config_read3(cfgp, sys_file_path,
249 FALSE, FALSE, FALSE, pool));
250 red_config = TRUE;
251 }
252 }
253
254 /*** ...followed by per-user configurations. ***/
255
256 #ifdef WIN32
257 if (usr_registry_path)
258 {
259 if (red_config)
260 SVN_ERR(svn_config_merge(*cfgp, usr_registry_path, FALSE));
261 else
262 {
263 SVN_ERR(svn_config_read3(cfgp, usr_registry_path,
264 FALSE, FALSE, FALSE, pool));
265 red_config = TRUE;
266 }
267 }
268 #endif /* WIN32 */
269
270 if (usr_file_path)
271 {
272 if (red_config)
273 SVN_ERR(svn_config_merge(*cfgp, usr_file_path, FALSE));
274 else
275 {
276 SVN_ERR(svn_config_read3(cfgp, usr_file_path,
277 FALSE, FALSE, FALSE, pool));
278 red_config = TRUE;
279 }
280 }
281
282 if (! red_config)
283 SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool));
284
285 return SVN_NO_ERROR;
286 }
287
288
289 /* CONFIG_DIR provides an override for the default behavior of reading
290 the default set of overlay files described by read_all()'s doc
291 string. Returns non-NULL *CFG or an error. */
292 static svn_error_t *
get_category_config(svn_config_t ** cfg,const char * config_dir,const char * category,apr_pool_t * pool)293 get_category_config(svn_config_t **cfg,
294 const char *config_dir,
295 const char *category,
296 apr_pool_t *pool)
297 {
298 const char *usr_reg_path = NULL, *sys_reg_path = NULL;
299 const char *usr_cfg_path, *sys_cfg_path;
300 svn_error_t *err = NULL;
301
302 *cfg = NULL;
303
304 if (! config_dir)
305 {
306 #ifdef WIN32
307 sys_reg_path = apr_pstrcat(pool, SVN_REGISTRY_SYS_CONFIG_PATH,
308 category, SVN_VA_NULL);
309 usr_reg_path = apr_pstrcat(pool, SVN_REGISTRY_USR_CONFIG_PATH,
310 category, SVN_VA_NULL);
311 #endif /* WIN32 */
312
313 err = svn_config__sys_config_path(&sys_cfg_path, category, pool);
314 if ((err) && (err->apr_err == SVN_ERR_BAD_FILENAME))
315 {
316 sys_cfg_path = NULL;
317 svn_error_clear(err);
318 }
319 else if (err)
320 return err;
321 }
322 else
323 sys_cfg_path = NULL;
324
325 SVN_ERR(svn_config_get_user_config_path(&usr_cfg_path, config_dir, category,
326 pool));
327 return read_all(cfg, sys_reg_path, usr_reg_path,
328 sys_cfg_path, usr_cfg_path, pool);
329 }
330
331
332 svn_error_t *
svn_config_get_config(apr_hash_t ** cfg_hash,const char * config_dir,apr_pool_t * pool)333 svn_config_get_config(apr_hash_t **cfg_hash,
334 const char *config_dir,
335 apr_pool_t *pool)
336 {
337 svn_config_t *cfg;
338 *cfg_hash = svn_hash__make(pool);
339
340 SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_SERVERS,
341 pool));
342 svn_hash_sets(*cfg_hash, SVN_CONFIG_CATEGORY_SERVERS, cfg);
343
344 SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_CONFIG,
345 pool));
346 svn_hash_sets(*cfg_hash, SVN_CONFIG_CATEGORY_CONFIG, cfg);
347
348 return SVN_NO_ERROR;
349 }
350
351 svn_error_t *
svn_config__get_default_config(apr_hash_t ** cfg_hash,apr_pool_t * pool)352 svn_config__get_default_config(apr_hash_t **cfg_hash,
353 apr_pool_t *pool)
354 {
355 svn_config_t *empty_cfg;
356 *cfg_hash = svn_hash__make(pool);
357
358 SVN_ERR(svn_config_create2(&empty_cfg, FALSE, FALSE, pool));
359 svn_hash_sets(*cfg_hash, SVN_CONFIG_CATEGORY_CONFIG, empty_cfg);
360
361 SVN_ERR(svn_config_create2(&empty_cfg, FALSE, FALSE, pool));
362 svn_hash_sets(*cfg_hash, SVN_CONFIG_CATEGORY_SERVERS, empty_cfg);
363
364 return SVN_NO_ERROR;
365 }
366
367
368
369 /* Iterate through CFG, passing BATON to CALLBACK for every (SECTION, OPTION)
370 pair. Stop if CALLBACK returns TRUE. Allocate from POOL. */
371 static void
for_each_option(svn_config_t * cfg,void * baton,apr_pool_t * pool,svn_boolean_t callback (void * same_baton,cfg_section_t * section,cfg_option_t * option))372 for_each_option(svn_config_t *cfg, void *baton, apr_pool_t *pool,
373 svn_boolean_t callback(void *same_baton,
374 cfg_section_t *section,
375 cfg_option_t *option))
376 {
377 apr_hash_index_t *sec_ndx;
378 for (sec_ndx = apr_hash_first(pool, cfg->sections);
379 sec_ndx != NULL;
380 sec_ndx = apr_hash_next(sec_ndx))
381 {
382 cfg_section_t *sec = apr_hash_this_val(sec_ndx);
383 apr_hash_index_t *opt_ndx;
384
385 for (opt_ndx = apr_hash_first(pool, sec->options);
386 opt_ndx != NULL;
387 opt_ndx = apr_hash_next(opt_ndx))
388 {
389 cfg_option_t *opt = apr_hash_this_val(opt_ndx);
390
391 if (callback(baton, sec, opt))
392 return;
393 }
394 }
395 }
396
397
398
399 static svn_boolean_t
merge_callback(void * baton,cfg_section_t * section,cfg_option_t * option)400 merge_callback(void *baton, cfg_section_t *section, cfg_option_t *option)
401 {
402 svn_config_set(baton, section->name, option->name, option->value);
403 return FALSE;
404 }
405
406 svn_error_t *
svn_config_merge(svn_config_t * cfg,const char * file,svn_boolean_t must_exist)407 svn_config_merge(svn_config_t *cfg, const char *file,
408 svn_boolean_t must_exist)
409 {
410 /* The original config hash shouldn't change if there's an error
411 while reading the confguration, so read into a temporary table.
412 ### We could use a tmp subpool for this, since merge_cfg is going
413 to be tossed afterwards. Premature optimization, though? */
414 svn_config_t *merge_cfg;
415 SVN_ERR(svn_config_read3(&merge_cfg, file, must_exist,
416 cfg->section_names_case_sensitive,
417 cfg->option_names_case_sensitive,
418 cfg->pool));
419
420 /* Now copy the new options into the original table. */
421 for_each_option(merge_cfg, cfg, merge_cfg->pool, merge_callback);
422 return SVN_NO_ERROR;
423 }
424
425
426
427 /* Remove variable expansions from CFG. Walk through the options tree,
428 killing all expanded values, then clear the expanded value pool. */
429 static svn_boolean_t
rmex_callback(void * baton,cfg_section_t * section,cfg_option_t * option)430 rmex_callback(void *baton, cfg_section_t *section, cfg_option_t *option)
431 {
432 /* Only reset the expansion state if the value actually contains
433 variable expansions. */
434 if ( (option->state == option_state_expanded && option->x_value != NULL)
435 || option->state == option_state_cyclic)
436 {
437 option->x_value = NULL;
438 option->state = option_state_needs_expanding;
439 }
440
441 return FALSE;
442 }
443
444 static void
remove_expansions(svn_config_t * cfg)445 remove_expansions(svn_config_t *cfg)
446 {
447 if (!cfg->x_values)
448 return;
449
450 for_each_option(cfg, NULL, cfg->x_pool, rmex_callback);
451 svn_pool_clear(cfg->x_pool);
452 cfg->x_values = FALSE;
453 }
454
455
456
457 /* Canonicalize a string for hashing. Modifies KEY in place. */
458 static APR_INLINE char *
make_hash_key(char * key)459 make_hash_key(char *key)
460 {
461 register char *p;
462 for (p = key; *p != 0; ++p)
463 *p = (char)apr_tolower(*p);
464 return key;
465 }
466
467 /* Return the value for KEY in HASH. If CASE_SENSITIVE is FALSE,
468 BUFFER will be used to construct the normalized hash key. */
469 static void *
get_hash_value(apr_hash_t * hash,svn_stringbuf_t * buffer,const char * key,svn_boolean_t case_sensitive)470 get_hash_value(apr_hash_t *hash,
471 svn_stringbuf_t *buffer,
472 const char *key,
473 svn_boolean_t case_sensitive)
474 {
475 apr_size_t i;
476 apr_size_t len = strlen(key);
477
478 if (case_sensitive)
479 return apr_hash_get(hash, key, len);
480
481 svn_stringbuf_ensure(buffer, len);
482 for (i = 0; i < len; ++i)
483 buffer->data[i] = (char)apr_tolower(key[i]);
484
485 return apr_hash_get(hash, buffer->data, len);
486 }
487
488 /* Return a pointer to an option in CFG, or NULL if it doesn't exist.
489 if SECTIONP is non-null, return a pointer to the option's section.
490 OPTION may be NULL. */
491 static cfg_option_t *
find_option(svn_config_t * cfg,const char * section,const char * option,cfg_section_t ** sectionp)492 find_option(svn_config_t *cfg, const char *section, const char *option,
493 cfg_section_t **sectionp)
494 {
495 void *sec_ptr = get_hash_value(cfg->sections, cfg->tmp_key, section,
496 cfg->section_names_case_sensitive);
497 if (sectionp != NULL)
498 *sectionp = sec_ptr;
499
500 if (sec_ptr != NULL && option != NULL)
501 {
502 cfg_section_t *sec = sec_ptr;
503 cfg_option_t *opt = get_hash_value(sec->options, cfg->tmp_key, option,
504 cfg->option_names_case_sensitive);
505 /* NOTE: ConfigParser's sections are case sensitive. */
506 if (opt == NULL
507 && apr_strnatcasecmp(section, SVN_CONFIG__DEFAULT_SECTION) != 0)
508 /* Options which aren't found in the requested section are
509 also sought after in the default section. */
510 opt = find_option(cfg, SVN_CONFIG__DEFAULT_SECTION, option, &sec);
511 return opt;
512 }
513
514 return NULL;
515 }
516
517
518 /* Has a bi-directional dependency with make_string_from_option(). */
519 static svn_boolean_t
520 expand_option_value(svn_config_t *cfg, cfg_section_t *section,
521 const char *opt_value, const char **opt_x_valuep,
522 apr_pool_t *x_pool);
523
524
525 /* Set *VALUEP according to the OPT's value. A value for X_POOL must
526 only ever be passed into this function by expand_option_value(). */
527 static void
make_string_from_option(const char ** valuep,svn_config_t * cfg,cfg_section_t * section,cfg_option_t * opt,apr_pool_t * x_pool)528 make_string_from_option(const char **valuep, svn_config_t *cfg,
529 cfg_section_t *section, cfg_option_t *opt,
530 apr_pool_t* x_pool)
531 {
532 /* Expand the option value if necessary. */
533 if ( opt->state == option_state_expanding
534 || opt->state == option_state_cyclic)
535 {
536 /* Recursion is not supported. Since we can't produce an error
537 * nor should we abort the process, the next best thing is to
538 * report the recursive part as an empty string. */
539 *valuep = "";
540
541 /* Go into "value undefined" state. */
542 opt->state = option_state_cyclic;
543
544 return;
545 }
546 else if (opt->state == option_state_needs_expanding)
547 {
548 /* before attempting to expand an option, check for the placeholder.
549 * If none is there, there is no point in calling expand_option_value.
550 */
551 if (opt->value && strchr(opt->value, '%'))
552 {
553 apr_pool_t *tmp_pool;
554
555 /* setting read-only mode should have expanded all values
556 * automatically. */
557 assert(!cfg->read_only);
558
559 tmp_pool = (x_pool ? x_pool : svn_pool_create(cfg->x_pool));
560
561 /* Expand the value. During that process, have the option marked
562 * as "expanding" to detect cycles. */
563 opt->state = option_state_expanding;
564 if (expand_option_value(cfg, section, opt->value, &opt->x_value,
565 tmp_pool))
566 opt->state = option_state_expanded;
567 else
568 opt->state = option_state_cyclic;
569
570 /* Ensure the expanded value is allocated in a permanent pool. */
571 if (x_pool != cfg->x_pool)
572 {
573 /* Grab the fully expanded value from tmp_pool before its
574 disappearing act. */
575 if (opt->x_value)
576 opt->x_value = apr_pstrmemdup(cfg->x_pool, opt->x_value,
577 strlen(opt->x_value));
578 if (!x_pool)
579 svn_pool_destroy(tmp_pool);
580 }
581 }
582 else
583 {
584 opt->state = option_state_expanded;
585 }
586 }
587
588 if (opt->x_value)
589 *valuep = opt->x_value;
590 else
591 *valuep = opt->value;
592 }
593
594
595 /* Start of variable-replacement placeholder */
596 #define FMT_START "%("
597 #define FMT_START_LEN (sizeof(FMT_START) - 1)
598
599 /* End of variable-replacement placeholder */
600 #define FMT_END ")s"
601 #define FMT_END_LEN (sizeof(FMT_END) - 1)
602
603
604 /* Expand OPT_VALUE (which may be NULL) in SECTION into *OPT_X_VALUEP.
605 If no variable replacements are done, set *OPT_X_VALUEP to
606 NULL. Return TRUE if the expanded value is defined and FALSE
607 for recursive definitions. Allocate from X_POOL. */
608 static svn_boolean_t
expand_option_value(svn_config_t * cfg,cfg_section_t * section,const char * opt_value,const char ** opt_x_valuep,apr_pool_t * x_pool)609 expand_option_value(svn_config_t *cfg, cfg_section_t *section,
610 const char *opt_value, const char **opt_x_valuep,
611 apr_pool_t *x_pool)
612 {
613 svn_stringbuf_t *buf = NULL;
614 const char *parse_from = opt_value;
615 const char *copy_from = parse_from;
616 const char *name_start, *name_end;
617
618 while (parse_from != NULL
619 && *parse_from != '\0'
620 && (name_start = strstr(parse_from, FMT_START)) != NULL)
621 {
622 name_start += FMT_START_LEN;
623 if (*name_start == '\0')
624 /* FMT_START at end of opt_value. */
625 break;
626
627 name_end = strstr(name_start, FMT_END);
628 if (name_end != NULL)
629 {
630 cfg_option_t *x_opt;
631 apr_size_t len = name_end - name_start;
632 char *name = apr_pstrmemdup(x_pool, name_start, len);
633
634 x_opt = find_option(cfg, section->name, name, NULL);
635
636 if (x_opt != NULL)
637 {
638 const char *cstring;
639
640 /* Pass back the sub-pool originally provided by
641 make_string_from_option() as an indication of when it
642 should terminate. */
643 make_string_from_option(&cstring, cfg, section, x_opt, x_pool);
644
645 /* Values depending on cyclic values must also be marked as
646 * "undefined" because they might themselves form cycles with
647 * the one cycle we just detected. Due to the early abort of
648 * the recursion, we won't follow and thus detect dependent
649 * cycles anymore.
650 */
651 if (x_opt->state == option_state_cyclic)
652 {
653 *opt_x_valuep = "";
654 return FALSE;
655 }
656
657 /* Append the plain text preceding the expansion. */
658 len = name_start - FMT_START_LEN - copy_from;
659 if (buf == NULL)
660 {
661 buf = svn_stringbuf_ncreate(copy_from, len, x_pool);
662 cfg->x_values = TRUE;
663 }
664 else
665 svn_stringbuf_appendbytes(buf, copy_from, len);
666
667 /* Append the expansion and adjust parse pointers. */
668 svn_stringbuf_appendcstr(buf, cstring);
669 parse_from = name_end + FMT_END_LEN;
670 copy_from = parse_from;
671 }
672 else
673 /* Though ConfigParser considers the failure to resolve
674 the requested expansion an exception condition, we
675 consider it to be plain text, and look for the start of
676 the next one. */
677 parse_from = name_end + FMT_END_LEN;
678 }
679 else
680 /* Though ConfigParser treats unterminated format specifiers
681 as an exception condition, we consider them to be plain
682 text. The fact that there are no more format specifier
683 endings means we're done parsing. */
684 parse_from = NULL;
685 }
686
687 if (buf != NULL)
688 {
689 /* Copy the remainder of the plain text. */
690 svn_stringbuf_appendcstr(buf, copy_from);
691 *opt_x_valuep = buf->data;
692 }
693 else
694 *opt_x_valuep = NULL;
695
696 /* Expansion has a well-defined answer. */
697 return TRUE;
698 }
699
700 static cfg_section_t *
svn_config_addsection(svn_config_t * cfg,const char * section)701 svn_config_addsection(svn_config_t *cfg,
702 const char *section)
703 {
704 cfg_section_t *s;
705 const char *hash_key;
706
707 s = apr_palloc(cfg->pool, sizeof(cfg_section_t));
708 s->name = apr_pstrdup(cfg->pool, section);
709 if(cfg->section_names_case_sensitive)
710 hash_key = s->name;
711 else
712 hash_key = make_hash_key(apr_pstrdup(cfg->pool, section));
713 s->options = svn_hash__make(cfg->pool);
714
715 svn_hash_sets(cfg->sections, hash_key, s);
716
717 return s;
718 }
719
720 static void
svn_config_create_option(cfg_option_t ** opt,const char * option,const char * value,svn_boolean_t option_names_case_sensitive,apr_pool_t * pool)721 svn_config_create_option(cfg_option_t **opt,
722 const char *option,
723 const char *value,
724 svn_boolean_t option_names_case_sensitive,
725 apr_pool_t *pool)
726 {
727 cfg_option_t *o;
728
729 o = apr_palloc(pool, sizeof(cfg_option_t));
730 o->name = apr_pstrdup(pool, option);
731 if(option_names_case_sensitive)
732 o->hash_key = o->name;
733 else
734 o->hash_key = make_hash_key(apr_pstrdup(pool, option));
735
736 o->value = apr_pstrdup(pool, value);
737 o->x_value = NULL;
738 o->state = option_state_needs_expanding;
739
740 *opt = o;
741 }
742
743 svn_boolean_t
svn_config__is_expanded(svn_config_t * cfg,const char * section,const char * option)744 svn_config__is_expanded(svn_config_t *cfg,
745 const char *section,
746 const char *option)
747 {
748 cfg_option_t *opt;
749
750 if (cfg == NULL)
751 return FALSE;
752
753 /* does the option even exist? */
754 opt = find_option(cfg, section, option, NULL);
755 if (opt == NULL)
756 return FALSE;
757
758 /* already expanded? */
759 if ( opt->state == option_state_expanded
760 || opt->state == option_state_cyclic)
761 return TRUE;
762
763 /* needs expansion? */
764 if (opt->value && strchr(opt->value, '%'))
765 return FALSE;
766
767 /* no expansion necessary */
768 return TRUE;
769 }
770
771
772 void
svn_config_get(svn_config_t * cfg,const char ** valuep,const char * section,const char * option,const char * default_value)773 svn_config_get(svn_config_t *cfg, const char **valuep,
774 const char *section, const char *option,
775 const char *default_value)
776 {
777 *valuep = default_value;
778 if (cfg)
779 {
780 cfg_section_t *sec;
781 cfg_option_t *opt = find_option(cfg, section, option, &sec);
782 if (opt != NULL)
783 {
784 make_string_from_option(valuep, cfg, sec, opt, NULL);
785 }
786 else
787 /* before attempting to expand an option, check for the placeholder.
788 * If there is none, there is no point in calling expand_option_value.
789 */
790 if (default_value && strchr(default_value, '%'))
791 {
792 apr_pool_t *tmp_pool = svn_pool_create(cfg->pool);
793 const char *x_default;
794 if (!expand_option_value(cfg, sec, default_value, &x_default,
795 tmp_pool))
796 {
797 /* Recursive definitions are not supported.
798 Normalize the answer in that case. */
799 *valuep = "";
800 }
801 else if (x_default)
802 {
803 svn_stringbuf_set(cfg->tmp_value, x_default);
804 *valuep = cfg->tmp_value->data;
805 }
806 svn_pool_destroy(tmp_pool);
807 }
808 }
809 }
810
811
812
813 void
svn_config_set(svn_config_t * cfg,const char * section,const char * option,const char * value)814 svn_config_set(svn_config_t *cfg,
815 const char *section, const char *option,
816 const char *value)
817 {
818 cfg_section_t *sec;
819 cfg_option_t *opt;
820
821 /* Ignore write attempts to r/o configurations.
822 *
823 * Since we should never try to modify r/o data, trigger an assertion
824 * in debug mode.
825 */
826 #ifdef SVN_DEBUG
827 SVN_ERR_ASSERT_NO_RETURN(!cfg->read_only);
828 #endif
829 if (cfg->read_only)
830 return;
831
832 remove_expansions(cfg);
833
834 opt = find_option(cfg, section, option, &sec);
835 if (opt != NULL)
836 {
837 /* Replace the option's value. */
838 opt->value = apr_pstrdup(cfg->pool, value);
839 opt->state = option_state_needs_expanding;
840 return;
841 }
842
843 /* Create a new option */
844 svn_config_create_option(&opt, option, value,
845 cfg->option_names_case_sensitive,
846 cfg->pool);
847
848 if (sec == NULL)
849 {
850 /* Even the section doesn't exist. Create it. */
851 sec = svn_config_addsection(cfg, section);
852 }
853
854 svn_hash_sets(sec->options, opt->hash_key, opt);
855 }
856
857
858
859 /* Set *BOOLP to true or false depending (case-insensitively) on INPUT.
860 If INPUT is null, set *BOOLP to DEFAULT_VALUE.
861
862 INPUT is a string indicating truth or falsehood in any of the usual
863 ways: "true"/"yes"/"on"/etc, "false"/"no"/"off"/etc.
864
865 If INPUT is neither NULL nor a recognized string, return an error
866 with code SVN_ERR_BAD_CONFIG_VALUE; use SECTION and OPTION in
867 constructing the error string. */
868 static svn_error_t *
get_bool(svn_boolean_t * boolp,const char * input,svn_boolean_t default_value,const char * section,const char * option)869 get_bool(svn_boolean_t *boolp, const char *input, svn_boolean_t default_value,
870 const char *section, const char *option)
871 {
872 svn_tristate_t value = svn_tristate__from_word(input);
873
874 if (value == svn_tristate_true)
875 *boolp = TRUE;
876 else if (value == svn_tristate_false)
877 *boolp = FALSE;
878 else if (input == NULL) /* no value provided */
879 *boolp = default_value;
880
881 else if (section) /* unrecognized value */
882 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
883 _("Config error: invalid boolean "
884 "value '%s' for '[%s] %s'"),
885 input, section, option);
886 else
887 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
888 _("Config error: invalid boolean "
889 "value '%s' for '%s'"),
890 input, option);
891
892 return SVN_NO_ERROR;
893 }
894
895
896 svn_error_t *
svn_config_get_bool(svn_config_t * cfg,svn_boolean_t * valuep,const char * section,const char * option,svn_boolean_t default_value)897 svn_config_get_bool(svn_config_t *cfg, svn_boolean_t *valuep,
898 const char *section, const char *option,
899 svn_boolean_t default_value)
900 {
901 const char *tmp_value;
902 svn_config_get(cfg, &tmp_value, section, option, NULL);
903 return get_bool(valuep, tmp_value, default_value, section, option);
904 }
905
906
907
908 void
svn_config_set_bool(svn_config_t * cfg,const char * section,const char * option,svn_boolean_t value)909 svn_config_set_bool(svn_config_t *cfg,
910 const char *section, const char *option,
911 svn_boolean_t value)
912 {
913 svn_config_set(cfg, section, option,
914 (value ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE));
915 }
916
917 svn_error_t *
svn_config_get_int64(svn_config_t * cfg,apr_int64_t * valuep,const char * section,const char * option,apr_int64_t default_value)918 svn_config_get_int64(svn_config_t *cfg,
919 apr_int64_t *valuep,
920 const char *section,
921 const char *option,
922 apr_int64_t default_value)
923 {
924 const char *tmp_value;
925 svn_config_get(cfg, &tmp_value, section, option, NULL);
926 if (tmp_value)
927 return svn_cstring_strtoi64(valuep, tmp_value,
928 APR_INT64_MIN, APR_INT64_MAX, 10);
929
930 *valuep = default_value;
931 return SVN_NO_ERROR;
932 }
933
934 void
svn_config_set_int64(svn_config_t * cfg,const char * section,const char * option,apr_int64_t value)935 svn_config_set_int64(svn_config_t *cfg,
936 const char *section,
937 const char *option,
938 apr_int64_t value)
939 {
940 svn_config_set(cfg, section, option,
941 apr_psprintf(cfg->pool, "%" APR_INT64_T_FMT, value));
942 }
943
944 svn_error_t *
svn_config_get_yes_no_ask(svn_config_t * cfg,const char ** valuep,const char * section,const char * option,const char * default_value)945 svn_config_get_yes_no_ask(svn_config_t *cfg, const char **valuep,
946 const char *section, const char *option,
947 const char* default_value)
948 {
949 const char *tmp_value;
950
951 svn_config_get(cfg, &tmp_value, section, option, NULL);
952
953 if (! tmp_value)
954 tmp_value = default_value;
955
956 if (tmp_value && (0 == svn_cstring_casecmp(tmp_value, SVN_CONFIG_ASK)))
957 {
958 *valuep = SVN_CONFIG_ASK;
959 }
960 else
961 {
962 svn_boolean_t bool_val;
963 /* We already incorporated default_value into tmp_value if
964 necessary, so the FALSE below will be ignored unless the
965 caller is doing something it shouldn't be doing. */
966 SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option));
967 *valuep = bool_val ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE;
968 }
969
970 return SVN_NO_ERROR;
971 }
972
973 svn_error_t *
svn_config_get_tristate(svn_config_t * cfg,svn_tristate_t * valuep,const char * section,const char * option,const char * unknown_value,svn_tristate_t default_value)974 svn_config_get_tristate(svn_config_t *cfg, svn_tristate_t *valuep,
975 const char *section, const char *option,
976 const char *unknown_value,
977 svn_tristate_t default_value)
978 {
979 const char *tmp_value;
980
981 svn_config_get(cfg, &tmp_value, section, option, NULL);
982
983 if (! tmp_value)
984 {
985 *valuep = default_value;
986 }
987 else if (0 == svn_cstring_casecmp(tmp_value, unknown_value))
988 {
989 *valuep = svn_tristate_unknown;
990 }
991 else
992 {
993 svn_boolean_t bool_val;
994 /* We already incorporated default_value into tmp_value if
995 necessary, so the FALSE below will be ignored unless the
996 caller is doing something it shouldn't be doing. */
997 SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option));
998 *valuep = bool_val ? svn_tristate_true : svn_tristate_false;
999 }
1000
1001 return SVN_NO_ERROR;
1002 }
1003
1004 int
svn_config_enumerate_sections(svn_config_t * cfg,svn_config_section_enumerator_t callback,void * baton)1005 svn_config_enumerate_sections(svn_config_t *cfg,
1006 svn_config_section_enumerator_t callback,
1007 void *baton)
1008 {
1009 apr_hash_index_t *sec_ndx;
1010 int count = 0;
1011 apr_pool_t *subpool = svn_pool_create(cfg->x_pool);
1012
1013 for (sec_ndx = apr_hash_first(subpool, cfg->sections);
1014 sec_ndx != NULL;
1015 sec_ndx = apr_hash_next(sec_ndx))
1016 {
1017 void *sec_ptr;
1018 cfg_section_t *sec;
1019
1020 apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr);
1021 sec = sec_ptr;
1022 ++count;
1023 if (!callback(sec->name, baton))
1024 break;
1025 }
1026
1027 svn_pool_destroy(subpool);
1028 return count;
1029 }
1030
1031
1032 int
svn_config_enumerate_sections2(svn_config_t * cfg,svn_config_section_enumerator2_t callback,void * baton,apr_pool_t * pool)1033 svn_config_enumerate_sections2(svn_config_t *cfg,
1034 svn_config_section_enumerator2_t callback,
1035 void *baton, apr_pool_t *pool)
1036 {
1037 apr_hash_index_t *sec_ndx;
1038 apr_pool_t *iteration_pool;
1039 int count = 0;
1040
1041 iteration_pool = svn_pool_create(pool);
1042 for (sec_ndx = apr_hash_first(pool, cfg->sections);
1043 sec_ndx != NULL;
1044 sec_ndx = apr_hash_next(sec_ndx))
1045 {
1046 cfg_section_t *sec = apr_hash_this_val(sec_ndx);
1047
1048 ++count;
1049 svn_pool_clear(iteration_pool);
1050 if (!callback(sec->name, baton, iteration_pool))
1051 break;
1052 }
1053 svn_pool_destroy(iteration_pool);
1054
1055 return count;
1056 }
1057
1058
1059
1060 int
svn_config_enumerate(svn_config_t * cfg,const char * section,svn_config_enumerator_t callback,void * baton)1061 svn_config_enumerate(svn_config_t *cfg, const char *section,
1062 svn_config_enumerator_t callback, void *baton)
1063 {
1064 cfg_section_t *sec;
1065 apr_hash_index_t *opt_ndx;
1066 int count;
1067 apr_pool_t *subpool;
1068
1069 find_option(cfg, section, NULL, &sec);
1070 if (sec == NULL)
1071 return 0;
1072
1073 subpool = svn_pool_create(cfg->pool);
1074 count = 0;
1075 for (opt_ndx = apr_hash_first(subpool, sec->options);
1076 opt_ndx != NULL;
1077 opt_ndx = apr_hash_next(opt_ndx))
1078 {
1079 cfg_option_t *opt = apr_hash_this_val(opt_ndx);
1080 const char *temp_value;
1081
1082 ++count;
1083 make_string_from_option(&temp_value, cfg, sec, opt, NULL);
1084 if (!callback(opt->name, temp_value, baton))
1085 break;
1086 }
1087
1088 svn_pool_destroy(subpool);
1089 return count;
1090 }
1091
1092
1093 int
svn_config_enumerate2(svn_config_t * cfg,const char * section,svn_config_enumerator2_t callback,void * baton,apr_pool_t * pool)1094 svn_config_enumerate2(svn_config_t *cfg, const char *section,
1095 svn_config_enumerator2_t callback, void *baton,
1096 apr_pool_t *pool)
1097 {
1098 cfg_section_t *sec;
1099 apr_hash_index_t *opt_ndx;
1100 apr_pool_t *iteration_pool;
1101 int count;
1102
1103 find_option(cfg, section, NULL, &sec);
1104 if (sec == NULL)
1105 return 0;
1106
1107 iteration_pool = svn_pool_create(pool);
1108 count = 0;
1109 for (opt_ndx = apr_hash_first(pool, sec->options);
1110 opt_ndx != NULL;
1111 opt_ndx = apr_hash_next(opt_ndx))
1112 {
1113 cfg_option_t *opt = apr_hash_this_val(opt_ndx);
1114 const char *temp_value;
1115
1116 ++count;
1117 make_string_from_option(&temp_value, cfg, sec, opt, NULL);
1118 svn_pool_clear(iteration_pool);
1119 if (!callback(opt->name, temp_value, baton, iteration_pool))
1120 break;
1121 }
1122 svn_pool_destroy(iteration_pool);
1123
1124 return count;
1125 }
1126
1127
1128
1129 /* Baton for search_groups() */
1130 struct search_groups_baton
1131 {
1132 const char *key; /* Provided by caller of svn_config_find_group */
1133 const char *match; /* Filled in by search_groups */
1134 apr_pool_t *pool;
1135 };
1136
1137
1138 /* This is an `svn_config_enumerator_t' function, and BATON is a
1139 * `struct search_groups_baton *'.
1140 */
search_groups(const char * name,const char * value,void * baton,apr_pool_t * pool)1141 static svn_boolean_t search_groups(const char *name,
1142 const char *value,
1143 void *baton,
1144 apr_pool_t *pool)
1145 {
1146 struct search_groups_baton *b = baton;
1147 apr_array_header_t *list;
1148
1149 list = svn_cstring_split(value, ",", TRUE, pool);
1150 if (svn_cstring_match_glob_list(b->key, list))
1151 {
1152 /* Fill in the match and return false, to stop enumerating. */
1153 b->match = apr_pstrdup(b->pool, name);
1154 return FALSE;
1155 }
1156 else
1157 return TRUE;
1158 }
1159
1160
svn_config_find_group(svn_config_t * cfg,const char * key,const char * master_section,apr_pool_t * pool)1161 const char *svn_config_find_group(svn_config_t *cfg, const char *key,
1162 const char *master_section,
1163 apr_pool_t *pool)
1164 {
1165 struct search_groups_baton gb;
1166
1167 gb.key = key;
1168 gb.match = NULL;
1169 gb.pool = pool;
1170 (void) svn_config_enumerate2(cfg, master_section, search_groups, &gb, pool);
1171 return gb.match;
1172 }
1173
1174
1175 const char*
svn_config_get_server_setting(svn_config_t * cfg,const char * server_group,const char * option_name,const char * default_value)1176 svn_config_get_server_setting(svn_config_t *cfg,
1177 const char* server_group,
1178 const char* option_name,
1179 const char* default_value)
1180 {
1181 const char *retval;
1182 svn_config_get(cfg, &retval, SVN_CONFIG_SECTION_GLOBAL,
1183 option_name, default_value);
1184 if (server_group)
1185 {
1186 svn_config_get(cfg, &retval, server_group, option_name, retval);
1187 }
1188 return retval;
1189 }
1190
1191
1192 svn_error_t *
svn_config_dup(svn_config_t ** cfgp,const svn_config_t * src,apr_pool_t * pool)1193 svn_config_dup(svn_config_t **cfgp,
1194 const svn_config_t *src,
1195 apr_pool_t *pool)
1196 {
1197 apr_hash_index_t *sectidx;
1198 apr_hash_index_t *optidx;
1199
1200 *cfgp = 0;
1201 SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool));
1202
1203 (*cfgp)->x_values = src->x_values;
1204 (*cfgp)->section_names_case_sensitive = src->section_names_case_sensitive;
1205 (*cfgp)->option_names_case_sensitive = src->option_names_case_sensitive;
1206
1207 for (sectidx = apr_hash_first(pool, src->sections);
1208 sectidx != NULL;
1209 sectidx = apr_hash_next(sectidx))
1210 {
1211 const void *sectkey;
1212 void *sectval;
1213 apr_ssize_t sectkeyLength;
1214 cfg_section_t * srcsect;
1215 cfg_section_t * destsec;
1216
1217 apr_hash_this(sectidx, §key, §keyLength, §val);
1218 srcsect = sectval;
1219
1220 destsec = svn_config_addsection(*cfgp, srcsect->name);
1221
1222 for (optidx = apr_hash_first(pool, srcsect->options);
1223 optidx != NULL;
1224 optidx = apr_hash_next(optidx))
1225 {
1226 const void *optkey;
1227 void *optval;
1228 apr_ssize_t optkeyLength;
1229 cfg_option_t *srcopt;
1230 cfg_option_t *destopt;
1231
1232 apr_hash_this(optidx, &optkey, &optkeyLength, &optval);
1233 srcopt = optval;
1234
1235 svn_config_create_option(&destopt, srcopt->name, srcopt->value,
1236 (*cfgp)->option_names_case_sensitive,
1237 pool);
1238
1239 destopt->value = apr_pstrdup(pool, srcopt->value);
1240 destopt->x_value = apr_pstrdup(pool, srcopt->x_value);
1241 destopt->state = srcopt->state;
1242 apr_hash_set(destsec->options,
1243 apr_pstrdup(pool, (const char*)optkey),
1244 optkeyLength, destopt);
1245 }
1246 }
1247
1248 return SVN_NO_ERROR;
1249 }
1250
1251 svn_error_t *
svn_config_copy_config(apr_hash_t ** cfg_hash,apr_hash_t * src_hash,apr_pool_t * pool)1252 svn_config_copy_config(apr_hash_t **cfg_hash,
1253 apr_hash_t *src_hash,
1254 apr_pool_t *pool)
1255 {
1256 apr_hash_index_t *cidx;
1257
1258 *cfg_hash = svn_hash__make(pool);
1259 for (cidx = apr_hash_first(pool, src_hash);
1260 cidx != NULL;
1261 cidx = apr_hash_next(cidx))
1262 {
1263 const void *ckey;
1264 void *cval;
1265 apr_ssize_t ckeyLength;
1266 svn_config_t * srcconfig;
1267 svn_config_t * destconfig;
1268
1269 apr_hash_this(cidx, &ckey, &ckeyLength, &cval);
1270 srcconfig = cval;
1271
1272 SVN_ERR(svn_config_dup(&destconfig, srcconfig, pool));
1273
1274 apr_hash_set(*cfg_hash,
1275 apr_pstrdup(pool, (const char*)ckey),
1276 ckeyLength, destconfig);
1277 }
1278
1279 return SVN_NO_ERROR;
1280 }
1281
1282 svn_error_t*
svn_config_get_server_setting_int(svn_config_t * cfg,const char * server_group,const char * option_name,apr_int64_t default_value,apr_int64_t * result_value,apr_pool_t * pool)1283 svn_config_get_server_setting_int(svn_config_t *cfg,
1284 const char *server_group,
1285 const char *option_name,
1286 apr_int64_t default_value,
1287 apr_int64_t *result_value,
1288 apr_pool_t *pool)
1289 {
1290 const char* tmp_value;
1291 char *end_pos;
1292
1293 tmp_value = svn_config_get_server_setting(cfg, server_group,
1294 option_name, NULL);
1295 if (tmp_value == NULL)
1296 *result_value = default_value;
1297 else
1298 {
1299 /* read tmp_value as an int now */
1300 *result_value = apr_strtoi64(tmp_value, &end_pos, 0);
1301
1302 if (*end_pos != 0)
1303 {
1304 return svn_error_createf
1305 (SVN_ERR_BAD_CONFIG_VALUE, NULL,
1306 _("Config error: invalid integer value '%s'"),
1307 tmp_value);
1308 }
1309 }
1310
1311 return SVN_NO_ERROR;
1312 }
1313
1314 svn_error_t *
svn_config_get_server_setting_bool(svn_config_t * cfg,svn_boolean_t * valuep,const char * server_group,const char * option_name,svn_boolean_t default_value)1315 svn_config_get_server_setting_bool(svn_config_t *cfg,
1316 svn_boolean_t *valuep,
1317 const char *server_group,
1318 const char *option_name,
1319 svn_boolean_t default_value)
1320 {
1321 const char* tmp_value;
1322 tmp_value = svn_config_get_server_setting(cfg, server_group,
1323 option_name, NULL);
1324 return get_bool(valuep, tmp_value, default_value,
1325 server_group, option_name);
1326 }
1327
1328
1329 svn_boolean_t
svn_config_has_section(svn_config_t * cfg,const char * section)1330 svn_config_has_section(svn_config_t *cfg, const char *section)
1331 {
1332 return NULL != get_hash_value(cfg->sections, cfg->tmp_key, section,
1333 cfg->section_names_case_sensitive);
1334 }
1335
1336 svn_error_t *
svn_config__write(svn_stream_t * stream,const struct svn_config_t * cfg,apr_pool_t * scratch_pool)1337 svn_config__write(svn_stream_t *stream,
1338 const struct svn_config_t *cfg,
1339 apr_pool_t *scratch_pool)
1340 {
1341 apr_hash_index_t *section_i;
1342 apr_hash_index_t *options_i;
1343 apr_pool_t *section_pool = svn_pool_create(scratch_pool);
1344 apr_pool_t *options_pool = svn_pool_create(scratch_pool);
1345
1346 for (section_i = apr_hash_first(scratch_pool, cfg->sections);
1347 section_i != NULL;
1348 section_i = apr_hash_next(section_i))
1349 {
1350 cfg_section_t *section = apr_hash_this_val(section_i);
1351 svn_pool_clear(section_pool);
1352 SVN_ERR(svn_stream_printf(stream, section_pool, "\n[%s]\n",
1353 section->name));
1354
1355 for (options_i = apr_hash_first(section_pool, section->options);
1356 options_i != NULL;
1357 options_i = apr_hash_next(options_i))
1358 {
1359 cfg_option_t *option = apr_hash_this_val(options_i);
1360 svn_pool_clear(options_pool);
1361 SVN_ERR(svn_stream_printf(stream, options_pool, "%s=%s\n",
1362 option->name, option->value));
1363 }
1364 }
1365
1366 svn_pool_destroy(section_pool);
1367 svn_pool_destroy(options_pool);
1368
1369 return SVN_NO_ERROR;
1370 }
1371
1372