summaryrefslogtreecommitdiffstats
path: root/src/lib-config/write.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-config/write.c')
-rw-r--r--src/lib-config/write.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/src/lib-config/write.c b/src/lib-config/write.c
new file mode 100644
index 0000000..b7c6edb
--- /dev/null
+++ b/src/lib-config/write.c
@@ -0,0 +1,395 @@
+/*
+ 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;
+}