2000-04-14 07:27:14 -04:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
|
2007-05-08 14:41:10 -04:00
|
|
|
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.
|
2000-04-14 07:27:14 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
#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++) {
|
2009-01-16 12:12:27 -05:00
|
|
|
if (g_io_channel_write_chars(rec->handle, indent_block, CONFIG_INDENT_SIZE,
|
|
|
|
NULL, NULL) == G_IO_STATUS_ERROR)
|
2000-04-14 07:27:14 -04:00
|
|
|
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 */
|
2001-01-07 03:05:14 -05:00
|
|
|
if (rec->tmp_last_lf && rec->tmp_indent_level > 0 &&
|
|
|
|
*str != '\n') {
|
2000-04-14 07:27:14 -04:00
|
|
|
if (config_write_indent(rec) == -1)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = strchr(strpos, '\n');
|
|
|
|
if (p == NULL) {
|
2009-01-16 12:12:27 -05:00
|
|
|
if (g_io_channel_write_chars(rec->handle, strpos, strlen(strpos),
|
|
|
|
NULL, NULL) == G_IO_STATUS_ERROR)
|
2000-04-14 07:27:14 -04:00
|
|
|
return -1;
|
|
|
|
strpos = "";
|
|
|
|
rec->tmp_last_lf = FALSE;
|
|
|
|
} else {
|
2009-01-16 12:12:27 -05:00
|
|
|
if (g_io_channel_write_chars(rec->handle, strpos, (int) (p-strpos)+1,
|
|
|
|
NULL, NULL) == G_IO_STATUS_ERROR)
|
2000-04-14 07:27:14 -04:00
|
|
|
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') {
|
2002-01-27 15:45:59 -05:00
|
|
|
if (!i_isalnum(*text) && *text != '_')
|
2000-04-14 07:27:14 -04:00
|
|
|
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 == '"')
|
2009-02-08 12:22:42 -05:00
|
|
|
g_string_append_printf(str, "\\%c", *text);
|
2000-04-14 07:27:14 -04:00
|
|
|
else if ((unsigned char) *text < 32)
|
2009-02-08 12:22:42 -05:00
|
|
|
g_string_append_printf(str, "\\%03o", *text);
|
2000-04-14 07:27:14 -04:00
|
|
|
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) {
|
2000-04-26 04:10:09 -04:00
|
|
|
if (config_write_word(rec, node->key, FALSE) == -1 ||
|
2000-04-14 07:27:14 -04:00
|
|
|
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) {
|
2000-04-26 04:10:09 -04:00
|
|
|
if (config_write_word(rec, node->key, FALSE) == -1 ||
|
2000-04-14 07:27:14 -04:00
|
|
|
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)
|
|
|
|
{
|
2000-12-16 18:38:12 -05:00
|
|
|
int ret;
|
2009-01-16 12:12:27 -05:00
|
|
|
int fd;
|
2018-04-08 14:52:09 -04:00
|
|
|
int save_errno;
|
2018-09-01 18:12:17 -04:00
|
|
|
const char *base_name;
|
2018-08-12 06:55:47 -04:00
|
|
|
char *tmp_name = NULL;
|
|
|
|
char *dest_name = NULL;
|
2023-08-08 01:59:31 -04:00
|
|
|
#if !defined(_POSIX_VERSION) || _POSIX_VERSION < 200809L
|
|
|
|
char resolved_path[PATH_MAX] = { 0 };
|
|
|
|
#endif
|
2000-12-16 18:38:12 -05:00
|
|
|
|
2000-04-14 07:27:14 -04:00
|
|
|
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);
|
|
|
|
|
2018-09-01 18:12:17 -04:00
|
|
|
base_name = fname != NULL ? fname : rec->fname;
|
|
|
|
|
2018-08-12 06:55:47 -04:00
|
|
|
/* expand all symlinks; else we may replace a symlink with a regular file */
|
2023-08-08 01:59:31 -04:00
|
|
|
#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);
|
2019-05-13 09:15:46 -04:00
|
|
|
}
|
2023-08-08 01:59:31 -04:00
|
|
|
#else
|
|
|
|
dest_name = realpath(base_name, NULL);
|
|
|
|
#endif
|
2019-05-13 09:15:46 -04:00
|
|
|
|
2018-08-12 06:55:47 -04:00
|
|
|
if (dest_name == NULL) {
|
2018-09-01 18:12:17 -04:00
|
|
|
if (errno == ENOENT) {
|
|
|
|
dest_name = g_strdup(base_name);
|
|
|
|
errno = 0;
|
|
|
|
} else {
|
|
|
|
config_error(rec, g_strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
2018-08-12 06:55:47 -04:00
|
|
|
}
|
|
|
|
|
2018-04-08 14:52:09 -04:00
|
|
|
tmp_name = g_strdup_printf("%s.XXXXXX", dest_name);
|
|
|
|
|
|
|
|
fd = g_mkstemp_full(tmp_name,
|
2000-04-14 07:27:14 -04:00
|
|
|
O_WRONLY | O_TRUNC | O_CREAT,
|
|
|
|
create_mode != -1 ? create_mode : rec->create_mode);
|
2018-04-08 14:52:09 -04:00
|
|
|
if (fd == -1) {
|
|
|
|
config_error(rec, g_strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
2000-04-14 07:27:14 -04:00
|
|
|
|
2009-01-16 12:12:27 -05:00
|
|
|
rec->handle = g_io_channel_unix_new(fd);
|
|
|
|
g_io_channel_set_encoding(rec->handle, NULL, NULL);
|
2009-10-23 17:17:03 -04:00
|
|
|
g_io_channel_set_close_on_unref(rec->handle, TRUE);
|
2018-04-08 14:52:09 -04:00
|
|
|
|
2000-04-14 07:27:14 -04:00
|
|
|
rec->tmp_indent_level = 0;
|
|
|
|
rec->tmp_last_lf = TRUE;
|
2000-12-16 18:38:12 -05:00
|
|
|
ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
|
2018-04-08 14:52:09 -04:00
|
|
|
save_errno = errno;
|
|
|
|
|
2000-12-16 18:38:12 -05:00
|
|
|
if (ret == -1) {
|
2000-04-14 07:27:14 -04:00
|
|
|
/* write error */
|
2018-04-08 14:52:09 -04:00
|
|
|
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;
|
2000-04-14 07:27:14 -04:00
|
|
|
}
|
|
|
|
|
2009-01-16 12:12:27 -05:00
|
|
|
g_io_channel_unref(rec->handle);
|
|
|
|
rec->handle = NULL;
|
2000-04-14 07:27:14 -04:00
|
|
|
|
2018-08-12 06:55:47 -04:00
|
|
|
if (rename(tmp_name, dest_name) == -1) {
|
2018-04-08 14:52:09 -04:00
|
|
|
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);
|
2018-08-12 06:55:47 -04:00
|
|
|
g_free(dest_name);
|
2018-04-08 14:52:09 -04:00
|
|
|
|
2000-12-16 18:38:12 -05:00
|
|
|
return ret;
|
2000-04-14 07:27:14 -04:00
|
|
|
}
|