blob: 303a66f3b6c362be41c9fa4e81c4699bbcc289f9 [file] [log] [blame]
/*
* config_file.c : parsing configuration files
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <apr_lib.h>
#include <apr_env.h>
#include <apr_tables.h>
#include "config_impl.h"
#include "svn_io.h"
#include "svn_types.h"
#include "svn_dirent_uri.h"
#include "svn_auth.h"
#include "svn_hash.h"
#include "svn_subst.h"
#include "svn_utf.h"
#include "svn_pools.h"
#include "svn_user.h"
#include "svn_ctype.h"
#include "private/svn_config_private.h"
#include "private/svn_subr_private.h"
#include "svn_private_config.h"
#ifdef __HAIKU__
# include <FindDirectory.h>
# include <StorageDefs.h>
#endif
/* Used to terminate lines in large multi-line string literals. */
#define NL APR_EOL_STR
/* File parsing context */
typedef struct parse_context_t
{
/* The configuration constructor. */
svn_config__constructor_t *constructor;
void *constructor_baton;
/* The stream struct */
svn_stream_t *stream;
/* The current line in the file */
int line;
/* Emulate an ungetc */
int ungotten_char;
/* We're currently parsing a section */
svn_boolean_t in_section;
/* Temporary strings */
svn_stringbuf_t *section;
svn_stringbuf_t *option;
svn_stringbuf_t *value;
svn_stringbuf_t *line_read;
/* Parser buffer for getc() to avoid call overhead into several libraries
for every character */
char parser_buffer[SVN__STREAM_CHUNK_SIZE]; /* Larger than most config files */
size_t buffer_pos; /* Current position within parser_buffer */
size_t buffer_size; /* parser_buffer contains this many bytes */
/* Non-zero if we hit EOF on the stream. */
svn_boolean_t hit_stream_eof;
} parse_context_t;
/* Config representation constructor */
struct svn_config__constructor_t
{
/* Constructor callbacks; see docs for svn_config__constructor_create. */
svn_config__open_section_fn open_section;
svn_config__close_section_fn close_section;
svn_config__add_value_fn add_value;
};
svn_config__constructor_t *
svn_config__constructor_create(
svn_config__open_section_fn open_section_callback,
svn_config__close_section_fn close_section_callback,
svn_config__add_value_fn add_value_callback,
apr_pool_t *result_pool)
{
svn_config__constructor_t *ctor = apr_palloc(result_pool, sizeof(*ctor));
ctor->open_section = open_section_callback;
ctor->close_section = close_section_callback;
ctor->add_value = add_value_callback;
return ctor;
}
/* Called after we've parsed a section name and before we start
parsing any options within that section. */
static APR_INLINE svn_error_t *
open_section(parse_context_t *ctx, svn_boolean_t *stop)
{
if (ctx->constructor->open_section)
{
svn_error_t *err = ctx->constructor->open_section(
ctx->constructor_baton, ctx->section);
if (err)
{
if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
{
*stop = TRUE;
svn_error_clear(err);
return SVN_NO_ERROR;
}
else
return svn_error_trace(err);
}
}
*stop = FALSE;
ctx->in_section = TRUE;
return SVN_NO_ERROR;
}
/* Called after we've parsed all options within a section and before
we start parsing the next section. */
static APR_INLINE svn_error_t *
close_section(parse_context_t *ctx, svn_boolean_t *stop)
{
ctx->in_section = FALSE;
if (ctx->constructor->close_section)
{
svn_error_t *err = ctx->constructor->close_section(
ctx->constructor_baton, ctx->section);
if (err)
{
if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
{
*stop = TRUE;
svn_error_clear(err);
return SVN_NO_ERROR;
}
else
return svn_error_trace(err);
}
}
*stop = FALSE;
return SVN_NO_ERROR;
}
/* Called every tyme we've parsed a complete (option, value) pair. */
static APR_INLINE svn_error_t *
add_value(parse_context_t *ctx, svn_boolean_t *stop)
{
if (ctx->constructor->add_value)
{
svn_error_t *err = ctx->constructor->add_value(
ctx->constructor_baton, ctx->section,
ctx->option, ctx->value);
if (err)
{
if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
{
*stop = TRUE;
svn_error_clear(err);
return SVN_NO_ERROR;
}
else
return svn_error_trace(err);
}
}
*stop = FALSE;
return SVN_NO_ERROR;
}
/* Emulate getc() because streams don't support it.
*
* In order to be able to ungetc(), use the CXT instead of the stream
* to be able to store the 'ungotton' character.
*
*/
static APR_INLINE svn_error_t *
parser_getc(parse_context_t *ctx, int *c)
{
do
{
if (ctx->ungotten_char != EOF)
{
*c = ctx->ungotten_char;
ctx->ungotten_char = EOF;
}
else if (ctx->buffer_pos < ctx->buffer_size)
{
*c = (unsigned char)ctx->parser_buffer[ctx->buffer_pos];
ctx->buffer_pos++;
}
else
{
if (!ctx->hit_stream_eof)
{
ctx->buffer_pos = 0;
ctx->buffer_size = sizeof(ctx->parser_buffer);
SVN_ERR(svn_stream_read_full(ctx->stream, ctx->parser_buffer,
&(ctx->buffer_size)));
ctx->hit_stream_eof = (ctx->buffer_size != sizeof(ctx->parser_buffer));
}
if (ctx->buffer_pos < ctx->buffer_size)
{
*c = (unsigned char)ctx->parser_buffer[ctx->buffer_pos];
ctx->buffer_pos++;
}
else
*c = EOF;
}
}
while (*c == '\r');
return SVN_NO_ERROR;
}
/* Simplified version of parser_getc() to be used inside skipping loops.
* It will not check for 'ungotton' chars and may or may not ignore '\r'.
*
* In a 'while(cond) getc();' loop, the first iteration must call
* parser_getc to handle all the special cases. Later iterations should
* use parser_getc_plain for maximum performance.
*/
static APR_INLINE svn_error_t *
parser_getc_plain(parse_context_t *ctx, int *c)
{
if (ctx->buffer_pos < ctx->buffer_size)
{
*c = (unsigned char)ctx->parser_buffer[ctx->buffer_pos];
ctx->buffer_pos++;
return SVN_NO_ERROR;
}
return parser_getc(ctx, c);
}
/* Emulate ungetc() because streams don't support it.
*
* Use CTX to store the ungotten character C.
*/
static APR_INLINE svn_error_t *
parser_ungetc(parse_context_t *ctx, int c)
{
ctx->ungotten_char = c;
return SVN_NO_ERROR;
}
/* Read from CTX to the end of the line (or file) and write the data
into LINE. Previous contents of *LINE will be overwritten.
Returns the char that ended the line in *C; the char is either
EOF or newline. */
static svn_error_t *
parser_get_line(parse_context_t *ctx, svn_stringbuf_t *line, int *c)
{
int ch;
svn_stringbuf_setempty(line);
/* Loop until EOF of newline. There is one extra iteration per
* parser buffer refill. The initial parser_getc() also ensures
* that the unget buffer is emptied. */
SVN_ERR(parser_getc(ctx, &ch));
while (ch != '\n' && ch != EOF)
{
const char *start, *newline;
/* Save the char we just read. */
svn_stringbuf_appendbyte(line, ch);
/* Scan the parser buffer for NL. */
start = ctx->parser_buffer + ctx->buffer_pos;
newline = memchr(start, '\n', ctx->buffer_size - ctx->buffer_pos);
if (newline)
{
/* NL found before the end of the of the buffer. */
svn_stringbuf_appendbytes(line, start, newline - start);
ch = '\n';
ctx->buffer_pos = newline - ctx->parser_buffer + 1;
break;
}
else
{
/* Hit the end of the buffer. This may be either due to
* buffer size or EOF. In any case, all (remaining) current
* buffer data is part of the line to read. */
const char *end = ctx->parser_buffer + ctx->buffer_size;
ctx->buffer_pos = ctx->buffer_size;
svn_stringbuf_appendbytes(line, start, end - start);
}
/* refill buffer, check for EOF */
SVN_ERR(parser_getc_plain(ctx, &ch));
}
*c = ch;
return SVN_NO_ERROR;
}
/* Eat chars from STREAM until encounter non-whitespace, newline, or EOF.
Set *PCOUNT to the number of characters eaten, not counting the
last one, and return the last char read (the one that caused the
break). */
static APR_INLINE svn_error_t *
skip_whitespace(parse_context_t *ctx, int *c, int *pcount)
{
int ch = 0;
int count = 0;
SVN_ERR(parser_getc(ctx, &ch));
while (svn_ctype_isspace(ch) && ch != '\n' && ch != EOF)
{
++count;
SVN_ERR(parser_getc_plain(ctx, &ch));
}
*pcount = count;
*c = ch;
return SVN_NO_ERROR;
}
/* Skip to the end of the line (or file). Returns the char that ended
the line; the char is either EOF or newline. */
static APR_INLINE svn_error_t *
skip_to_eoln(parse_context_t *ctx, int *c)
{
int ch;
SVN_ERR(parser_getc(ctx, &ch));
while (ch != '\n' && ch != EOF)
{
/* This is much faster than checking individual bytes.
* We use this function a lot when skipping comment lines.
*
* This assumes that the ungetc buffer is empty, but that is a
* safe assumption right after reading a character (which would
* clear the buffer. */
const char *newline = memchr(ctx->parser_buffer + ctx->buffer_pos, '\n',
ctx->buffer_size - ctx->buffer_pos);
if (newline)
{
ch = '\n';
ctx->buffer_pos = newline - ctx->parser_buffer + 1;
break;
}
/* refill buffer, check for EOF */
SVN_ERR(parser_getc_plain(ctx, &ch));
}
*c = ch;
return SVN_NO_ERROR;
}
/* Skip a UTF-8 Byte Order Mark if found. */
static APR_INLINE svn_error_t *
skip_bom(parse_context_t *ctx)
{
int ch;
SVN_ERR(parser_getc(ctx, &ch));
if (ch == 0xEF)
{
const unsigned char *buf = (unsigned char *)ctx->parser_buffer;
/* This makes assumptions about the implementation of parser_getc and
* the use of skip_bom. Specifically that parser_getc() will get all
* of the BOM characters into the parse_context_t buffer. This can
* safely be assumed as long as we only try to use skip_bom() at the
* start of the stream and the buffer is longer than 3 characters. */
SVN_ERR_ASSERT(ctx->buffer_size > ctx->buffer_pos + 1 ||
ctx->hit_stream_eof);
if (ctx->buffer_size > ctx->buffer_pos + 1 &&
buf[ctx->buffer_pos] == 0xBB && buf[ctx->buffer_pos + 1] == 0xBF)
ctx->buffer_pos += 2;
else
SVN_ERR(parser_ungetc(ctx, ch));
}
else
SVN_ERR(parser_ungetc(ctx, ch));
return SVN_NO_ERROR;
}
/* Parse continuation lines of single option value if they exist. Append
* their contents, including *PCH, to CTX->VALUE. Return the first char
* of the next line or EOF in *PCH. */
static svn_error_t *
parse_value_continuation_lines(int *pch, parse_context_t *ctx)
{
svn_boolean_t end_of_val = FALSE;
svn_boolean_t stop;
int ch = *pch;
/* Leading and trailing whitespace is ignored. */
svn_stringbuf_strip_whitespace(ctx->value);
/* Look for any continuation lines. */
for (;;)
{
if (ch == EOF || end_of_val)
{
/* At end of file. The value is complete, there can't be
any continuation lines. */
SVN_ERR(add_value(ctx, &stop));
if (stop)
return SVN_NO_ERROR;
break;
}
else
{
int count;
++ctx->line;
SVN_ERR(skip_whitespace(ctx, &ch, &count));
switch (ch)
{
case '\n':
/* The next line was empty. Ergo, it can't be a
continuation line. */
++ctx->line;
end_of_val = TRUE;
continue;
case EOF:
/* This is also an empty line. */
end_of_val = TRUE;
continue;
default:
if (count == 0)
{
/* This line starts in the first column. That means
it's either a section, option or comment. Put
the char back into the stream, because it doesn't
belong to us. */
SVN_ERR(parser_ungetc(ctx, ch));
end_of_val = TRUE;
}
else
{
/* This is a continuation line. Read it. */
SVN_ERR(parser_ungetc(ctx, ch));
SVN_ERR(parser_get_line(ctx, ctx->line_read, &ch));
/* Trailing whitespace is ignored. */
svn_stringbuf_strip_whitespace(ctx->line_read);
svn_stringbuf_appendbyte(ctx->value, ' ');
svn_stringbuf_appendbytes(ctx->value, ctx->line_read->data,
ctx->line_read->len);
}
}
}
}
*pch = ch;
return SVN_NO_ERROR;
}
/* Parse a single option */
static svn_error_t *
parse_option(int *pch, parse_context_t *ctx, apr_pool_t *scratch_pool)
{
svn_error_t *err = SVN_NO_ERROR;
int ch;
char *equals, *separator;
/* Read the first line. */
parser_ungetc(ctx, *pch); /* Yes, the first char is relevant. */
SVN_ERR(parser_get_line(ctx, ctx->line_read, &ch));
/* Extract the option name from it. */
equals = strchr(ctx->line_read->data, '=');
if (equals)
{
/* Efficiently look for a colon separator prior to the equals sign.
* Since there is no strnchr, we limit the search by temporarily
* limiting the string. */
char *colon;
*equals = 0;
colon = strchr(ctx->line_read->data, ':');
*equals = '=';
separator = colon ? colon : equals;
}
else
{
/* No '=' separator so far. Look for colon. */
separator = strchr(ctx->line_read->data, ':');
}
if (!separator)
{
ch = EOF;
err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Option must end with ':' or '='"),
ctx->line);
}
else
{
/* Whitespace around the name separator is ignored. */
const char *end = separator;
while (svn_ctype_isspace(*--end))
;
while (svn_ctype_isspace(*++separator))
;
/* Extract the option. It can't contain whitespace chars. */
svn_stringbuf_setempty(ctx->option);
svn_stringbuf_appendbytes(ctx->option, ctx->line_read->data,
end + 1 - ctx->line_read->data);
/* Extract the first line of the value. */
end = ctx->line_read->data + ctx->line_read->len;
svn_stringbuf_setempty(ctx->value);
svn_stringbuf_appendbytes(ctx->value, separator, end - separator);
err = parse_value_continuation_lines(&ch, ctx);
}
*pch = ch;
return err;
}
/* Read chars until encounter ']', then skip everything to the end of
* the line. Set *PCH to the character that ended the line (either
* newline or EOF), and set CTX->section to the string of characters
* seen before ']'.
*
* This is meant to be called immediately after reading the '[' that
* starts a section name.
*/
static svn_error_t *
parse_section_name(int *pch, parse_context_t *ctx,
apr_pool_t *scratch_pool)
{
svn_error_t *err = SVN_NO_ERROR;
int ch;
char *terminal;
SVN_ERR(parser_get_line(ctx, ctx->section, &ch));
terminal = strchr(ctx->section->data, ']');
if (!terminal)
{
ch = EOF;
err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Section header must end with ']'"),
ctx->line);
}
else
{
/* Everything from the ']' to the end of the line is ignored. */
*terminal = 0;
ctx->section->len = terminal - ctx->section->data;
}
*pch = ch;
return err;
}
svn_error_t *
svn_config__sys_config_path(const char **path_p,
const char *fname,
apr_pool_t *pool)
{
*path_p = NULL;
/* Note that even if fname is null, svn_dirent_join_many will DTRT. */
#ifdef WIN32
{
const char *folder;
SVN_ERR(svn_config__win_config_path(&folder, TRUE, pool, pool));
*path_p = svn_dirent_join_many(pool, folder,
SVN_CONFIG__SUBDIRECTORY, fname,
SVN_VA_NULL);
}
#elif defined(__HAIKU__)
{
char folder[B_PATH_NAME_LENGTH];
status_t error = find_directory(B_COMMON_SETTINGS_DIRECTORY, -1, false,
folder, sizeof(folder));
if (error)
return SVN_NO_ERROR;
*path_p = svn_dirent_join_many(pool, folder,
SVN_CONFIG__SYS_DIRECTORY, fname,
SVN_VA_NULL);
}
#else /* ! WIN32 && !__HAIKU__ */
*path_p = svn_dirent_join_many(pool, SVN_CONFIG__SYS_DIRECTORY, fname,
SVN_VA_NULL);
#endif /* WIN32 */
return SVN_NO_ERROR;
}
/* Callback for svn_config_enumerate2: Continue to next value. */
static svn_boolean_t
expand_value(const char *name,
const char *value,
void *baton,
apr_pool_t *pool)
{
return TRUE;
}
/* Callback for svn_config_enumerate_sections2:
* Enumerate and implicitly expand all values in this section.
*/
static svn_boolean_t
expand_values_in_section(const char *name,
void *baton,
apr_pool_t *pool)
{
svn_config_t *cfg = baton;
svn_config_enumerate2(cfg, name, expand_value, NULL, pool);
return TRUE;
}
/*** Exported interfaces. ***/
void
svn_config__set_read_only(svn_config_t *cfg,
apr_pool_t *scratch_pool)
{
/* expand all items such that later calls to getters won't need to
* change internal state */
svn_config_enumerate_sections2(cfg, expand_values_in_section,
cfg, scratch_pool);
/* now, any modification attempt will be ignored / trigger an assertion
* in debug mode */
cfg->read_only = TRUE;
}
svn_boolean_t
svn_config__is_read_only(svn_config_t *cfg)
{
return cfg->read_only;
}
svn_config_t *
svn_config__shallow_copy(svn_config_t *src,
apr_pool_t *pool)
{
svn_config_t *cfg = apr_palloc(pool, sizeof(*cfg));
cfg->sections = src->sections;
cfg->pool = pool;
/* r/o configs are fully expanded and don't need the x_pool anymore */
cfg->x_pool = src->read_only ? NULL : svn_pool_create(pool);
cfg->x_values = src->x_values;
cfg->tmp_key = svn_stringbuf_create_empty(pool);
cfg->tmp_value = svn_stringbuf_create_empty(pool);
cfg->section_names_case_sensitive = src->section_names_case_sensitive;
cfg->option_names_case_sensitive = src->option_names_case_sensitive;
cfg->read_only = src->read_only;
return cfg;
}
void
svn_config__shallow_replace_section(svn_config_t *target,
svn_config_t *source,
const char *section)
{
if (target->read_only)
target->sections = apr_hash_copy(target->pool, target->sections);
svn_hash_sets(target->sections, section,
svn_hash_gets(source->sections, section));
}
svn_error_t *
svn_config__parse_file(svn_config_t *cfg, const char *file,
svn_boolean_t must_exist, apr_pool_t *result_pool)
{
svn_error_t *err = SVN_NO_ERROR;
apr_file_t *apr_file;
svn_stream_t *stream;
apr_pool_t *scratch_pool = svn_pool_create(result_pool);
/* Use unbuffered IO since we use our own buffering. */
err = svn_io_file_open(&apr_file, file, APR_READ, APR_OS_DEFAULT,
scratch_pool);
if (! must_exist && err && APR_STATUS_IS_ENOENT(err->apr_err))
{
svn_error_clear(err);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else
SVN_ERR(err);
stream = svn_stream_from_aprfile2(apr_file, FALSE, scratch_pool);
err = svn_config__parse_stream(stream,
svn_config__constructor_create(
NULL, NULL,
svn_config__default_add_value_fn,
scratch_pool),
cfg, scratch_pool);
if (err != SVN_NO_ERROR)
{
/* Add the filename to the error stack. */
err = svn_error_createf(err->apr_err, err,
_("Error while parsing config file: %s:"),
svn_dirent_local_style(file, scratch_pool));
}
/* Close the streams (and other cleanup): */
svn_pool_destroy(scratch_pool);
return err;
}
svn_error_t *
svn_config__parse_stream(svn_stream_t *stream,
svn_config__constructor_t *constructor,
void *constructor_baton,
apr_pool_t *scratch_pool)
{
parse_context_t *ctx;
svn_boolean_t stop;
int ch, count;
ctx = apr_palloc(scratch_pool, sizeof(*ctx));
ctx->constructor = constructor;
ctx->constructor_baton = constructor_baton;
ctx->stream = stream;
ctx->line = 1;
ctx->ungotten_char = EOF;
ctx->in_section = FALSE;
ctx->section = svn_stringbuf_create_empty(scratch_pool);
ctx->option = svn_stringbuf_create_empty(scratch_pool);
ctx->value = svn_stringbuf_create_empty(scratch_pool);
ctx->line_read = svn_stringbuf_create_empty(scratch_pool);
ctx->buffer_pos = 0;
ctx->buffer_size = 0;
ctx->hit_stream_eof = FALSE;
SVN_ERR(skip_bom(ctx));
do
{
SVN_ERR(skip_whitespace(ctx, &ch, &count));
switch (ch)
{
case '[': /* Start of section header */
if (count != 0)
return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Section header"
" must start in the first column"),
ctx->line);
/* Close the previous section before starting a new one. */
if (ctx->in_section)
{
SVN_ERR(close_section(ctx, &stop));
if (stop)
return SVN_NO_ERROR;
}
SVN_ERR(parse_section_name(&ch, ctx, scratch_pool));
SVN_ERR(open_section(ctx, &stop));
if (stop)
return SVN_NO_ERROR;
break;
case '#': /* Comment */
if (count == 0)
{
SVN_ERR(skip_to_eoln(ctx, &ch));
++(ctx->line);
}
else
return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Comment"
" must start in the first column"),
ctx->line);
break;
case '\n': /* Empty line */
++(ctx->line);
break;
case EOF: /* End of file or read error */
break;
default:
if (svn_stringbuf_isempty(ctx->section))
return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Section header expected"),
ctx->line);
else if (count != 0)
return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
_("line %d: Option expected"),
ctx->line);
else
SVN_ERR(parse_option(&ch, ctx, scratch_pool));
break;
}
}
while (ch != EOF);
/* Emit the last close-section call to wrap up. */
if (ctx->in_section)
SVN_ERR(close_section(ctx, &stop));
return SVN_NO_ERROR;
}
/* Helper for ensure_auth_dirs: create SUBDIR under AUTH_DIR, iff
SUBDIR does not already exist, but ignore any errors. Use POOL for
temporary allocation. */
static void
ensure_auth_subdir(const char *auth_dir,
const char *subdir,
apr_pool_t *pool)
{
svn_error_t *err;
const char *subdir_full_path;
svn_node_kind_t kind;
subdir_full_path = svn_dirent_join(auth_dir, subdir, pool);
err = svn_io_check_path(subdir_full_path, &kind, pool);
if (err || kind == svn_node_none)
{
svn_error_clear(err);
svn_error_clear(svn_io_dir_make(subdir_full_path, APR_OS_DEFAULT, pool));
}
}
/* Helper for svn_config_ensure: see if ~/.subversion/auth/ and its
subdirs exist, try to create them, but don't throw errors on
failure. PATH is assumed to be a path to the user's private config
directory. */
static void
ensure_auth_dirs(const char *path,
apr_pool_t *pool)
{
svn_node_kind_t kind;
const char *auth_dir;
svn_error_t *err;
/* Ensure ~/.subversion/auth/ */
auth_dir = svn_dirent_join(path, SVN_CONFIG__AUTH_SUBDIR, pool);
err = svn_io_check_path(auth_dir, &kind, pool);
if (err || kind == svn_node_none)
{
svn_error_clear(err);
/* 'chmod 700' permissions: */
err = svn_io_dir_make(auth_dir,
(APR_UREAD | APR_UWRITE | APR_UEXECUTE),
pool);
if (err)
{
/* Don't try making subdirs if we can't make the top-level dir. */
svn_error_clear(err);
return;
}
}
/* If a provider exists that wants to store credentials in
~/.subversion, a subdirectory for the cred_kind must exist. */
ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SIMPLE, pool);
ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_USERNAME, pool);
ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_SERVER_TRUST, pool);
ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, pool);
}
svn_error_t *
svn_config_ensure(const char *config_dir, apr_pool_t *pool)
{
const char *path;
svn_node_kind_t kind;
svn_error_t *err;
/* Ensure that the user-specific config directory exists. */
SVN_ERR(svn_config_get_user_config_path(&path, config_dir, NULL, pool));
if (! path)
return SVN_NO_ERROR;
err = svn_io_check_resolved_path(path, &kind, pool);
if (err)
{
/* Don't throw an error, but don't continue. */
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (kind == svn_node_none)
{
err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
if (err)
{
/* Don't throw an error, but don't continue. */
svn_error_clear(err);
return SVN_NO_ERROR;
}
}
else if (kind == svn_node_file)
{
/* Somebody put a file where the config directory should be.
Wacky. Let's bail. */
return SVN_NO_ERROR;
}
/* Else, there's a configuration directory. */
/* If we get errors trying to do things below, just stop and return
success. There's no _need_ to init a config directory if
something's preventing it. */
/** If non-existent, try to create a number of auth/ subdirectories. */
ensure_auth_dirs(path, pool);
/** Ensure that the `README.txt' file exists. **/
SVN_ERR(svn_config_get_user_config_path
(&path, config_dir, SVN_CONFIG__USR_README_FILE, pool));
if (! path) /* highly unlikely, since a previous call succeeded */
return SVN_NO_ERROR;
err = svn_io_check_path(path, &kind, pool);
if (err)
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (kind == svn_node_none)
{
apr_file_t *f;
const char *contents =
"This directory holds run-time configuration information for Subversion" NL
"clients. The configuration files all share the same syntax, but you" NL
"should examine a particular file to learn what configuration" NL
"directives are valid for that file." NL
"" NL
"The syntax is standard INI format:" NL
"" NL
" - Empty lines, and lines starting with '#', are ignored." NL
" The first significant line in a file must be a section header." NL
"" NL
" - A section starts with a section header, which must start in" NL
" the first column:" NL
"" NL
" [section-name]" NL
"" NL
" - An option, which must always appear within a section, is a pair" NL
" (name, value). There are two valid forms for defining an" NL
" option, both of which must start in the first column:" NL
"" NL
" name: value" NL
" name = value" NL
"" NL
" Whitespace around the separator (:, =) is optional." NL
"" NL
" - Section and option names are case-insensitive, but case is" NL
" preserved." NL
"" NL
" - An option's value may be broken into several lines. The value" NL
" continuation lines must start with at least one whitespace." NL
" Trailing whitespace in the previous line, the newline character" NL
" and the leading whitespace in the continuation line is compressed" NL
" into a single space character." NL
"" NL
" - All leading and trailing whitespace around a value is trimmed," NL
" but the whitespace within a value is preserved, with the" NL
" exception of whitespace around line continuations, as" NL
" described above." NL
"" NL
" - When a value is a boolean, any of the following strings are" NL
" recognised as truth values (case does not matter):" NL
"" NL
" true false" NL
" yes no" NL
" on off" NL
" 1 0" NL
"" NL
" - When a value is a list, it is comma-separated. Again, the" NL
" whitespace around each element of the list is trimmed." NL
"" NL
" - Option values may be expanded within a value by enclosing the" NL
" option name in parentheses, preceded by a percent sign and" NL
" followed by an 's':" NL
"" NL
" %(name)s" NL
"" NL
" The expansion is performed recursively and on demand, during" NL
" svn_option_get. The name is first searched for in the same" NL
" section, then in the special [DEFAULT] section. If the name" NL
" is not found, the whole '%(name)s' placeholder is left" NL
" unchanged." NL
"" NL
" Any modifications to the configuration data invalidate all" NL
" previously expanded values, so that the next svn_option_get" NL
" will take the modifications into account." NL
"" NL
"The syntax of the configuration files is a subset of the one used by" NL
"Python's ConfigParser module; see" NL
"" NL
" https://docs.python.org/3/library/configparser.html" NL
"" NL
"Configuration data in the Windows registry" NL
"==========================================" NL
"" NL
"On Windows, configuration data may also be stored in the registry. The" NL
"functions svn_config_read and svn_config_merge will read from the" NL
"registry when passed file names of the form:" NL
"" NL
" REGISTRY:<hive>/path/to/config-key" NL
"" NL
"The REGISTRY: prefix must be in upper case. The <hive> part must be" NL
"one of:" NL
"" NL
" HKLM for HKEY_LOCAL_MACHINE" NL
" HKCU for HKEY_CURRENT_USER" NL
"" NL
"The values in config-key represent the options in the [DEFAULT] section."NL
"The keys below config-key represent other sections, and their values" NL
"represent the options. Only values of type REG_SZ whose name doesn't" NL
"start with a '#' will be used; other values, as well as the keys'" NL
"default values, will be ignored." NL
"" NL
"" NL
"File locations" NL
"==============" NL
"" NL
"Typically, Subversion uses two config directories, one for site-wide" NL
"configuration," NL
"" NL
" Unix:" NL
" /etc/subversion/servers" NL
" /etc/subversion/config" NL
" /etc/subversion/hairstyles" NL
" Windows:" NL
" %ALLUSERSPROFILE%\\Application Data\\Subversion\\servers" NL
" %ALLUSERSPROFILE%\\Application Data\\Subversion\\config" NL
" %ALLUSERSPROFILE%\\Application Data\\Subversion\\hairstyles" NL
" REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Servers" NL
" REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Config" NL
" REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Hairstyles" NL
"" NL
"and one for per-user configuration:" NL
"" NL
" Unix:" NL
" ~/.subversion/servers" NL
" ~/.subversion/config" NL
" ~/.subversion/hairstyles" NL
" Windows:" NL
" %APPDATA%\\Subversion\\servers" NL
" %APPDATA%\\Subversion\\config" NL
" %APPDATA%\\Subversion\\hairstyles" NL
" REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Servers" NL
" REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Config" NL
" REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Hairstyles" NL
"" NL;
err = svn_io_file_open(&f, path,
(APR_WRITE | APR_CREATE | APR_EXCL),
APR_OS_DEFAULT,
pool);
if (! err)
{
SVN_ERR(svn_io_file_write_full(f, contents,
strlen(contents), NULL, pool));
SVN_ERR(svn_io_file_close(f, pool));
}
svn_error_clear(err);
}
/** Ensure that the `servers' file exists. **/
SVN_ERR(svn_config_get_user_config_path
(&path, config_dir, SVN_CONFIG_CATEGORY_SERVERS, pool));
if (! path) /* highly unlikely, since a previous call succeeded */
return SVN_NO_ERROR;
err = svn_io_check_path(path, &kind, pool);
if (err)
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (kind == svn_node_none)
{
apr_file_t *f;
const char *contents =
"### This file specifies server-specific parameters," NL
"### including HTTP proxy information, HTTP timeout settings," NL
"### and authentication settings." NL
"###" NL
"### The currently defined server options are:" NL
"### http-proxy-host Proxy host for HTTP connection" NL
"### http-proxy-port Port number of proxy host service" NL
"### http-proxy-username Username for auth to proxy service"NL
"### http-proxy-password Password for auth to proxy service"NL
"### http-proxy-exceptions List of sites that do not use proxy"
NL
"### http-timeout Timeout for HTTP requests in seconds"
NL
"### http-compression Whether to compress HTTP requests" NL
"### (yes/no/auto)." NL
"### http-max-connections Maximum number of parallel server" NL
"### connections to use for any given" NL
"### HTTP operation." NL
"### http-chunked-requests Whether to use chunked transfer" NL
"### encoding for HTTP requests body." NL
"### http-auth-types List of HTTP authentication types."NL
"### ssl-authority-files List of files, each of a trusted CA"
NL
"### ssl-trust-default-ca Trust the system 'default' CAs" NL
"### ssl-client-cert-file PKCS#12 format client certificate file"
NL
"### ssl-client-cert-password Client Key password, if needed." NL
"### ssl-pkcs11-provider Name of PKCS#11 provider to use." NL
"### http-library Which library to use for http/https"
NL
"### connections." NL
"### http-bulk-updates Whether to request bulk update" NL
"### responses or to fetch each file" NL
"### in an individual request. " NL
"### store-passwords Specifies whether passwords used" NL
"### to authenticate against a" NL
"### Subversion server may be cached" NL
"### to disk in any way." NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"### store-plaintext-passwords Specifies whether passwords may" NL
"### be cached on disk unencrypted." NL
#endif
"### store-ssl-client-cert-pp Specifies whether passphrase used" NL
"### to authenticate against a client" NL
"### certificate may be cached to disk" NL
"### in any way" NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"### store-ssl-client-cert-pp-plaintext" NL
"### Specifies whether client cert" NL
"### passphrases may be cached on disk" NL
"### unencrypted (i.e., as plaintext)." NL
#endif
"### store-auth-creds Specifies whether any auth info" NL
"### (passwords, server certs, etc.)" NL
"### may be cached to disk." NL
"### username Specifies the default username." NL
"###" NL
"### Set store-passwords to 'no' to avoid storing new passwords on" NL
"### disk in any way, including in password stores. It defaults to" NL
"### 'yes', but Subversion will never save your password to disk in" NL
"### plaintext unless explicitly configured to do so." NL
"###" NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"### Set store-plaintext-passwords to 'no' to avoid storing new" NL
"### passwords in unencrypted form in the auth/ area of your config" NL
"### directory. Set it to 'yes' to allow Subversion to store" NL
"### unencrypted passwords in the auth/ area. The default is" NL
"### 'ask', which means that Subversion will ask you before" NL
"### saving a password to disk in unencrypted form. Note that" NL
"### this option has no effect if either 'store-passwords' or " NL
"### 'store-auth-creds' is set to 'no'." NL
"###" NL
#endif
"### Set store-ssl-client-cert-pp to 'no' to avoid storing new ssl" NL
"### client certificate passphrases in the auth/ area of your" NL
"### config directory. It defaults to 'yes', but Subversion will" NL
"### never save your passphrase to disk in plaintext unless" NL
"### explicitly configured to do so." NL
"###" NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"### Set store-ssl-client-cert-pp-plaintext to 'no' to avoid storing"NL
"### new passphrases in unencrypted form in the auth/ area of your" NL
"### config directory. Set it to 'yes' to allow Subversion to" NL
"### store unencrypted passphrases in the auth/ area. The default" NL
"### is 'ask', which means that Subversion will prompt before" NL
"### saving a passphrase to disk in unencrypted form. Note that" NL
"### this option has no effect if either 'store-auth-creds' or " NL
"### 'store-ssl-client-cert-pp' is set to 'no'." NL
"###" NL
#endif
"### Set store-auth-creds to 'no' to avoid storing any new Subversion"
NL
"### credentials in the auth/ area of your config directory." NL
"### Note that this includes SSL server certificates." NL
"### It defaults to 'yes'." NL
"###" NL
"### Note that setting a 'store-*' option to 'no' only prevents" NL
"### saving of *new* passwords, passphrases or other credentials." NL
"### It does not remove or invalidate existing stored credentials." NL
"### To do that, see the 'svn auth --remove' command, or remove the" NL
"### cache files by hand as described in the Subversion book at" NL
"### http://svnbook.red-bean.com/nightly/en/svn.serverconfig.netmodel.html#svn.tour.initial.authn-cache-purge"
NL
"###" NL
"### HTTP timeouts, if given, are specified in seconds. A timeout" NL
"### of 0, i.e. zero, causes a builtin default to be used." NL
"###" NL
"### Most users will not need to explicitly set the http-library" NL
"### option, but valid values for the option include:" NL
"### 'serf': Serf-based module (Subversion 1.5 - present)" NL
"### Availability of these modules may depend on your specific" NL
"### Subversion distribution." NL
"###" NL
"### The commented-out examples below are intended only to" NL
"### demonstrate how to use this file; any resemblance to actual" NL
"### servers, living or dead, is entirely coincidental." NL
"" NL
"### In the 'groups' section, the URL of the repository you're" NL
"### trying to access is matched against the patterns on the right." NL
"### If a match is found, the server options are taken from the" NL
"### section with the corresponding name on the left." NL
"" NL
"[groups]" NL
"# group1 = *.collab.net" NL
"# othergroup = repository.blarggitywhoomph.com" NL
"# thirdgroup = *.example.com" NL
"" NL
"### Information for the first group:" NL
"# [group1]" NL
"# http-proxy-host = proxy1.some-domain-name.com" NL
"# http-proxy-port = 80" NL
"# http-proxy-username = blah" NL
"# http-proxy-password = doubleblah" NL
"# http-timeout = 60" NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"# store-plaintext-passwords = no" NL
#endif
"# username = harry" NL
"" NL
"### Information for the second group:" NL
"# [othergroup]" NL
"# http-proxy-host = proxy2.some-domain-name.com" NL
"# http-proxy-port = 9000" NL
"# No username and password for the proxy, so use the defaults below."
NL
"" NL
"### You can set default parameters in the 'global' section." NL
"### These parameters apply if no corresponding parameter is set in" NL
"### a specifically matched group as shown above. Thus, if you go" NL
"### through the same proxy server to reach every site on the" NL
"### Internet, you probably just want to put that server's" NL
"### information in the 'global' section and not bother with" NL
"### 'groups' or any other sections." NL
"###" NL
"### Most people might want to configure password caching" NL
"### parameters here, but you can also configure them per server" NL
"### group (per-group settings override global settings)." NL
"###" NL
"### If you go through a proxy for all but a few sites, you can" NL
"### list those exceptions under 'http-proxy-exceptions'. This only"NL
"### overrides defaults, not explicitly matched server names." NL
"###" NL
"### 'ssl-authority-files' is a semicolon-delimited list of files," NL
"### each pointing to a PEM-encoded Certificate Authority (CA) " NL
"### SSL certificate. See details above for overriding security " NL
"### due to SSL." NL
"[global]" NL
"# http-proxy-exceptions = *.exception.com, www.internal-site.org" NL
"# http-proxy-host = defaultproxy.whatever.com" NL
"# http-proxy-port = 7000" NL
"# http-proxy-username = defaultusername" NL
"# http-proxy-password = defaultpassword" NL
"# http-compression = auto" NL
"# No http-timeout, so just use the builtin default." NL
"# ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem" NL
"#" NL
"# Password / passphrase caching parameters:" NL
"# store-passwords = no" NL
"# store-ssl-client-cert-pp = no" NL
#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
"# store-plaintext-passwords = no" NL
"# store-ssl-client-cert-pp-plaintext = no" NL
#endif
;
err = svn_io_file_open(&f, path,
(APR_WRITE | APR_CREATE | APR_EXCL),
APR_OS_DEFAULT,
pool);
if (! err)
{
SVN_ERR(svn_io_file_write_full(f, contents,
strlen(contents), NULL, pool));
SVN_ERR(svn_io_file_close(f, pool));
}
svn_error_clear(err);
}
/** Ensure that the `config' file exists. **/
SVN_ERR(svn_config_get_user_config_path
(&path, config_dir, SVN_CONFIG_CATEGORY_CONFIG, pool));
if (! path) /* highly unlikely, since a previous call succeeded */
return SVN_NO_ERROR;
err = svn_io_check_path(path, &kind, pool);
if (err)
{
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (kind == svn_node_none)
{
apr_file_t *f;
const char *contents =
"### This file configures various client-side behaviors." NL
"###" NL
"### The commented-out examples below are intended to demonstrate" NL
"### how to use this file." NL
"" NL
"### Section for authentication and authorization customizations." NL
"[auth]" NL
"### Set password stores used by Subversion. They should be" NL
"### delimited by spaces or commas. The order of values determines" NL
"### the order in which password stores are used." NL
"### Valid password stores:" NL
"### gnome-keyring (Unix-like systems)" NL
"### kwallet (Unix-like systems)" NL
"### gpg-agent (Unix-like systems)" NL
"### keychain (Mac OS X)" NL
"### windows-cryptoapi (Windows)" NL
#ifdef SVN_HAVE_KEYCHAIN_SERVICES
"# password-stores = keychain" NL
#elif defined(WIN32) && !defined(__MINGW32__)
"# password-stores = windows-cryptoapi" NL
#else
"# password-stores = gpg-agent,gnome-keyring,kwallet" NL
#endif
"### To disable all password stores, use an empty list:" NL
"# password-stores =" NL
#ifdef SVN_HAVE_KWALLET
"###" NL
"### Set KWallet wallet used by Subversion. If empty or unset," NL
"### then the default network wallet will be used." NL
"# kwallet-wallet =" NL
"###" NL
"### Include PID (Process ID) in Subversion application name when" NL
"### using KWallet. It defaults to 'no'." NL
"# kwallet-svn-application-name-with-pid = yes" NL
#endif
"###" NL
"### Set ssl-client-cert-file-prompt to 'yes' to cause the client" NL
"### to prompt for a path to a client cert file when the server" NL
"### requests a client cert but no client cert file is found in the" NL
"### expected place (see the 'ssl-client-cert-file' option in the" NL
"### 'servers' configuration file). Defaults to 'no'." NL
"# ssl-client-cert-file-prompt = no" NL
"###" NL
"### The rest of the [auth] section in this file has been deprecated."
NL
"### Both 'store-passwords' and 'store-auth-creds' can now be" NL
"### specified in the 'servers' file in your config directory" NL
"### and are documented there. Anything specified in this section " NL
"### is overridden by settings specified in the 'servers' file." NL
"# store-passwords = no" NL
"# store-auth-creds = no" NL
"" NL
"### Section for configuring external helper applications." NL
"[helpers]" NL
"### Set editor-cmd to the command used to invoke your text editor." NL
"### This will override the environment variables that Subversion" NL
"### examines by default to find this information ($EDITOR, " NL
"### et al)." NL
"# editor-cmd = editor (vi, emacs, notepad, etc.)" NL
"### Set diff-cmd to the absolute path of your 'diff' program." NL
"### This will override the compile-time default, which is to use" NL
"### Subversion's internal diff implementation." NL
"# diff-cmd = diff_program (diff, gdiff, etc.)" NL
"### Diff-extensions are arguments passed to an external diff" NL
"### program or to Subversion's internal diff implementation." NL
"### Set diff-extensions to override the default arguments ('-u')." NL
"# diff-extensions = -u -p" NL
"### Set diff3-cmd to the absolute path of your 'diff3' program." NL
"### This will override the compile-time default, which is to use" NL
"### Subversion's internal diff3 implementation." NL
"# diff3-cmd = diff3_program (diff3, gdiff3, etc.)" NL
"### Set diff3-has-program-arg to 'yes' if your 'diff3' program" NL
"### accepts the '--diff-program' option." NL
"# diff3-has-program-arg = [yes | no]" NL
"### Set merge-tool-cmd to the command used to invoke your external" NL
"### merging tool of choice. Subversion will pass 5 arguments to" NL
"### the specified command: base theirs mine merged wcfile" NL
"# merge-tool-cmd = merge_command" NL
"" NL
"### Section for configuring tunnel agents." NL
"[tunnels]" NL
"### Configure svn protocol tunnel schemes here. By default, only" NL
"### the 'ssh' scheme is defined. You can define other schemes to" NL
"### be used with 'svn+scheme://hostname/path' URLs. A scheme" NL
"### definition is simply a command, optionally prefixed by an" NL
"### environment variable name which can override the command if it" NL
"### is defined. The command (or environment variable) may contain" NL
"### arguments, using standard shell quoting for arguments with" NL
"### spaces. The command will be invoked as:" NL
"### <command> <hostname> svnserve -t" NL
"### (If the URL includes a username, then the hostname will be" NL
"### passed to the tunnel agent as <user>@<hostname>.) If the" NL
"### built-in ssh scheme were not predefined, it could be defined" NL
"### as:" NL
"# ssh = $SVN_SSH ssh -q --" NL
"### If you wanted to define a new 'rsh' scheme, to be used with" NL
"### 'svn+rsh:' URLs, you could do so as follows:" NL
"# rsh = rsh --" NL
"### Or, if you wanted to specify a full path and arguments:" NL
"# rsh = /path/to/rsh -l myusername --" NL
"### On Windows, if you are specifying a full path to a command," NL
"### use a forward slash (/) or a paired backslash (\\\\) as the" NL
"### path separator. A single backslash will be treated as an" NL
"### escape for the following character." NL
"" NL
"### Section for configuring miscellaneous Subversion options." NL
"[miscellany]" NL
"### Set global-ignores to a set of whitespace-delimited globs" NL
"### which Subversion will ignore in its 'status' output, and" NL
"### while importing or adding files and directories." NL
"### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'." NL
"# global-ignores = " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_1 NL
"# " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_2 NL
"### Set log-encoding to the default encoding for log messages" NL
"# log-encoding = latin1" NL
"### Set use-commit-times to make checkout/update/switch/revert" NL
"### put last-committed timestamps on every file touched." NL
"# use-commit-times = yes" NL
"### Set no-unlock to prevent 'svn commit' from automatically" NL
"### releasing locks on files." NL
"# no-unlock = yes" NL
"### Set mime-types-file to a MIME type registry file, used to" NL
"### provide hints to Subversion's MIME type auto-detection" NL
"### algorithm." NL
"# mime-types-file = /path/to/mime.types" NL
"### Set preserved-conflict-file-exts to a whitespace-delimited" NL
"### list of patterns matching file extensions which should be" NL
"### preserved in generated conflict file names. By default," NL
"### conflict files use custom extensions." NL
"# preserved-conflict-file-exts = doc ppt xls od?" NL
"### Set enable-auto-props to 'yes' to enable automatic properties" NL
"### for 'svn add' and 'svn import', it defaults to 'no'." NL
"### Automatic properties are defined in the section 'auto-props'." NL
"# enable-auto-props = yes" NL
#ifdef SVN_HAVE_LIBMAGIC
"### Set enable-magic-file to 'no' to disable magic file detection" NL
"### of the file type when automatically setting svn:mime-type. It" NL
"### defaults to 'yes' if magic file support is possible." NL
"# enable-magic-file = yes" NL
#endif
"### Set interactive-conflicts to 'no' to disable interactive" NL
"### conflict resolution prompting. It defaults to 'yes'." NL
"# interactive-conflicts = no" NL
"### Set memory-cache-size to define the size of the memory cache" NL
"### used by the client when accessing a FSFS repository via" NL
"### ra_local (the file:// scheme). The value represents the number" NL
"### of MB used by the cache." NL
"# memory-cache-size = 16" NL
"### Set diff-ignore-content-type to 'yes' to cause 'svn diff' to" NL
"### attempt to show differences of all modified files regardless" NL
"### of their MIME content type. By default, Subversion will only" NL
"### attempt to show differences for files believed to have human-" NL
"### readable (non-binary) content. This option is especially" NL
"### useful when Subversion is configured (via the 'diff-cmd'" NL
"### option) to employ an external differencing tool which is able" NL
"### to show meaningful differences for binary file formats. [New" NL
"### in 1.9]" NL
"# diff-ignore-content-type = no" NL
"" NL
"### Section for configuring automatic properties." NL
"[auto-props]" NL
"### The format of the entries is:" NL
"### file-name-pattern = propname[=value][;propname[=value]...]" NL
"### The file-name-pattern can contain wildcards (such as '*' and" NL
"### '?'). All entries which match (case-insensitively) will be" NL
"### applied to the file. Note that auto-props functionality" NL
"### must be enabled, which is typically done by setting the" NL
"### 'enable-auto-props' option." NL
"# *.c = svn:eol-style=native" NL
"# *.cpp = svn:eol-style=native" NL
"# *.h = svn:keywords=Author Date Id Rev URL;svn:eol-style=native" NL
"# *.dsp = svn:eol-style=CRLF" NL
"# *.dsw = svn:eol-style=CRLF" NL
"# *.sh = svn:eol-style=native;svn:executable" NL
"# *.txt = svn:eol-style=native;svn:keywords=Author Date Id Rev URL;"NL
"# *.png = svn:mime-type=image/png" NL
"# *.jpg = svn:mime-type=image/jpeg" NL
"# Makefile = svn:eol-style=native" NL
"" NL
"### Section for configuring working copies." NL
"[working-copy]" NL
"### Set to a list of the names of specific clients that should use" NL
"### exclusive SQLite locking of working copies. This increases the"NL
"### performance of the client but prevents concurrent access by" NL
"### other clients. Third-party clients may also support this" NL
"### option." NL
"### Possible values:" NL
"### svn (the command line client)" NL
"# exclusive-locking-clients =" NL
"### Set to true to enable exclusive SQLite locking of working" NL
"### copies by all clients using the 1.8 APIs. Enabling this may" NL
"### cause some clients to fail to work properly. This does not have"NL
"### to be set for exclusive-locking-clients to work." NL
"# exclusive-locking = false" NL
"### Set the SQLite busy timeout in milliseconds: the maximum time" NL
"### the client waits to get access to the SQLite database before" NL
"### returning an error. The default is 10000, i.e. 10 seconds." NL
"### Longer values may be useful when exclusive locking is enabled." NL
"# busy-timeout = 10000" NL
;
err = svn_io_file_open(&f, path,
(APR_WRITE | APR_CREATE | APR_EXCL),
APR_OS_DEFAULT,
pool);
if (! err)
{
SVN_ERR(svn_io_file_write_full(f, contents,
strlen(contents), NULL, pool));
SVN_ERR(svn_io_file_close(f, pool));
}
svn_error_clear(err);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_config_get_user_config_path(const char **path,
const char *config_dir,
const char *fname,
apr_pool_t *pool)
{
*path= NULL;
/* Note that even if fname is null, svn_dirent_join_many will DTRT. */
if (config_dir)
{
*path = svn_dirent_join_many(pool, config_dir, fname, SVN_VA_NULL);
return SVN_NO_ERROR;
}
#ifdef WIN32
{
const char *folder;
SVN_ERR(svn_config__win_config_path(&folder, FALSE, pool, pool));
if (! folder)
return SVN_NO_ERROR;
*path = svn_dirent_join_many(pool, folder,
SVN_CONFIG__SUBDIRECTORY, fname, SVN_VA_NULL);
}
#elif defined(__HAIKU__)
{
char folder[B_PATH_NAME_LENGTH];
status_t error = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false,
folder, sizeof(folder));
if (error)
return SVN_NO_ERROR;
*path = svn_dirent_join_many(pool, folder,
SVN_CONFIG__USR_DIRECTORY, fname,
SVN_VA_NULL);
}
#else /* ! WIN32 && !__HAIKU__ */
{
const char *homedir = svn_user_get_homedir(pool);
if (! homedir)
return SVN_NO_ERROR;
*path = svn_dirent_join_many(pool,
homedir,
SVN_CONFIG__USR_DIRECTORY, fname, SVN_VA_NULL);
}
#endif /* WIN32 */
return SVN_NO_ERROR;
}