aboutsummaryrefslogtreecommitdiffstats
path: root/storage_xml.c
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2013-04-20 14:05:55 +0100
committerWilmer van der Gaast <wilmer@gaast.net>2013-04-20 14:05:55 +0100
commite31e5b8f340a162180830dbe42dd438e59591cfd (patch)
tree029bbb166fda2e9b98a041039338835beb35a08e /storage_xml.c
parent9b2a8c10b61540c3c6892a4de7f52bf8657d455e (diff)
parentbfafb99e6162b72e0f1ca7639de05f2b7bb3b23c (diff)
Merging "storage" branch which I wrote long ago. It separates generation of
XML-formatted user configs from disk I/O so we can try to start using other mechanisms to store them (a REST API or something, for example).
Diffstat (limited to 'storage_xml.c')
-rw-r--r--storage_xml.c654
1 files changed, 236 insertions, 418 deletions
diff --git a/storage_xml.c b/storage_xml.c
index 0525fef5..10cb6495 100644
--- a/storage_xml.c
+++ b/storage_xml.c
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2006 Wilmer van der Gaast and others *
+ * Copyright 2002-2012 Wilmer van der Gaast and others *
\********************************************************************/
/* Storage backend that uses an XMLish format for all data. */
@@ -28,14 +28,9 @@
#include "base64.h"
#include "arc.h"
#include "md5.h"
+#include "xmltree.h"
-#if GLIB_CHECK_VERSION(2,8,0)
#include <glib/gstdio.h>
-#else
-/* GLib < 2.8.0 doesn't have g_access, so just use the system access(). */
-#include <unistd.h>
-#define g_access access
-#endif
typedef enum
{
@@ -46,373 +41,201 @@ typedef enum
} xml_pass_st;
/* To make it easier later when extending the format: */
-#define XML_FORMAT_VERSION 1
+#define XML_FORMAT_VERSION "1"
struct xml_parsedata
{
irc_t *irc;
- char *current_setting;
- account_t *current_account;
- irc_channel_t *current_channel;
- set_t **current_set_head;
- char *given_nick;
+ char given_nick[MAX_NICK_LENGTH+1];
char *given_pass;
- xml_pass_st pass_st;
- int unknown_tag;
};
-static char *xml_attr( const gchar **attr_names, const gchar **attr_values, const gchar *key )
-{
- int i;
-
- for( i = 0; attr_names[i]; i ++ )
- if( g_strcasecmp( attr_names[i], key ) == 0 )
- return (char*) attr_values[i];
-
- return NULL;
-}
-
-static void xml_destroy_xd( gpointer data )
+static void xml_init( void )
{
- struct xml_parsedata *xd = data;
-
- g_free( xd->given_nick );
- g_free( xd->given_pass );
- g_free( xd );
+ if( g_access( global.conf->configdir, F_OK ) != 0 )
+ log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir );
+ else if( g_access( global.conf->configdir, F_OK ) != 0 ||
+ g_access( global.conf->configdir, W_OK ) != 0 )
+ log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir );
}
-static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
+static void handle_settings( struct xt_node *node, set_t **head )
{
- struct xml_parsedata *xd = data;
- irc_t *irc = xd->irc;
+ struct xt_node *c;
- if( xd->unknown_tag > 0 )
- {
- xd->unknown_tag ++;
- }
- else if( g_strcasecmp( element_name, "user" ) == 0 )
+ for( c = node->children; ( c = xt_find_node( c, "setting" ) ); c = c->next )
{
- char *nick = xml_attr( attr_names, attr_values, "nick" );
- char *pass = xml_attr( attr_names, attr_values, "password" );
- int st;
+ char *name = xt_find_attr( c, "name" );
- if( !nick || !pass )
- {
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
- }
- else if( ( st = md5_verify_password( xd->given_pass, pass ) ) == -1 )
- {
- xd->pass_st = XML_PASS_WRONG;
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Error while decoding password attribute" );
- }
- else if( st == 0 )
- {
- if( xd->pass_st != XML_PASS_CHECK_ONLY )
- xd->pass_st = XML_PASS_OK;
- }
- else
- {
- xd->pass_st = XML_PASS_WRONG;
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Password mismatch" );
- }
- }
- else if( xd->pass_st < XML_PASS_OK )
- {
- /* Let's not parse anything else if we only have to check
- the password. */
- }
- else if( g_strcasecmp( element_name, "account" ) == 0 )
- {
- char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag;
- char *pass_b64 = NULL;
- unsigned char *pass_cr = NULL;
- int pass_len;
- struct prpl *prpl = NULL;
-
- handle = xml_attr( attr_names, attr_values, "handle" );
- pass_b64 = xml_attr( attr_names, attr_values, "password" );
- server = xml_attr( attr_names, attr_values, "server" );
- autoconnect = xml_attr( attr_names, attr_values, "autoconnect" );
- tag = xml_attr( attr_names, attr_values, "tag" );
-
- protocol = xml_attr( attr_names, attr_values, "protocol" );
- if( protocol )
- prpl = find_protocol( protocol );
+ if( !name )
+ continue;
- if( !handle || !pass_b64 || !protocol )
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
- else if( !prpl )
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Unknown protocol: %s", protocol );
- else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) &&
- arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 )
- {
- xd->current_account = account_add( irc->b, prpl, handle, password );
- if( server )
- set_setstr( &xd->current_account->set, "server", server );
- if( autoconnect )
- set_setstr( &xd->current_account->set, "auto_connect", autoconnect );
- if( tag )
- set_setstr( &xd->current_account->set, "tag", tag );
- }
- else
+ if( strcmp( node->name, "account" ) == 0 )
{
- /* Actually the _decode functions don't even return error codes,
- but maybe they will later... */
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Error while decrypting account password" );
+ set_t *s = set_find( head, name );
+ if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) )
+ continue; /* U can't touch this! */
}
-
- g_free( pass_cr );
- g_free( password );
+ set_setstr( head, name, c->text );
}
- else if( g_strcasecmp( element_name, "setting" ) == 0 )
+}
+
+static xt_status handle_account( struct xt_node *node, gpointer data )
+{
+ struct xml_parsedata *xd = data;
+ char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag;
+ char *pass_b64 = NULL;
+ unsigned char *pass_cr = NULL;
+ int pass_len;
+ struct prpl *prpl = NULL;
+ account_t *acc;
+ struct xt_node *c;
+
+ handle = xt_find_attr( node, "handle" );
+ pass_b64 = xt_find_attr( node, "password" );
+ server = xt_find_attr( node, "server" );
+ autoconnect = xt_find_attr( node, "autoconnect" );
+ tag = xt_find_attr( node, "tag" );
+
+ protocol = xt_find_attr( node, "protocol" );
+ if( protocol )
+ prpl = find_protocol( protocol );
+
+ if( !handle || !pass_b64 || !protocol || !prpl )
+ return XT_ABORT;
+ else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) &&
+ arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 )
{
- char *setting;
-
- if( xd->current_setting )
- {
- g_free( xd->current_setting );
- xd->current_setting = NULL;
- }
-
- if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) )
- {
- if( xd->current_channel != NULL )
- xd->current_set_head = &xd->current_channel->set;
- else if( xd->current_account != NULL )
- xd->current_set_head = &xd->current_account->set;
- else
- xd->current_set_head = &xd->irc->b->set;
-
- xd->current_setting = g_strdup( setting );
- }
- else
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
+ acc = account_add( xd->irc->b, prpl, handle, password );
+ if( server )
+ set_setstr( &acc->set, "server", server );
+ if( autoconnect )
+ set_setstr( &acc->set, "auto_connect", autoconnect );
+ if( tag )
+ set_setstr( &acc->set, "tag", tag );
}
- else if( g_strcasecmp( element_name, "buddy" ) == 0 )
+ else
+ return XT_ABORT;
+
+ g_free( pass_cr );
+ g_free( password );
+
+ handle_settings( node, &acc->set );
+
+ for( c = node->children; ( c = xt_find_node( c, "buddy" ) ); c = c->next )
{
char *handle, *nick;
- handle = xml_attr( attr_names, attr_values, "handle" );
- nick = xml_attr( attr_names, attr_values, "nick" );
+ handle = xt_find_attr( c, "handle" );
+ nick = xt_find_attr( c, "nick" );
- if( xd->current_account && handle && nick )
- {
- nick_set_raw( xd->current_account, handle, nick );
- }
+ if( handle && nick )
+ nick_set_raw( acc, handle, nick );
else
- {
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
- }
- }
- else if( g_strcasecmp( element_name, "channel" ) == 0 )
- {
- char *name, *type;
-
- name = xml_attr( attr_names, attr_values, "name" );
- type = xml_attr( attr_names, attr_values, "type" );
-
- if( !name || !type )
- {
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
- return;
- }
-
- /* The channel may exist already, for example if it's &bitlbee.
- Also, it's possible that the user just reconnected and the
- IRC client already rejoined all channels it was in. They
- should still get the right settings. */
- if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) ||
- ( xd->current_channel = irc_channel_new( irc, name ) ) )
- set_setstr( &xd->current_channel->set, "type", type );
- }
- /* Backward compatibility: Keep this around for a while for people
- switching from BitlBee 1.2.4+. */
- else if( g_strcasecmp( element_name, "chat" ) == 0 )
- {
- char *handle, *channel;
-
- handle = xml_attr( attr_names, attr_values, "handle" );
- channel = xml_attr( attr_names, attr_values, "channel" );
-
- if( xd->current_account && handle && channel )
- {
- irc_channel_t *ic;
-
- if( ( ic = irc_channel_new( irc, channel ) ) &&
- set_setstr( &ic->set, "type", "chat" ) &&
- set_setstr( &ic->set, "chat_type", "room" ) &&
- set_setstr( &ic->set, "account", xd->current_account->tag ) &&
- set_setstr( &ic->set, "room", handle ) )
- {
- /* Try to pick up some settings where possible. */
- xd->current_channel = ic;
- }
- else if( ic )
- irc_channel_free( ic );
- }
- else
- {
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "Missing attributes for %s element", element_name );
- }
- }
- else
- {
- xd->unknown_tag ++;
- irc_rootmsg( irc, "Warning: Unknown XML tag found in configuration file (%s). "
- "This may happen when downgrading BitlBee versions. "
- "This tag will be skipped and the information will be lost "
- "once you save your settings.", element_name );
- /*
- g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
- "Unkown element: %s", element_name );
- */
- }
+ return XT_ABORT;
+ }
+ return XT_HANDLED;
}
-static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
+static xt_status handle_channel( struct xt_node *node, gpointer data )
{
struct xml_parsedata *xd = data;
+ irc_channel_t *ic;
+ char *name, *type;
- if( xd->unknown_tag > 0 )
- {
- xd->unknown_tag --;
- }
- else if( g_strcasecmp( element_name, "setting" ) == 0 && xd->current_setting )
- {
- g_free( xd->current_setting );
- xd->current_setting = NULL;
- }
- else if( g_strcasecmp( element_name, "account" ) == 0 )
- {
- xd->current_account = NULL;
- }
- else if( g_strcasecmp( element_name, "channel" ) == 0 ||
- g_strcasecmp( element_name, "chat" ) == 0 )
- {
- xd->current_channel = NULL;
- }
-}
-
-static void xml_text( GMarkupParseContext *ctx, const gchar *text_orig, gsize text_len, gpointer data, GError **error )
-{
- char text[text_len+1];
- struct xml_parsedata *xd = data;
+ name = xt_find_attr( node, "name" );
+ type = xt_find_attr( node, "type" );
- strncpy( text, text_orig, text_len );
- text[text_len] = 0;
+ if( !name || !type )
+ return XT_ABORT;
- if( xd->pass_st < XML_PASS_OK )
- {
- /* Let's not parse anything else if we only have to check
- the password, or if we didn't get the chance to check it
- yet. */
- }
- else if( g_strcasecmp( g_markup_parse_context_get_element( ctx ), "setting" ) == 0 && xd->current_setting )
- {
- if( xd->current_account )
- {
- set_t *s = set_find( xd->current_set_head, xd->current_setting );
- if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) )
- {
- g_free( xd->current_setting );
- xd->current_setting = NULL;
- return;
- }
- }
- set_setstr( xd->current_set_head, xd->current_setting, (char*) text );
- g_free( xd->current_setting );
- xd->current_setting = NULL;
- }
+ /* The channel may exist already, for example if it's &bitlbee.
+ Also, it's possible that the user just reconnected and the
+ IRC client already rejoined all channels it was in. They
+ should still get the right settings. */
+ if( ( ic = irc_channel_by_name( xd->irc, name ) ) ||
+ ( ic = irc_channel_new( xd->irc, name ) ) )
+ set_setstr( &ic->set, "type", type );
+
+ handle_settings( node, &ic->set );
+
+ return XT_HANDLED;
}
-GMarkupParser xml_parser =
-{
- xml_start_element,
- xml_end_element,
- xml_text,
- NULL,
- NULL
+static const struct xt_handler_entry handlers[] = {
+ { "account", "user", handle_account, },
+ { "channel", "user", handle_channel, },
+ { NULL, NULL, NULL, },
};
-static void xml_init( void )
-{
- if( g_access( global.conf->configdir, F_OK ) != 0 )
- log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir );
- else if( g_access( global.conf->configdir, F_OK ) != 0 ||
- g_access( global.conf->configdir, W_OK ) != 0 )
- log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir );
-}
-
static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action )
{
- GMarkupParseContext *ctx;
- struct xml_parsedata *xd;
- char *fn, buf[512];
- GError *gerr = NULL;
+ struct xml_parsedata xd[1];
+ char *fn, buf[2048];
int fd, st;
+ struct xt_parser *xp;
+ struct xt_node *node;
+ storage_status_t ret = STORAGE_OTHER_ERROR;
- xd = g_new0( struct xml_parsedata, 1 );
xd->irc = irc;
- xd->given_nick = g_strdup( my_nick );
- xd->given_pass = g_strdup( password );
- xd->pass_st = action;
+ strncpy( xd->given_nick, my_nick, MAX_NICK_LENGTH );
+ xd->given_nick[MAX_NICK_LENGTH] = '\0';
nick_lc( xd->given_nick );
+ xd->given_pass = password;
- fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" );
+ fn = g_strconcat( global.conf->configdir, xd->given_nick, ".xml", NULL );
if( ( fd = open( fn, O_RDONLY ) ) < 0 )
{
- xml_destroy_xd( xd );
- g_free( fn );
- return STORAGE_NO_SUCH_USER;
+ ret = STORAGE_NO_SUCH_USER;
+ goto error;
}
- g_free( fn );
-
- ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd );
+ xp = xt_new( handlers, xd );
while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 )
{
- if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr )
+ st = xt_feed( xp, buf, st );
+ if( st != 1 )
+ break;
+ }
+ close( fd );
+ if( st != 0 )
+ goto error;
+
+ node = xp->root;
+ if( node == NULL || node->next != NULL || strcmp( node->name, "user" ) != 0 )
+ goto error;
+
+ {
+ char *nick = xt_find_attr( node, "nick" );
+ char *pass = xt_find_attr( node, "password" );
+
+ if( !nick || !pass )
{
- xml_pass_st pass_st = xd->pass_st;
-
- g_markup_parse_context_free( ctx );
- close( fd );
-
- if( pass_st == XML_PASS_WRONG )
- {
- g_clear_error( &gerr );
- return STORAGE_INVALID_PASSWORD;
- }
- else
- {
- if( gerr && irc )
- irc_rootmsg( irc, "Error from XML-parser: %s", gerr->message );
-
- g_clear_error( &gerr );
- return STORAGE_OTHER_ERROR;
- }
+ goto error;
+ }
+ else if( ( st = md5_verify_password( xd->given_pass, pass ) ) != 0 )
+ {
+ ret = STORAGE_INVALID_PASSWORD;
+ goto error;
}
}
- /* Just to be sure... */
- g_clear_error( &gerr );
-
- g_markup_parse_context_free( ctx );
- close( fd );
if( action == XML_PASS_CHECK_ONLY )
- return STORAGE_OK;
+ {
+ ret = STORAGE_OK;
+ goto error;
+ }
- return STORAGE_OK;
+ /* DO NOT call xt_handle() before verifying the password! */
+ if( xt_handle( xp, NULL, 1 ) == XT_HANDLED )
+ ret = STORAGE_OK;
+
+ handle_settings( node, &xd->irc->b->set );
+
+error:
+ xt_free( xp );
+ g_free( fn );
+ return ret;
}
static storage_status_t xml_load( irc_t *irc, const char *password )
@@ -422,59 +245,21 @@ static storage_status_t xml_load( irc_t *irc, const char *password )
static storage_status_t xml_check_pass( const char *my_nick, const char *password )
{
- /* This is a little bit risky because we have to pass NULL for the
- irc_t argument. This *should* be fine, if I didn't miss anything... */
return xml_load_real( NULL, my_nick, password, XML_PASS_CHECK_ONLY );
}
-static int xml_printf( int fd, int indent, char *fmt, ... )
-{
- va_list params;
- char *out;
- char tabs[9] = "\t\t\t\t\t\t\t\t";
- int len;
-
- /* Maybe not very clean, but who needs more than 8 levels of indentation anyway? */
- if( write( fd, tabs, indent <= 8 ? indent : 8 ) != indent )
- return 0;
-
- va_start( params, fmt );
- out = g_markup_vprintf_escaped( fmt, params );
- va_end( params );
-
- len = strlen( out );
- len -= write( fd, out, len );
- g_free( out );
-
- return len == 0;
-}
-static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data );
+static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data );
+static void xml_generate_settings( struct xt_node *cur, set_t **head );
-static storage_status_t xml_save( irc_t *irc, int overwrite )
+struct xt_node *xml_generate( irc_t *irc )
{
- char path[512], *path2, *pass_buf = NULL;
- set_t *set;
+ char *pass_buf = NULL;
account_t *acc;
- int fd;
md5_byte_t pass_md5[21];
md5_state_t md5_state;
GSList *l;
-
- path2 = g_strdup( irc->user->nick );
- nick_lc( path2 );
- g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" );
- g_free( path2 );
-
- if( !overwrite && g_access( path, F_OK ) == 0 )
- return STORAGE_ALREADY_EXISTS;
-
- strcat( path, ".XXXXXX" );
- if( ( fd = mkstemp( path ) ) < 0 )
- {
- irc_rootmsg( irc, "Error while opening configuration file." );
- return STORAGE_OTHER_ERROR;
- }
+ struct xt_node *root, *cur;
/* Generate a salted md5sum of the password. Use 5 bytes for the salt
(to prevent dictionary lookups of passwords) to end up with a 21-
@@ -487,15 +272,14 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
/* Save the hash in base64-encoded form. */
pass_buf = base64_encode( pass_md5, 21 );
- if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) )
- goto write_error;
+ root = cur = xt_new_node( "user", NULL, NULL );
+ xt_add_attr( cur, "nick", irc->user->nick );
+ xt_add_attr( cur, "password", pass_buf );
+ xt_add_attr( cur, "version", XML_FORMAT_VERSION );
g_free( pass_buf );
- for( set = irc->b->set; set; set = set->next )
- if( set->value && !( set->flags & SET_NOSAVE ) )
- if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
- goto write_error;
+ xml_generate_settings( cur, &irc->b->set );
for( acc = irc->b->accounts; acc; acc = acc->next )
{
@@ -507,37 +291,29 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
pass_b64 = base64_encode( pass_cr, pass_len );
g_free( pass_cr );
- if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" "
- "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user,
- pass_b64, acc->auto_connect, acc->tag ) )
- {
- g_free( pass_b64 );
- goto write_error;
- }
- g_free( pass_b64 );
-
- if( acc->server && acc->server[0] && !xml_printf( fd, 0, " server=\"%s\"", acc->server ) )
- goto write_error;
- if( !xml_printf( fd, 0, ">\n" ) )
- goto write_error;
+ cur = xt_new_node( "account", NULL, NULL );
+ xt_add_attr( cur, "protocol", acc->prpl->name );
+ xt_add_attr( cur, "handle", acc->user );
+ xt_add_attr( cur, "password", pass_b64 );
+ xt_add_attr( cur, "autoconnect", acc->auto_connect ? "true" : "false" );
+ xt_add_attr( cur, "tag", acc->tag );
+ if( acc->server && acc->server[0] )
+ xt_add_attr( cur, "server", acc->server );
- for( set = acc->set; set; set = set->next )
- if( set->value && !( set->flags & ACC_SET_NOSAVE ) )
- if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
- goto write_error;
+ g_free( pass_b64 );
/* This probably looks pretty strange. g_hash_table_foreach
is quite a PITA already (but it can't get much better in
- C without using #define, I'm afraid), and since it
+ C without using #define, I'm afraid), and it
doesn't seem to be possible to abort the foreach on write
errors, so instead let's use the _find function and
return TRUE on write errors. Which means, if we found
something, there was an error. :-) */
- if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) )
- goto write_error;
+ g_hash_table_find( acc->nicks, xml_generate_nick, cur );
- if( !xml_printf( fd, 1, "</account>\n" ) )
- goto write_error;
+ xml_generate_settings( cur, &acc->set );
+
+ xt_add_child( root, cur );
}
for( l = irc->channels; l; l = l->next )
@@ -547,53 +323,95 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
if( ic->flags & IRC_CHANNEL_TEMP )
continue;
- if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n",
- ic->name, set_getstr( &ic->set, "type" ) ) )
- goto write_error;
+ cur = xt_new_node( "channel", NULL, NULL );
+ xt_add_attr( cur, "name", ic->name );
+ xt_add_attr( cur, "type", set_getstr( &ic->set, "type" ) );
- for( set = ic->set; set; set = set->next )
- if( set->value && strcmp( set->key, "type" ) != 0 )
- if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) )
- goto write_error;
+ xml_generate_settings( cur, &ic->set );
- if( !xml_printf( fd, 1, "</channel>\n" ) )
- goto write_error;
+ xt_add_child( root, cur );
}
- if( !xml_printf( fd, 0, "</user>\n" ) )
- goto write_error;
+ return root;
+}
+
+static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data )
+{
+ struct xt_node *node = xt_new_node( "buddy", NULL, NULL );
+ xt_add_attr( node, "handle", key );
+ xt_add_attr( node, "nick", value );
+ xt_add_child( (struct xt_node *) data, node );
- fsync( fd );
- close( fd );
+ return FALSE;
+}
+
+static void xml_generate_settings( struct xt_node *cur, set_t **head )
+{
+ set_t *set;
+
+ for( set = *head; set; set = set->next )
+ if( set->value && !( set->flags & SET_NOSAVE ) )
+ {
+ struct xt_node *xset;
+ xt_add_child( cur, xset = xt_new_node( "setting", set->value, NULL ) );
+ xt_add_attr( xset, "name", set->key );
+ }
+}
+
+static storage_status_t xml_save( irc_t *irc, int overwrite )
+{
+ storage_status_t ret = STORAGE_OK;
+ char path[512], *path2 = NULL, *xml = NULL;
+ struct xt_node *tree = NULL;
+ size_t len;
+ int fd;
+
+ path2 = g_strdup( irc->user->nick );
+ nick_lc( path2 );
+ g_snprintf( path, sizeof( path ) - 20, "%s%s%s", global.conf->configdir, path2, ".xml" );
+ g_free( path2 );
+
+ if( !overwrite && g_access( path, F_OK ) == 0 )
+ return STORAGE_ALREADY_EXISTS;
+
+ strcat( path, ".XXXXXX" );
+ if( ( fd = mkstemp( path ) ) < 0 )
+ {
+ irc_rootmsg( irc, "Error while opening configuration file." );
+ return STORAGE_OTHER_ERROR;
+ }
+
+ tree = xml_generate( irc );
+ xml = xt_to_string_i( tree );
+ len = strlen( xml );
+ if( write( fd, xml, len ) != len ||
+ fsync( fd ) != 0 || /* #559 */
+ close( fd ) != 0 )
+ goto error;
path2 = g_strndup( path, strlen( path ) - 7 );
if( rename( path, path2 ) != 0 )
{
- irc_rootmsg( irc, "Error while renaming temporary configuration file." );
-
g_free( path2 );
- unlink( path );
-
- return STORAGE_OTHER_ERROR;
+ goto error;
}
-
g_free( path2 );
- return STORAGE_OK;
+ goto finish;
-write_error:
- g_free( pass_buf );
-
+error:
irc_rootmsg( irc, "Write error. Disk full?" );
+ ret = STORAGE_OTHER_ERROR;
+
+finish:
close( fd );
+ unlink( path );
+ g_free( xml );
+ xt_free_node( tree );
- return STORAGE_OTHER_ERROR;
+ return ret;
}
-static gboolean xml_save_nick( gpointer key, gpointer value, gpointer data )
-{
- return !xml_printf( *( (int*) data ), 2, "<buddy handle=\"%s\" nick=\"%s\" />\n", key, value );
-}
static storage_status_t xml_remove( const char *nick, const char *password )
{