1
0
mirror of https://github.com/irssi/irssi.git synced 2024-06-16 06:25:24 +00:00
irssi/src/lib-config/write.c

396 lines
9.5 KiB
C

/*
write.c : irssi configuration - write configuration file
Copyright (C) 1999 Timo Sirainen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
/* maximum length of lines in config file before splitting them to multiple lines */
#define MAX_CHARS_IN_LINE 70
#define CONFIG_INDENT_SIZE 2
static const char *indent_block = " "; /* needs to be the same size as CONFIG_INDENT_SIZE! */
/* write needed amount of indentation to the start of the line */
static int config_write_indent(CONFIG_REC *rec)
{
int n;
for (n = 0; n < rec->tmp_indent_level/CONFIG_INDENT_SIZE; n++) {
if (g_io_channel_write_chars(rec->handle, indent_block, CONFIG_INDENT_SIZE,
NULL, NULL) == G_IO_STATUS_ERROR)
return -1;
}
return 0;
}
static int config_write_str(CONFIG_REC *rec, const char *str)
{
const char *strpos, *p;
g_return_val_if_fail(rec != NULL, -1);
g_return_val_if_fail(str != NULL, -1);
strpos = str;
while (*strpos != '\0') {
/* fill the indentation */
if (rec->tmp_last_lf && rec->tmp_indent_level > 0 &&
*str != '\n') {
if (config_write_indent(rec) == -1)
return -1;
}
p = strchr(strpos, '\n');
if (p == NULL) {
if (g_io_channel_write_chars(rec->handle, strpos, strlen(strpos),
NULL, NULL) == G_IO_STATUS_ERROR)
return -1;
strpos = "";
rec->tmp_last_lf = FALSE;
} else {
if (g_io_channel_write_chars(rec->handle, strpos, (int) (p-strpos)+1,
NULL, NULL) == G_IO_STATUS_ERROR)
return -1;
strpos = p+1;
rec->tmp_last_lf = TRUE;
}
}
return 0;
}
static int config_has_specials(const char *text)
{
g_return_val_if_fail(text != NULL, FALSE);
while (*text != '\0') {
if (!i_isalnum(*text) && *text != '_')
return TRUE;
text++;
}
return FALSE;
}
static char *config_escape_string(const char *text)
{
GString *str;
char *ret;
g_return_val_if_fail(text != NULL, NULL);
str = g_string_new("\"");
while (*text != '\0') {
if (*text == '\\' || *text == '"')
g_string_append_printf(str, "\\%c", *text);
else if ((unsigned char) *text < 32)
g_string_append_printf(str, "\\%03o", *text);
else
g_string_append_c(str, *text);
text++;
}
g_string_append_c(str, '"');
ret = str->str;
g_string_free(str, FALSE);
return ret;
}
static int config_write_word(CONFIG_REC *rec, const char *word, int string)
{
char *str;
int ret;
g_return_val_if_fail(rec != NULL, -1);
g_return_val_if_fail(word != NULL, -1);
if (!string && !config_has_specials(word))
return config_write_str(rec, word);
str = config_escape_string(word);
ret = config_write_str(rec, str);
g_free(str);
return ret;
}
static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds);
static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds)
{
g_return_val_if_fail(rec != NULL, -1);
g_return_val_if_fail(node != NULL, -1);
switch (node->type) {
case NODE_TYPE_KEY:
if (config_write_word(rec, node->key, FALSE) == -1 ||
config_write_str(rec, " = ") == -1 ||
config_write_word(rec, node->value, TRUE) == -1)
return -1;
break;
case NODE_TYPE_VALUE:
if (config_write_word(rec, node->value, TRUE) == -1)
return -1;
break;
case NODE_TYPE_BLOCK:
/* key = { */
if (node->key != NULL) {
if (config_write_word(rec, node->key, FALSE) == -1 ||
config_write_str(rec, " = ") == -1)
return -1;
}
if (config_write_str(rec, line_feeds ? "{\n" : "{ ") == -1)
return -1;
/* ..block.. */
rec->tmp_indent_level += CONFIG_INDENT_SIZE;
if (config_write_block(rec, node, FALSE, line_feeds) == -1)
return -1;
rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
/* }; */
if (config_write_str(rec, "}") == -1)
return -1;
break;
case NODE_TYPE_LIST:
/* key = ( */
if (node->key != NULL) {
if (config_write_word(rec, node->key, FALSE) == -1 ||
config_write_str(rec, " = ") == -1)
return -1;
}
if (config_write_str(rec, line_feeds ? "(\n" : "( ") == -1)
return -1;
/* ..list.. */
rec->tmp_indent_level += CONFIG_INDENT_SIZE;
if (config_write_block(rec, node, TRUE, line_feeds) == -1)
return -1;
rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
/* ); */
if (config_write_str(rec, ")") == -1)
return -1;
break;
case NODE_TYPE_COMMENT:
if (node->value == NULL)
break;
if (config_write_str(rec, "#") == -1 ||
config_write_str(rec, node->value) == -1)
return -1;
break;
}
return 0;
}
static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node);
static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
{
int len;
switch (node->type) {
case NODE_TYPE_KEY:
/* "key = value; " */
len = 5 + strlen(node->key) + strlen(node->value);
break;
case NODE_TYPE_VALUE:
/* "value, " */
len = 2 + strlen(node->value);
break;
case NODE_TYPE_BLOCK:
case NODE_TYPE_LIST:
/* "{ list }; " */
len = 6;
if (node->key != NULL) len += strlen(node->key);
len += config_block_get_length(rec, node);
break;
default:
/* comments always split the line */
len = 1000;
break;
}
return len;
}
/* return the number of characters `node' and it's subnodes take
if written to file */
static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
{
GSList *tmp;
int len;
len = 0;
for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
CONFIG_NODE *subnode = tmp->data;
len += config_node_get_length(rec, subnode);
if (len > MAX_CHARS_IN_LINE) return len;
}
return len;
}
/* check if `node' and it's subnodes fit in one line in the config file */
static int config_block_fit_one_line(CONFIG_REC *rec, CONFIG_NODE *node)
{
g_return_val_if_fail(rec != NULL, 0);
g_return_val_if_fail(node != NULL, 0);
return rec->tmp_indent_level +
config_node_get_length(rec, node) <= MAX_CHARS_IN_LINE;
}
static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds)
{
GSList *tmp;
int list_line_feeds, node_line_feeds;
g_return_val_if_fail(rec != NULL, -1);
g_return_val_if_fail(node != NULL, -1);
g_return_val_if_fail(is_node_list(node), -1);
list_line_feeds = !config_block_fit_one_line(rec, node);
if (!line_feeds && list_line_feeds)
config_write_str(rec, "\n");
for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
CONFIG_NODE *subnode = tmp->data;
node_line_feeds = !line_feeds ? FALSE : !config_block_fit_one_line(rec, subnode);
if (config_write_node(rec, subnode, node_line_feeds) == -1)
return -1;
if (subnode->type == NODE_TYPE_COMMENT)
config_write_str(rec, "\n");
else if (list) {
if (tmp->next != NULL)
config_write_str(rec, list_line_feeds ? ",\n" : ", ");
else
config_write_str(rec, list_line_feeds ? "\n" : " ");
} else {
config_write_str(rec, list_line_feeds ? ";\n" : "; ");
}
}
return 0;
}
int config_write(CONFIG_REC *rec, const char *fname, int create_mode)
{
int ret;
int fd;
int save_errno;
const char *base_name;
char *tmp_name = NULL;
char *dest_name = NULL;
#if !defined(_POSIX_VERSION) || _POSIX_VERSION < 200809L
char resolved_path[PATH_MAX] = { 0 };
#endif
g_return_val_if_fail(rec != NULL, -1);
g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
base_name = fname != NULL ? fname : rec->fname;
/* expand all symlinks; else we may replace a symlink with a regular file */
#if !defined(_POSIX_VERSION) || _POSIX_VERSION < 200809L
/* variable path length not supported by glibc < 2.3, Solaris < 11 */
errno = 0;
if ((dest_name = realpath(base_name, resolved_path)) != NULL) {
dest_name = g_strdup(dest_name);
}
#else
dest_name = realpath(base_name, NULL);
#endif
if (dest_name == NULL) {
if (errno == ENOENT) {
dest_name = g_strdup(base_name);
errno = 0;
} else {
config_error(rec, g_strerror(errno));
ret = -1;
goto out;
}
}
tmp_name = g_strdup_printf("%s.XXXXXX", dest_name);
fd = g_mkstemp_full(tmp_name,
O_WRONLY | O_TRUNC | O_CREAT,
create_mode != -1 ? create_mode : rec->create_mode);
if (fd == -1) {
config_error(rec, g_strerror(errno));
ret = -1;
goto out;
}
rec->handle = g_io_channel_unix_new(fd);
g_io_channel_set_encoding(rec->handle, NULL, NULL);
g_io_channel_set_close_on_unref(rec->handle, TRUE);
rec->tmp_indent_level = 0;
rec->tmp_last_lf = TRUE;
ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
save_errno = errno;
if (ret == -1) {
/* write error */
unlink(tmp_name);
config_error(rec, save_errno == 0 ? "bug" : g_strerror(save_errno));
goto out;
}
ret = fsync(fd);
save_errno = errno;
if (ret == -1) {
unlink(tmp_name);
config_error(rec, g_strerror(errno));
goto out;
}
g_io_channel_unref(rec->handle);
rec->handle = NULL;
if (rename(tmp_name, dest_name) == -1) {
unlink(tmp_name);
config_error(rec, g_strerror(errno));
goto out;
}
out:
if (rec->handle) {
g_io_channel_unref(rec->handle);
rec->handle = NULL;
}
g_free(tmp_name);
g_free(dest_name);
return ret;
}