📄 config_file.c
字号:
/*
* config_file.c : parsing configuration files
*
* ====================================================================
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <apr_lib.h>
#include <apr_md5.h>
#include "config_impl.h"
#include "svn_io.h"
#include "svn_types.h"
#include "svn_path.h"
#include "svn_auth.h"
#include "svn_md5.h"
#include "svn_hash.h"
#include "svn_utf.h"
/* File parsing context */
typedef struct parse_context_t
{
/* This config struct and file */
svn_config_t *cfg;
const char *file;
/* The file descriptor */
FILE *fd;
/* The current line in the file */
int line;
/* Temporary strings, allocated from the temp pool */
svn_stringbuf_t *section;
svn_stringbuf_t *option;
svn_stringbuf_t *value;
/* Temporary pool parsing */
apr_pool_t *pool;
} parse_context_t;
/* Eat chars from FD 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 int
skip_whitespace (FILE* fd, int *pcount)
{
int ch = getc (fd);
int count = 0;
while (ch != EOF && ch != '\n' && apr_isspace (ch))
{
++count;
ch = getc (fd);
}
*pcount = count;
return ch;
}
/* 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 int
skip_to_eoln (FILE *fd)
{
int ch = getc (fd);
while (ch != EOF && ch != '\n')
ch = getc (fd);
return ch;
}
/* Parse a single option value */
static svn_error_t *
parse_value (int *pch, parse_context_t *ctx)
{
svn_error_t *err = SVN_NO_ERROR;
svn_boolean_t end_of_val = FALSE;
int ch;
/* Read the first line of the value */
svn_stringbuf_setempty (ctx->value);
for (ch = getc (ctx->fd); /* last ch seen was ':' or '=' in parse_option. */
ch != EOF && ch != '\n';
ch = getc (ctx->fd))
{
const char char_from_int = ch;
svn_stringbuf_appendbytes (ctx->value, &char_from_int, 1);
}
/* Leading and trailing whitespace is ignored. */
svn_stringbuf_strip_whitespace (ctx->value);
/* Look for any continuation lines. */
for (;;)
{
if (ch == EOF || end_of_val)
{
if (!ferror (ctx->fd))
{
/* At end of file. The value is complete, there can't be
any continuation lines. */
svn_config_set (ctx->cfg, ctx->section->data,
ctx->option->data, ctx->value->data);
}
break;
}
else
{
int count;
++ctx->line;
ch = skip_whitespace (ctx->fd, &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. */
ungetc (ch, ctx->fd);
end_of_val = TRUE;
}
else
{
/* This is a continuation line. Read it. */
svn_stringbuf_appendbytes (ctx->value, " ", 1);
for (;
ch != EOF && ch != '\n';
ch = getc (ctx->fd))
{
const char char_from_int = ch;
svn_stringbuf_appendbytes (ctx->value,
&char_from_int, 1);
}
/* Trailing whitespace is ignored. */
svn_stringbuf_strip_whitespace (ctx->value);
}
}
}
}
*pch = ch;
return err;
}
/* Parse a single option */
static svn_error_t *
parse_option (int *pch, parse_context_t *ctx)
{
svn_error_t *err = SVN_NO_ERROR;
int ch;
svn_stringbuf_setempty (ctx->option);
for (ch = *pch; /* Yes, the first char is relevant. */
ch != EOF && ch != ':' && ch != '=' && ch != '\n';
ch = getc (ctx->fd))
{
const char char_from_int = ch;
svn_stringbuf_appendbytes (ctx->option, &char_from_int, 1);
}
if (ch != ':' && ch != '=')
{
ch = EOF;
err = svn_error_createf (SVN_ERR_MALFORMED_FILE, NULL,
"%s:%d: Option must end with ':' or '='",
ctx->file, ctx->line);
}
else
{
/* Whitespace around the name separator is ignored. */
svn_stringbuf_strip_whitespace (ctx->option);
err = parse_value (&ch, ctx);
}
*pch = ch;
return err;
}
/* Read chars until enounter ']', 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)
{
svn_error_t *err = SVN_NO_ERROR;
int ch;
svn_stringbuf_setempty (ctx->section);
for (ch = getc (ctx->fd);
ch != EOF && ch != ']' && ch != '\n';
ch = getc (ctx->fd))
{
const char char_from_int = ch;
svn_stringbuf_appendbytes (ctx->section, &char_from_int, 1);
}
if (ch != ']')
{
ch = EOF;
err = svn_error_createf (SVN_ERR_MALFORMED_FILE, NULL,
"%s:%d: Section header must end with ']'",
ctx->file, ctx->line);
}
else
{
/* Everything from the ']' to the end of the line is ignored. */
ch = skip_to_eoln (ctx->fd);
if (ch != EOF)
++ctx->line;
}
*pch = ch;
return err;
}
svn_error_t *
svn_config__sys_config_path (const char **path_p,
const char *fname,
apr_pool_t *pool)
{
/* ### This never actually returns error in practice. Perhaps the
prototype should change? */
*path_p = NULL;
/* Note that even if fname is null, svn_path_join_many will DTRT. */
#ifdef WIN32
{
const char *folder;
SVN_ERR (svn_config__win_config_path (&folder, TRUE, pool));
*path_p = svn_path_join_many (pool, folder,
SVN_CONFIG__SUBDIRECTORY, fname, NULL);
}
#else /* ! WIN32 */
*path_p = svn_path_join_many (pool, SVN_CONFIG__SYS_DIRECTORY, fname, NULL);
#endif /* WIN32 */
return SVN_NO_ERROR;
}
svn_error_t *
svn_config__user_config_path (const char *config_dir,
const char **path_p,
const char *fname,
apr_pool_t *pool)
{
/* ### This never actually returns error in practice. Perhaps the
prototype should change? */
*path_p = NULL;
/* Note that even if fname is null, svn_path_join_many will DTRT. */
if (config_dir)
{
*path_p = svn_path_join_many(pool, config_dir, fname, NULL);
return SVN_NO_ERROR;
}
#ifdef WIN32
{
const char *folder;
SVN_ERR (svn_config__win_config_path (&folder, FALSE, pool));
*path_p = svn_path_join_many (pool, folder,
SVN_CONFIG__SUBDIRECTORY, fname, NULL);
}
#else /* ! WIN32 */
{
apr_status_t apr_err;
apr_uid_t uid;
apr_gid_t gid;
char *username, *homedir;
const char *homedir_utf8;
apr_err = apr_uid_current (&uid, &gid, pool);
if (apr_err)
return SVN_NO_ERROR;
apr_err = apr_uid_name_get (&username, uid, pool);
if (apr_err)
return SVN_NO_ERROR;
apr_err = apr_uid_homepath_get (&homedir, username, pool);
if (apr_err)
return SVN_NO_ERROR;
SVN_ERR (svn_utf_cstring_to_utf8 (&homedir_utf8, homedir, pool));
*path_p = svn_path_join_many (pool,
svn_path_canonicalize (homedir_utf8, pool),
SVN_CONFIG__USR_DIRECTORY, fname, NULL);
}
#endif /* WIN32 */
return SVN_NO_ERROR;
}
/*** Exported interfaces. ***/
#ifndef WIN32
svn_error_t *
svn_config__open_file (FILE **pfile,
const char *filename,
const char *mode,
apr_pool_t *pool)
{
const char *filename_native;
SVN_ERR (svn_utf_cstring_from_utf8 (&filename_native, filename, pool));
*pfile = fopen (filename_native, mode);
return SVN_NO_ERROR;
}
#endif /* WIN32 */
svn_error_t *
svn_config__parse_file (svn_config_t *cfg, const char *file,
svn_boolean_t must_exist)
{
apr_pool_t *pool = svn_pool_create (cfg->pool);
svn_error_t *err = SVN_NO_ERROR;
parse_context_t ctx;
int ch, count;
FILE *fd;
/* "Why," you ask yourself, "is he using stdio FILE's instead of
apr_file_t's?" The answer is simple: newline translation. For
all that it has an APR_BINARY flag, APR doesn't do newline
translation in files. The only portable way I know to get
translated text files is to use the standard stdio library. */
SVN_ERR (svn_config__open_file (&fd, file, "rt", pool));
if (fd == NULL)
{
if (errno != ENOENT)
return svn_error_createf (SVN_ERR_BAD_FILENAME, NULL,
"Can't open config file '%s'", file);
else if (must_exist && errno == ENOENT)
return svn_error_createf (SVN_ERR_BAD_FILENAME, NULL,
"Can't find config file '%s'", file);
else
return SVN_NO_ERROR;
}
ctx.cfg = cfg;
ctx.file = file;
ctx.fd = fd;
ctx.line = 1;
ctx.pool = pool;
ctx.section = svn_stringbuf_create("", ctx.pool);
ctx.option = svn_stringbuf_create("", ctx.pool);
ctx.value = svn_stringbuf_create("", ctx.pool);
do
{
ch = skip_whitespace (fd, &count);
switch (ch)
{
case '[': /* Start of section header */
if (count == 0)
err = parse_section_name (&ch, &ctx);
else
{
ch = EOF;
err = svn_error_createf (SVN_ERR_MALFORMED_FILE, NULL,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -