/* 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 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 (write(rec->handle, indent_block, CONFIG_INDENT_SIZE) == -1) 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 (write(rec->handle, strpos, strlen(strpos)) == -1) return -1; strpos = ""; rec->tmp_last_lf = FALSE; } else { if (write(rec->handle, strpos, (int) (p-strpos)+1) == -1) 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 int get_octal(int decimal) { int octal, pos; octal = 0; pos = 0; while (decimal > 0) { octal += (decimal & 7)*(pos == 0 ? 1 : pos); decimal /= 8; pos += 10; } return octal; } 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_sprintfa(str, "\\%c", *text); else if ((unsigned char) *text < 32) g_string_sprintfa(str, "\\%03d", get_octal(*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; } /* Write configuration file. Write to `fname' if it's not NULL. */ int config_write(CONFIG_REC *rec, const char *fname, int create_mode) { int ret; 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); if (rec->handle != -1) close(rec->handle); rec->handle = open(fname != NULL ? fname : rec->fname, O_WRONLY | O_TRUNC | O_CREAT, create_mode != -1 ? create_mode : rec->create_mode); if (rec->handle == -1) return config_error(rec, g_strerror(errno)); rec->tmp_indent_level = 0; rec->tmp_last_lf = TRUE; ret = config_write_block(rec, rec->mainnode, FALSE, TRUE); if (ret == -1) { /* write error */ config_error(rec, errno == 0 ? "bug" : g_strerror(errno)); } close(rec->handle); rec->handle = -1; return ret; }