| @@ -0,0 +1,104 @@ | ||
| NOTE: This is just a slightly modified file taken from EPIC's help. | ||
| '!' at start of the line means that the feature doesn't work yet.. | ||
| Special Variables and Expandos | ||
| Irssi supports a number of reserved, dynamic variables, sometimes | ||
| referred to as expandos. They are special in that the client is | ||
| constantly updating their values automatically. There are also | ||
| numerous variable modifiers available. | ||
| Modifier Description | ||
| $variable A normal variable, expanding to the first match of: | ||
| | 1) an internal SET variable | ||
| | 2) an environment variable | ||
| $[num]variable Expands to the variables value, with 'num' width. If | ||
| | the number is negative, the value is right-aligned. | ||
| | The value is padded to meet the width with the | ||
| | character given after number (default is space). | ||
| | The value is truncated to specified width unless | ||
| | '!' character precedes the number. | ||
| $#variable Expands to the number of words in $variable. If $variable | ||
| | is omitted, it assumes $* | ||
| $@variable Expands to the number of characters in $variable. if | ||
| | $variable is omitted, it assumes $* | ||
| $($subvariable) This is somewhat similar to a pointer, in that the | ||
| | value of $subvar is taken as the name of the | ||
| | variable to expand to. Nesting is allowed. | ||
| ${expression} Permits the value to be embedded in another string | ||
| | unambiguously. | ||
| ! $!history! Expands to a matching entry in the client's command | ||
| | history, wildcards allowed. | ||
| ! $"some text" Uses 'text' as an input prompt, and returns whatever | ||
| | is typed next. This usage is deprecated, use the | ||
| | INPUT command instead. | ||
| ! $'some text' Same as $"text" except that it only returns the first | ||
| | next typed character. | ||
| Whenever an alias is called, these expandos are set to the arguments passed | ||
| to it. If none of these expandos are used in the alias, or the $() form | ||
| shown above, any arguments passed will automatically be appended to the last | ||
| command in the alias. | ||
| Expando Description | ||
| $* expands to all arguments passed to an alias | ||
| $n expands to argument 'n' passed to an alias (counting from zero) | ||
| $n-m expands to arguments 'n' through 'm' passed to an alias | ||
| $n- expands to all arguments from 'n' on passed to an alias | ||
| $-m expands to all arguments up to 'm' passed to an alias | ||
| $~ expands to the last argument passed to an alias | ||
| These variables are set and updated dynamically by the client. The case of | ||
| $A .. $Z is important. | ||
| Variable Description | ||
| ! $, last person who sent you a MSG | ||
| ! $. last person to whom you sent a MSG | ||
| ! $: last person to join a channel you are on | ||
| ! $; last person to send a public message to a channel you are on | ||
| $A text of your AWAY message, if any | ||
| ! $B body of last MSG you sent | ||
| $C current channel | ||
| ! $D last person that NOTIFY detected a signon for | ||
| ! $E idle time | ||
| ! $F time client was started, $time() format | ||
| ! $H current server numeric being processed | ||
| ! $I channel you were last INVITEd to | ||
| $J client version text string | ||
| $K current value of CMDCHARS | ||
| ! $L current contents of the input line | ||
| $M modes of current channel, if any | ||
| $N current nickname | ||
| ! $O value of STATUS_OPER if you are an irc operator | ||
| $P if you are a channel operator in $C, expands to a '@' | ||
| $Q nickname of whomever you are QUERYing | ||
| ! $R version of current server | ||
| $S current server name | ||
| $T target of current input (channel or QUERY nickname) | ||
| ! $U value of cutbuffer | ||
| ! $V client release date (numeric version string) | ||
| $W current working directory | ||
| ! $X your /userhost $N address (user@host) | ||
| $Y value of REALNAME | ||
| $Z time of day (hh:mm) | ||
| $$ a literal '$' | ||
| For example, assume you have the following alias: | ||
| alias blah msg $D Hi there! | ||
| If /blah is passed any arguments, they will automatically be appended to the | ||
| MSG text. For example: | ||
| /blah oops /* command as entered */ | ||
| "Hi there! oops" /* text sent to $D */ | ||
| Another useful form is ${}. In general, variables can be embedded inside | ||
| strings without problems, assuming the surrounding text could not be | ||
| misinterpreted as part of the variable name. This form guarantees that | ||
| surrounding text will not affect the expression's return value. | ||
| /eval echo foo$Nfoo /* breaks, looks for $nfoo */ | ||
| /eval echo foo${N}foo /* ${N} returns current nickname */ | ||
| fooYourNickfoo /* returned by above command */ | ||
| @@ -1,9 +1,15 @@ | ||
| noinst_LTLIBRARIES = libirssi_config.la | ||
| INCLUDES = $(GLIB_CFLAGS) | ||
| INCLUDES = \ | ||
| $(GLIB_CFLAGS) \ | ||
| -I$(top_srcdir)/src | ||
| libirssi_config_la_SOURCES = \ | ||
| irssi-config.c | ||
| get.c \ | ||
| set.c \ | ||
| parse.c \ | ||
| write.c | ||
| noinst_HEADERS = \ | ||
| irssi-config.h | ||
| iconfig.h \ | ||
| module.h |
| @@ -0,0 +1,256 @@ | ||
| /* | ||
| get.c : irssi configuration - get settings from memory | ||
| 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| */ | ||
| #include "module.h" | ||
| CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key) | ||
| { | ||
| GSList *tmp; | ||
| g_return_val_if_fail(node != NULL, NULL); | ||
| g_return_val_if_fail(key != NULL, NULL); | ||
| g_return_val_if_fail(is_node_list(node), NULL); | ||
| for (tmp = node->value; tmp != NULL; tmp = tmp->next) { | ||
| CONFIG_NODE *node = tmp->data; | ||
| if (node->key != NULL && g_strcasecmp(node->key, key) == 0) | ||
| return node; | ||
| } | ||
| return NULL; | ||
| } | ||
| /* find the section from node - if not found create it unless new_type is -1. | ||
| you can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */ | ||
| CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type) | ||
| { | ||
| CONFIG_NODE *node; | ||
| g_return_val_if_fail(rec != NULL, NULL); | ||
| g_return_val_if_fail(parent != NULL, NULL); | ||
| g_return_val_if_fail(is_node_list(parent), NULL); | ||
| node = key == NULL ? NULL : config_node_find(parent, key); | ||
| if (node != NULL) { | ||
| g_return_val_if_fail(new_type == -1 || new_type == node->type, NULL); | ||
| return node; | ||
| } | ||
| if (new_type == -1) | ||
| return NULL; | ||
| node = g_new0(CONFIG_NODE, 1); | ||
| parent->value = g_slist_append(parent->value, node); | ||
| node->type = new_type; | ||
| node->key = key == NULL ? NULL : g_strdup(key); | ||
| return node; | ||
| } | ||
| /* find the section with the whole path. | ||
| create the path if necessary `create' is TRUE. */ | ||
| CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create) | ||
| { | ||
| CONFIG_NODE *node; | ||
| char **list, **tmp; | ||
| int is_list, new_type; | ||
| g_return_val_if_fail(rec != NULL, NULL); | ||
| if (section == NULL || *section == '\0') | ||
| return rec->mainnode; | ||
| /* check if it already exists in cache */ | ||
| node = g_hash_table_lookup(rec->cache, section); | ||
| if (node != NULL) return node; | ||
| new_type = -1; | ||
| node = rec->mainnode; | ||
| list = g_strsplit(section, "/", -1); | ||
| for (tmp = list; *tmp != NULL; tmp++) { | ||
| is_list = **tmp == '('; | ||
| if (create) new_type = is_list ? NODE_TYPE_LIST : NODE_TYPE_BLOCK; | ||
| node = config_node_section(rec, node, *tmp + is_list, new_type); | ||
| if (node == NULL) return NULL; | ||
| } | ||
| g_strfreev(list); | ||
| /* save to cache */ | ||
| g_hash_table_insert(rec->cache, g_strdup(section), node); | ||
| return node; | ||
| } | ||
| char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def) | ||
| { | ||
| CONFIG_NODE *parent, *node; | ||
| char *path; | ||
| g_return_val_if_fail(rec != NULL, (char *) def); | ||
| g_return_val_if_fail(section != NULL, (char *) def); | ||
| g_return_val_if_fail(key != NULL, (char *) def); | ||
| /* check if it already exists in cache */ | ||
| path = g_strconcat(section, "/", key, NULL); | ||
| node = g_hash_table_lookup(rec->cache, path); | ||
| if (node != NULL) | ||
| g_free(path); | ||
| else { | ||
| parent = config_node_traverse(rec, section, FALSE); | ||
| node = parent == NULL ? NULL : | ||
| config_node_find(parent, key); | ||
| /* save to cache */ | ||
| if (node != NULL) | ||
| g_hash_table_insert(rec->cache, path, node); | ||
| else | ||
| g_free(path); | ||
| } | ||
| return (node == NULL || !has_node_value(node)) ? (char *) def : node->value; | ||
| } | ||
| int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def) | ||
| { | ||
| char *str; | ||
| str = config_get_str(rec, section, key, NULL); | ||
| if (str == NULL) return def; | ||
| return atoi(str); | ||
| } | ||
| int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def) | ||
| { | ||
| char *str; | ||
| str = config_get_str(rec, section, key, NULL); | ||
| if (str == NULL) return def; | ||
| return toupper(*str) == 'T' || toupper(*str) == 'Y'; | ||
| } | ||
| /* Return value of key `value_key' from list item where `key' is `value' */ | ||
| const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key) | ||
| { | ||
| CONFIG_NODE *node; | ||
| node = config_list_find_node(rec, section, key, value, value_key); | ||
| return node != NULL && node->type == NODE_TYPE_KEY ? | ||
| node->value : NULL; | ||
| } | ||
| /* Like config_list_find(), but return node instead of it's value */ | ||
| CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key) | ||
| { | ||
| CONFIG_NODE *node, *keynode; | ||
| GSList *tmp; | ||
| g_return_val_if_fail(rec != NULL, NULL); | ||
| g_return_val_if_fail(section != NULL, NULL); | ||
| g_return_val_if_fail(key != NULL, NULL); | ||
| g_return_val_if_fail(value_key != NULL, NULL); | ||
| node = config_node_traverse(rec, section, FALSE); | ||
| if (node == NULL || !is_node_list(node)) return NULL; | ||
| for (tmp = node->value; tmp != NULL; tmp = tmp->next) { | ||
| node = tmp->data; | ||
| if (node->type != NODE_TYPE_BLOCK) | ||
| continue; | ||
| /* key matches value? */ | ||
| keynode = config_node_find(node, key); | ||
| if (keynode == NULL || keynode->type != NODE_TYPE_KEY || | ||
| g_strcasecmp(keynode->value, value) != 0) continue; | ||
| return config_node_find(node, value_key); | ||
| } | ||
| return NULL; | ||
| } | ||
| char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def) | ||
| { | ||
| CONFIG_NODE *node; | ||
| node = config_node_find(parent, key); | ||
| return (node == NULL || !has_node_value(node)) ? def : node->value; | ||
| } | ||
| int config_node_get_int(CONFIG_NODE *parent, const char *key, int def) | ||
| { | ||
| char *str; | ||
| str = config_node_get_str(parent, key, NULL); | ||
| if (str == NULL) return def; | ||
| return atoi(str); | ||
| } | ||
| int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def) | ||
| { | ||
| char *str; | ||
| str = config_node_get_str(parent, key, NULL); | ||
| if (str == NULL) return def; | ||
| return toupper(*str) == 'T' || toupper(*str) == 'Y' || | ||
| (toupper(*str) == 'O' && toupper(str[1]) == 'N'); | ||
| } | ||
| /* Get the value of keys `key' and `key_value' and put them to | ||
| `ret_key' and `ret_value'. Returns -1 if not found. */ | ||
| int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value) | ||
| { | ||
| CONFIG_NODE *keynode, *valuenode; | ||
| GSList *tmp; | ||
| g_return_val_if_fail(node != NULL, -1); | ||
| g_return_val_if_fail(key != NULL, -1); | ||
| g_return_val_if_fail(value_key != NULL, -1); | ||
| g_return_val_if_fail(ret_key != NULL, -1); | ||
| g_return_val_if_fail(ret_value != NULL, -1); | ||
| for (tmp = node->value; tmp != NULL; tmp = tmp->next) { | ||
| node = tmp->data; | ||
| if (node->type != NODE_TYPE_BLOCK) | ||
| continue; | ||
| keynode = config_node_find(node, key); | ||
| if (keynode == NULL || keynode->type != NODE_TYPE_KEY) | ||
| continue; | ||
| valuenode = config_node_find(node, value_key); | ||
| *ret_key = keynode->key; | ||
| *ret_value = valuenode != NULL && valuenode->type == NODE_TYPE_KEY ? | ||
| valuenode->value : NULL; | ||
| return 0; | ||
| } | ||
| return -1; | ||
| } |
| @@ -0,0 +1,136 @@ | ||
| #ifndef __ICONFIG_H | ||
| #define __ICONFIG_H | ||
| enum { | ||
| NODE_TYPE_KEY, | ||
| NODE_TYPE_VALUE, | ||
| NODE_TYPE_BLOCK, | ||
| NODE_TYPE_LIST, | ||
| NODE_TYPE_COMMENT, | ||
| }; | ||
| #define has_node_value(a) \ | ||
| ((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE) | ||
| #define is_node_list(a) \ | ||
| ((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST) | ||
| typedef struct { | ||
| int type; | ||
| char *key; | ||
| void *value; | ||
| } CONFIG_NODE; | ||
| /* a = { x=y; y=z; } | ||
| node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes | ||
| nodes: (node2, node3) | ||
| node2: type = NODE_TYPE_KEY, key = "x", value = (char *) "y" | ||
| node3: type = NODE_TYPE_KEY, key = "y", value = (char *) "z" | ||
| b = ( a, { b=c; d=e; } ) | ||
| node1: type = NODE_TYPE_LIST, key = "b", value = (GSList *) nodes | ||
| nodes: (node2, node3) | ||
| node2: type = NODE_TYPE_VALUE, key = NULL, value = (char *) "a" | ||
| node4: type = NODE_TYPE_BLOCK, key = NULL, value = (GSList *) nodes2 | ||
| nodes2: (node4, node5) | ||
| node4: type = NODE_TYPE_KEY, key = "b", value = (char *) "c" | ||
| node5: type = NODE_TYPE_KEY, key = "d", value = (char *) "e" | ||
| Comments node has key=NULL and value is the comment line. Empty lines are | ||
| also in comments so they won't be forgotten when the config file is | ||
| written. | ||
| */ | ||
| struct _config_rec { | ||
| char *fname; | ||
| int handle; | ||
| int create_mode; | ||
| char *last_error; | ||
| CONFIG_NODE *mainnode; | ||
| GHashTable *cache; | ||
| GScanner *scanner; | ||
| /* while writing to configuration file.. */ | ||
| int tmp_indent_level; /* indentation position */ | ||
| int tmp_last_lf; /* last character was a line feed */ | ||
| }; | ||
| typedef struct _config_rec CONFIG_REC; | ||
| /* Open configuration. The file is created if it doesn't exist, unless | ||
| `create_mode' is -1. `fname' can be NULL if you just want to use | ||
| config_parse_data() */ | ||
| CONFIG_REC *config_open(const char *fname, int create_mode); | ||
| /* Release all memory used by configuration */ | ||
| void config_close(CONFIG_REC *rec); | ||
| /* Change file name of config file */ | ||
| void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode); | ||
| /* Parse configuration file */ | ||
| int config_parse(CONFIG_REC *rec); | ||
| /* Parse configuration found from `data'. `input_name' is specifies the | ||
| "configuration name" which is displayed in error messages. */ | ||
| int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name); | ||
| /* Write configuration file. Write to `fname' if it's not NULL. | ||
| If `create_mode' is -1, use the one that was given to config_open(). */ | ||
| int config_write(CONFIG_REC *rec, const char *fname, int create_mode); | ||
| #define config_last_error(rec) \ | ||
| (rec)->last_error | ||
| /* Getting values | ||
| `section' is something like "maingroup/key/subkey", or with lists | ||
| "maingroup/(list/subkey" | ||
| `def' is returned if the value is not found. */ | ||
| char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def); | ||
| int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def); | ||
| int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def); | ||
| /* Return value of key `value_key' from list item where `key' is `value' */ | ||
| const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key); | ||
| /* Like config_list_find(), but return node instead of it's value */ | ||
| CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key); | ||
| /* Setting values */ | ||
| int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value); | ||
| int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value); | ||
| int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value); | ||
| /* Handling the configuration directly with nodes - | ||
| useful when you need to read all values in a block/list. */ | ||
| CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key); | ||
| /* Find the section from node - if not found create it unless new_type is -1. | ||
| You can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */ | ||
| CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type); | ||
| /* Find the section with the whole path. | ||
| Create the path if necessary `create' is TRUE. */ | ||
| CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create); | ||
| /* Get the value of keys `key' and `key_value' and put them to | ||
| `ret_key' and `ret_value'. Returns -1 if not found. */ | ||
| int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value); | ||
| char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def); | ||
| int config_node_get_int(CONFIG_NODE *parent, const char *key, int def); | ||
| int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def); | ||
| void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value); | ||
| void config_node_set_int(CONFIG_NODE *parent, const char *key, int value); | ||
| void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value); | ||
| /* add/change the value of the `key' */ | ||
| void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value); | ||
| /* remove one node from block/list. | ||
| ..set_str() with value = NULL does the same. */ | ||
| void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node); | ||
| /* clear the entire configuration */ | ||
| void config_nodes_remove_all(CONFIG_REC *rec); | ||
| #endif |
| @@ -0,0 +1,6 @@ | ||
| #include "common.h" | ||
| #include "iconfig.h" | ||
| /* private */ | ||
| int config_error(CONFIG_REC *rec, const char *msg); | ||
| @@ -0,0 +1,335 @@ | ||
| /* | ||
| parse.c : irssi configuration - parse 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| */ | ||
| #include "module.h" | ||
| int config_error(CONFIG_REC *rec, const char *msg) | ||
| { | ||
| g_free_and_null(rec->last_error); | ||
| rec->last_error = g_strdup(msg); | ||
| return -1; | ||
| } | ||
| static int node_add_comment(CONFIG_NODE *parent, const char *str) | ||
| { | ||
| CONFIG_NODE *node; | ||
| g_return_val_if_fail(parent != NULL, -1); | ||
| if (!is_node_list(parent)) | ||
| return -1; | ||
| node = g_new0(CONFIG_NODE, 1); | ||
| node->type = NODE_TYPE_COMMENT; | ||
| node->value = str == NULL ? NULL : g_strdup(str); | ||
| parent->value = g_slist_append(parent->value, node); | ||
| return 0; | ||
| } | ||
| /* same as g_scanner_get_next_token() except skips and reads the comments */ | ||
| static void config_parse_get_token(GScanner *scanner, CONFIG_NODE *node) | ||
| { | ||
| int prev_empty = FALSE; | ||
| for (;;) { | ||
| g_scanner_get_next_token(scanner); | ||
| if (scanner->token == G_TOKEN_COMMENT_SINGLE) | ||
| node_add_comment(node, scanner->value.v_string); | ||
| else if (scanner->token == '\n') { | ||
| if (prev_empty) node_add_comment(node, NULL); | ||
| } else { | ||
| if (scanner->token == G_TOKEN_INT) { | ||
| scanner->token = G_TOKEN_STRING; | ||
| #undef g_strdup_printf /* This is free'd by GLib itself */ | ||
| scanner->value.v_string = g_strdup_printf("%lu", scanner->value.v_int); | ||
| #ifdef MEM_DEBUG | ||
| #define g_strdup_printf ig_strdup_printf | ||
| #endif | ||
| } | ||
| break; | ||
| } | ||
| prev_empty = TRUE; | ||
| } | ||
| } | ||
| /* same as g_scanner_peek_next_token() except skips and reads the comments */ | ||
| static void config_parse_peek_token(GScanner *scanner, CONFIG_NODE *node) | ||
| { | ||
| int prev_empty = FALSE; | ||
| for (;;) { | ||
| g_scanner_peek_next_token(scanner); | ||
| if (scanner->next_token == G_TOKEN_COMMENT_SINGLE) | ||
| node_add_comment(node, scanner->next_value.v_string); | ||
| else if (scanner->next_token == '\n') { | ||
| if (prev_empty) node_add_comment(node, NULL); | ||
| } else | ||
| break; | ||
| prev_empty = TRUE; | ||
| g_scanner_get_next_token(scanner); | ||
| } | ||
| } | ||
| /* get optional token, optionally warn if it's missing */ | ||
| static void config_parse_warn_missing(CONFIG_REC *rec, CONFIG_NODE *node, int expected_token, int print_warning) | ||
| { | ||
| config_parse_peek_token(rec->scanner, node); | ||
| if (rec->scanner->next_token == expected_token) { | ||
| g_scanner_get_next_token(rec->scanner); | ||
| return; | ||
| } | ||
| if (print_warning) | ||
| g_scanner_warn(rec->scanner, "Warning: missing '%c'", expected_token); | ||
| } | ||
| static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect); | ||
| static int config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node) | ||
| { | ||
| CONFIG_NODE *newnode; | ||
| int print_warning; | ||
| char *key, last_char; | ||
| g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR); | ||
| g_return_val_if_fail(node != NULL, G_TOKEN_ERROR); | ||
| config_parse_get_token(rec->scanner, node); | ||
| last_char = node->type == NODE_TYPE_LIST ? ',' : ';'; | ||
| /* key */ | ||
| key = NULL; | ||
| if (node->type != NODE_TYPE_LIST && | ||
| (rec->scanner->token == G_TOKEN_STRING)) { | ||
| key = g_strdup(rec->scanner->value.v_string); | ||
| config_parse_get_token(rec->scanner, node); | ||
| if (rec->scanner->token != '=') | ||
| return '='; | ||
| config_parse_get_token(rec->scanner, node); | ||
| } | ||
| switch (rec->scanner->token) { | ||
| case G_TOKEN_STRING: | ||
| /* value */ | ||
| config_node_set_str(node, key, rec->scanner->value.v_string); | ||
| g_free_not_null(key); | ||
| print_warning = TRUE; | ||
| if (node->type == NODE_TYPE_LIST) { | ||
| /* if it's last item it doesn't need comma */ | ||
| config_parse_peek_token(rec->scanner, node); | ||
| if (rec->scanner->next_token == ')') | ||
| print_warning = FALSE; | ||
| } | ||
| config_parse_warn_missing(rec, node, last_char, print_warning); | ||
| break; | ||
| case '{': | ||
| /* block */ | ||
| if (key == NULL && node->type != NODE_TYPE_LIST) | ||
| return G_TOKEN_ERROR; | ||
| newnode = config_node_section(rec, node, key, NODE_TYPE_BLOCK); | ||
| config_parse_loop(rec, newnode, '}'); | ||
| g_free_not_null(key); | ||
| config_parse_get_token(rec->scanner, node); | ||
| if (rec->scanner->token != '}') | ||
| return '}'; | ||
| config_parse_warn_missing(rec, node, last_char, FALSE); | ||
| break; | ||
| case '(': | ||
| /* list */ | ||
| if (key == NULL) | ||
| return G_TOKEN_ERROR; | ||
| newnode = config_node_section(rec, node, key, NODE_TYPE_LIST); | ||
| config_parse_loop(rec, newnode, ')'); | ||
| g_free_not_null(key); | ||
| config_parse_get_token(rec->scanner, node); | ||
| if (rec->scanner->token != ')') | ||
| return ')'; | ||
| config_parse_warn_missing(rec, node, last_char, FALSE); | ||
| break; | ||
| default: | ||
| /* error */ | ||
| g_free_not_null(key); | ||
| return G_TOKEN_STRING; | ||
| } | ||
| return G_TOKEN_NONE; | ||
| } | ||
| static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect) | ||
| { | ||
| int expected_token; | ||
| g_return_if_fail(rec != NULL); | ||
| g_return_if_fail(node != NULL); | ||
| do { | ||
| expected_token = config_parse_symbol(rec, node); | ||
| if (expected_token != G_TOKEN_NONE) { | ||
| if (expected_token == G_TOKEN_ERROR) | ||
| expected_token = G_TOKEN_NONE; | ||
| g_scanner_unexp_token(rec->scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE); | ||
| } | ||
| config_parse_peek_token(rec->scanner, node); | ||
| } while (rec->scanner->next_token != expect && | ||
| rec->scanner->next_token != G_TOKEN_EOF); | ||
| } | ||
| static void config_parse_error_func(GScanner *scanner, char *message, int is_error) | ||
| { | ||
| CONFIG_REC *rec = scanner->user_data; | ||
| char *old; | ||
| old = rec->last_error; | ||
| rec->last_error = g_strdup_printf("%s%s:%d: %s%s\n", | ||
| old == NULL ? "" : old, | ||
| scanner->input_name, scanner->line, | ||
| is_error ? "error: " : "", | ||
| message); | ||
| g_free_not_null(old); | ||
| } | ||
| void config_parse_init(CONFIG_REC *rec, const char *name) | ||
| { | ||
| GScanner *scanner; | ||
| g_free_and_null(rec->last_error); | ||
| config_nodes_remove_all(rec); | ||
| rec->scanner = scanner = g_scanner_new(NULL); | ||
| scanner->config->skip_comment_single = FALSE; | ||
| scanner->config->cset_skip_characters = " \t"; | ||
| scanner->config->scan_binary = FALSE; | ||
| scanner->config->scan_octal = FALSE; | ||
| scanner->config->scan_float = FALSE; | ||
| scanner->config->scan_string_sq = TRUE; | ||
| scanner->config->scan_string_dq = TRUE; | ||
| scanner->config->scan_identifier_1char = TRUE; | ||
| scanner->config->identifier_2_string = TRUE; | ||
| scanner->user_data = rec; | ||
| scanner->input_name = name; | ||
| scanner->msg_handler = (GScannerMsgFunc) config_parse_error_func; | ||
| } | ||
| /* Parse configuration file */ | ||
| int config_parse(CONFIG_REC *rec) | ||
| { | ||
| g_return_val_if_fail(rec != NULL, -1); | ||
| g_return_val_if_fail(rec->fname != NULL, -1); | ||
| rec->handle = open(rec->fname, O_RDONLY); | ||
| if (rec->handle == -1) | ||
| return config_error(rec, g_strerror(errno)); | ||
| config_parse_init(rec, rec->fname); | ||
| g_scanner_input_file(rec->scanner, rec->handle); | ||
| config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF); | ||
| g_scanner_destroy(rec->scanner); | ||
| close(rec->handle); | ||
| rec->handle = -1; | ||
| return rec->last_error == NULL ? 0 : -1; | ||
| } | ||
| /* Parse configuration found from `data'. `input_name' is specifies the | ||
| "configuration name" which is displayed in error messages. */ | ||
| int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name) | ||
| { | ||
| config_parse_init(rec, input_name); | ||
| g_scanner_input_text(rec->scanner, data, strlen(data)); | ||
| config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF); | ||
| g_scanner_destroy(rec->scanner); | ||
| return rec->last_error == NULL ? 0 : -1; | ||
| } | ||
| /* Open configuration. The file is created if it doesn't exist, unless | ||
| `create_mode' is -1. `fname' can be NULL if you just want to use | ||
| config_parse_data() */ | ||
| CONFIG_REC *config_open(const char *fname, int create_mode) | ||
| { | ||
| CONFIG_REC *rec; | ||
| int f; | ||
| if (fname != NULL) { | ||
| f = open(fname, O_RDONLY | (create_mode != -1 ? O_CREAT : 0), create_mode); | ||
| if (f == -1) return NULL; | ||
| close(f); | ||
| } | ||
| rec = g_new0(CONFIG_REC, 1); | ||
| rec->fname = fname == NULL ? NULL : g_strdup(fname); | ||
| rec->handle = -1; | ||
| rec->create_mode = create_mode; | ||
| rec->mainnode = g_new0(CONFIG_NODE, 1); | ||
| rec->mainnode->type = NODE_TYPE_BLOCK; | ||
| rec->cache = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); | ||
| return rec; | ||
| } | ||
| /* Release all memory used by configuration */ | ||
| void config_close(CONFIG_REC *rec) | ||
| { | ||
| g_return_if_fail(rec != NULL); | ||
| config_nodes_remove_all(rec); | ||
| g_free(rec->mainnode); | ||
| if (rec->handle != -1) close(rec->handle); | ||
| g_hash_table_foreach(rec->cache, (GHFunc) g_free, NULL); | ||
| g_hash_table_destroy(rec->cache); | ||
| g_free_not_null(rec->last_error); | ||
| g_free(rec->fname); | ||
| g_free(rec); | ||
| } | ||
| /* Change file name of config file */ | ||
| void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode) | ||
| { | ||
| g_return_if_fail(rec != NULL); | ||
| g_return_if_fail(fname != NULL); | ||
| g_free_not_null(rec->fname); | ||
| rec->fname = g_strdup(fname); | ||
| if (create_mode != -1) | ||
| rec->create_mode = create_mode; | ||
| } |
| @@ -0,0 +1,122 @@ | ||
| /* | ||
| set.c : irssi configuration - change settings in memory | ||
| 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| */ | ||
| #include "module.h" | ||
| void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node) | ||
| { | ||
| g_return_if_fail(parent != NULL); | ||
| g_return_if_fail(node != NULL); | ||
| parent->value = g_slist_remove(parent->value, node); | ||
| switch (node->type) { | ||
| case NODE_TYPE_KEY: | ||
| case NODE_TYPE_VALUE: | ||
| case NODE_TYPE_COMMENT: | ||
| g_free_not_null(node->value); | ||
| break; | ||
| case NODE_TYPE_BLOCK: | ||
| case NODE_TYPE_LIST: | ||
| while (node->value != NULL) | ||
| config_node_remove(node, ((GSList *) node->value)->data); | ||
| break; | ||
| } | ||
| g_free_not_null(node->key); | ||
| g_free(node); | ||
| } | ||
| void config_nodes_remove_all(CONFIG_REC *rec) | ||
| { | ||
| g_return_if_fail(rec != NULL); | ||
| while (rec->mainnode->value != NULL) | ||
| config_node_remove(rec->mainnode, ((GSList *) rec->mainnode->value)->data); | ||
| } | ||
| void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value) | ||
| { | ||
| CONFIG_NODE *node; | ||
| int no_key; | ||
| g_return_if_fail(parent != NULL); | ||
| no_key = key == NULL; | ||
| node = no_key ? NULL : config_node_find(parent, key); | ||
| if (value == NULL) { | ||
| /* remove the key */ | ||
| if (node != NULL) config_node_remove(parent, node); | ||
| return; | ||
| } | ||
| if (node != NULL) | ||
| g_free(node->value); | ||
| else { | ||
| node = g_new0(CONFIG_NODE, 1); | ||
| parent->value = g_slist_append(parent->value, node); | ||
| node->type = no_key ? NODE_TYPE_VALUE : NODE_TYPE_KEY; | ||
| node->key = no_key ? NULL : g_strdup(key); | ||
| } | ||
| node->value = g_strdup(value); | ||
| } | ||
| void config_node_set_int(CONFIG_NODE *parent, const char *key, int value) | ||
| { | ||
| char str[MAX_INT_STRLEN]; | ||
| g_snprintf(str, sizeof(str), "%d", value); | ||
| return config_node_set_str(parent, key, str); | ||
| } | ||
| void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value) | ||
| { | ||
| return config_node_set_str(parent, key, value ? "yes" : "no"); | ||
| } | ||
| int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value) | ||
| { | ||
| CONFIG_NODE *parent; | ||
| g_return_val_if_fail(rec != NULL, -1); | ||
| g_return_val_if_fail(section != NULL, -1); | ||
| parent = config_node_traverse(rec, section, TRUE); | ||
| if (parent == NULL) return -1; | ||
| config_node_set_str(parent, key, value); | ||
| return 0; | ||
| } | ||
| int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value) | ||
| { | ||
| char str[MAX_INT_STRLEN]; | ||
| g_snprintf(str, sizeof(str), "%d", value); | ||
| return config_set_str(rec, section, key, str); | ||
| } | ||
| int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value) | ||
| { | ||
| return config_set_str(rec, section, key, value ? "yes" : "no"); | ||
| } |
| @@ -0,0 +1,336 @@ | ||
| /* | ||
| 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) { | ||
| 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 ((unsigned char) *text <= 32 || *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_str(rec, node->key) == -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_str(rec, node->key) == -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) | ||
| { | ||
| 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); | ||
| 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; | ||
| if (config_write_block(rec, rec->mainnode, FALSE, TRUE) == -1) { | ||
| /* write error */ | ||
| config_error(rec, errno == 0 ? "bug" : g_strerror(errno)); | ||
| return -1; | ||
| } | ||
| write(rec->handle, "\n", 1); | ||
| close(rec->handle); | ||
| rec->handle = -1; | ||
| return 0; | ||
| } |