/* $Id$ Copyright (c) 2005-2006 Joshua Elsasser. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "conf.h" #include "util.h" #define CONF_SUBDIR "gtk" #define FILE_LOCK "lock" #define FILE_SOCKET "socket" #define FILE_PREFS "prefs" #define FILE_PREFS_TMP "prefs.tmp" #define FILE_STATE "state" #define FILE_STATE_TMP "state.tmp" #define OLD_FILE_LOCK "gtk_lock" /* remove this after next release */ #define OLD_FILE_PREFS "gtk_prefs" #define OLD_FILE_STATE "gtk_state" #define PREF_SEP_KEYVAL '\t' #define PREF_SEP_LINE '\n' #define STATE_SEP '\n' static int lockfile(const char *file, char **errstr); static void cf_removelocks(void); static char * cf_readfile(const char *file, const char *oldfile, gsize *len, gboolean *usedold, char **errstr); static void cf_benc_append(benc_val_t *val, char type, int incsize); static void cf_writebenc(const char *file, const char *tmp, benc_val_t *data, char **errstr); static gboolean writefile_traverse(gpointer key, gpointer value, gpointer data); static char * getstateval(benc_val_t *state, char *line); static char *gl_confdir = NULL; static char *gl_old_confdir = NULL; static GTree *gl_prefs = NULL; static char *gl_lockpath = NULL; static char *gl_old_lockpath = NULL; /* errstr may be NULL, this might be called before GTK is initialized */ static int lockfile(const char *file, char **errstr) { int fd, savederr; struct flock lk; if(NULL != errstr) *errstr = NULL; if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) { savederr = errno; if(NULL != errstr) *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"), file, strerror(errno)); errno = savederr; return -1; } bzero(&lk, sizeof(lk)); lk.l_start = 0; lk.l_len = 0; lk.l_type = F_WRLCK; lk.l_whence = SEEK_SET; if(-1 == fcntl(fd, F_SETLK, &lk)) { savederr = errno; if(NULL != errstr) { if(EAGAIN == errno) *errstr = g_strdup_printf(_("Another copy of %s is already running."), g_get_application_name()); else *errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"), file, strerror(errno)); } close(fd); errno = savederr; return -1; } return fd; } /* errstr may be NULL, this might be called before GTK is initialized */ gboolean cf_init(const char *dir, char **errstr) { if(NULL != errstr) *errstr = NULL; gl_old_confdir = g_strdup(dir); gl_confdir = g_build_filename(dir, CONF_SUBDIR, NULL); if(mkdir_p(gl_confdir, 0777)) return TRUE; if(NULL != errstr) *errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"), gl_confdir, strerror(errno)); return FALSE; } /* errstr may be NULL, this might be called before GTK is initialized */ gboolean cf_lock(char **errstr) { char *path = g_build_filename(gl_old_confdir, OLD_FILE_LOCK, NULL); int fd = lockfile(path, errstr); if(0 > fd) g_free(path); else { gl_old_lockpath = path; path = g_build_filename(gl_confdir, FILE_LOCK, NULL); fd = lockfile(path, errstr); if(0 > fd) g_free(path); else gl_lockpath = path; } g_atexit(cf_removelocks); return 0 <= fd; } static void cf_removelocks(void) { if(NULL != gl_lockpath) { unlink(gl_lockpath); g_free(gl_lockpath); } if(NULL != gl_old_lockpath) { unlink(gl_old_lockpath); g_free(gl_old_lockpath); } } char * cf_sockname(void) { return g_build_filename(gl_confdir, FILE_SOCKET, NULL); } static char * cf_readfile(const char *file, const char *oldfile, gsize *len, gboolean *usedold, char **errstr) { char *path; GIOChannel *io; GError *err; char *ret; *errstr = NULL; *usedold = FALSE; ret = NULL; err = NULL; *len = 0; path = g_build_filename(gl_confdir, file, NULL); io = g_io_channel_new_file(path, "r", &err); if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_error_free(err); err = NULL; g_free(path); path = g_build_filename(gl_old_confdir, oldfile, NULL); io = g_io_channel_new_file(path, "r", &err); *usedold = TRUE; } if(NULL != err) { if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) *errstr = g_strdup_printf( _("Failed to open the file %s for reading:\n%s"), path, err->message); goto done; } g_io_channel_set_encoding(io, NULL, NULL); if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(io, &ret, len, &err)) { *errstr = g_strdup_printf( _("Error while reading from the file %s:\n%s"), path, err->message); goto done; } done: if(NULL != err) g_error_free(err); if(NULL != io) g_io_channel_unref(io); return ret; } void cf_loadprefs(char **errstr) { char *data, *line, *eol, *sep, *key; gsize len; benc_val_t val; gboolean usedold; int ii; *errstr = NULL; if(NULL != gl_prefs) g_tree_destroy(gl_prefs); gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL, g_free, g_free); data = cf_readfile(FILE_PREFS, OLD_FILE_PREFS, &len, &usedold, errstr); if(NULL != *errstr) { g_assert(NULL == data); return; } if(NULL == data) return; bzero(&val, sizeof(val)); if(!usedold && !tr_bencLoad(data, len, &val, NULL)) { if(TYPE_DICT == val.type) { key = NULL; for(ii = 0; ii < val.val.l.count; ii++) { if(NULL == key) { g_assert(TYPE_STR == val.val.l.vals[ii].type); key = val.val.l.vals[ii].val.s.s; } else { if(TYPE_INT == val.val.l.vals[ii].type) g_tree_insert(gl_prefs, g_strdup(key), g_strdup_printf("%"PRIu64, val.val.l.vals[ii].val.i)); else if(TYPE_STR == val.val.l.vals[ii].type) g_tree_insert(gl_prefs, g_strdup(key), g_strdup(val.val.l.vals[ii].val.s.s)); key = NULL; } } } } else { /* XXX remove this in a release or two */ for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE)); line = eol + 1) { *eol = '\0'; if(g_utf8_validate(line, -1, NULL) && NULL != (sep = strchr(line, PREF_SEP_KEYVAL))) { *sep = '\0'; g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep+1)); } } cf_saveprefs(errstr); } g_free(data); } benc_val_t * cf_loadstate(char **errstr) { char *data, *line, *eol, *prog; gsize len; gboolean usedold; benc_val_t *state, *torstate; *errstr = NULL; data = cf_readfile(FILE_STATE, OLD_FILE_STATE, &len, &usedold, errstr); if(NULL != *errstr) { g_assert(NULL == data); return NULL; } if(NULL == data) return NULL; state = g_new0(benc_val_t, 1); if(usedold || tr_bencLoad(data, len, state, NULL)) { /* XXX all this evil compat code should go away at some point */ tr_bencFree(state); bzero(state, sizeof(benc_val_t)); state->type = TYPE_LIST; for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE)); line = eol + 1) { *eol = '\0'; if(g_utf8_validate(line, -1, NULL)) { cf_benc_append(state, TYPE_DICT, 10); torstate = state->val.l.vals + state->val.l.count - 1; prog = line; while(NULL != (prog = getstateval(torstate, prog))) ; } } } g_free(data); return state; } static void cf_benc_append(benc_val_t *val, char type, int incsize) { if(++val->val.l.count > val->val.l.alloc) { val->val.l.alloc += incsize; val->val.l.vals = g_renew(benc_val_t, val->val.l.vals, val->val.l.alloc); bzero(val->val.l.vals + val->val.l.alloc - incsize, incsize * sizeof(benc_val_t)); } val->val.l.vals[val->val.l.count-1].type = type; } const char * cf_getpref(const char *name) { g_assert(NULL != gl_prefs); return g_tree_lookup(gl_prefs, name); } void cf_setpref(const char *name, const char *value) { g_assert(NULL != gl_prefs); g_tree_insert(gl_prefs, g_strdup(name), g_strdup(value)); } static void cf_writebenc(const char *file, const char *tmp, benc_val_t *data, char **errstr) { char *path = g_build_filename(gl_confdir, file, NULL); char *pathtmp = g_build_filename(gl_confdir, tmp, NULL); GIOChannel *io = NULL; GError *err; char *datastr; size_t len; gsize written; *errstr = NULL; err = NULL; datastr = NULL; io = g_io_channel_new_file(pathtmp, "w", &err); if(NULL != err) { *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"), pathtmp, err->message); goto done; } g_io_channel_set_encoding(io, NULL, NULL); len = 0; datastr = tr_bencSaveMalloc(data, &len); written = 0; g_io_channel_write_chars(io, datastr, len, &written, &err); if(NULL != err) g_io_channel_flush(io, &err); if(NULL != err) { *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"), pathtmp, err->message); goto done; } if(0 > rename(pathtmp, path)) { *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"), pathtmp, file, strerror(errno)); goto done; } done: g_free(path); g_free(pathtmp); if(NULL != io) g_io_channel_unref(io); if(NULL != datastr) free(datastr); } void cf_saveprefs(char **errstr) { benc_val_t val; benc_val_t *ptr; *errstr = NULL; bzero(&val, sizeof(val)); val.type = TYPE_DICT; val.val.l.alloc = val.val.l.count = g_tree_nnodes(gl_prefs) * 2; val.val.l.vals = g_new0(benc_val_t, val.val.l.alloc); ptr = val.val.l.vals; g_tree_foreach(gl_prefs, writefile_traverse, &ptr); g_assert(ptr - val.val.l.vals == val.val.l.alloc); cf_writebenc(FILE_PREFS, FILE_PREFS_TMP, &val, errstr); tr_bencFree(&val); } static gboolean writefile_traverse(gpointer key, gpointer value, gpointer data) { benc_val_t **ptr = data; benc_val_t *bkey = *ptr; benc_val_t *bval = (*ptr) + 1; *ptr = (*ptr) + 2; bkey->type = TYPE_STR; bkey->val.s.s = g_strdup(key); bkey->val.s.i = strlen(key); bval->type = TYPE_STR; bval->val.s.s = g_strdup(value); bval->val.s.i = strlen(value); return FALSE; } static char * getstateval(benc_val_t *state, char *line) { char *start, *end; /* skip any leading whitespace */ while(g_ascii_isspace(*line)) line++; /* walk over the key, which may be alphanumerics as well as - or _ */ for(start = line; g_ascii_isalnum(*start) || '_' == *start || '-' == *start; start++) ; /* they key must be immediately followed by an = */ if('=' != *start) return NULL; *(start++) = '\0'; /* then the opening quote for the value */ if('"' != *(start++)) return NULL; /* walk over the value */ for(end = start; '\0' != *end && '"' != *end; end++) /* skip over escaped quotes */ if('\\' == *end && '\0' != *(end + 1)) end++; /* make sure we didn't hit the end of the string */ if('"' != *end) return NULL; *end = '\0'; /* if it's a key we recognize then save the data */ if(0 == strcmp(line, "torrent") || 0 == strcmp(line, "dir") || 0 == strcmp(line, "paused")) { cf_benc_append(state, TYPE_STR, 6); state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(line); state->val.l.vals[state->val.l.count-1].val.s.i = strlen(line); if('p' == *line) { cf_benc_append(state, TYPE_INT, 6); state->val.l.vals[state->val.l.count-1].val.i = strbool(start); } else { cf_benc_append(state, TYPE_STR, 6); state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(start); state->val.l.vals[state->val.l.count-1].val.s.i = strlen(start); } } /* return a pointer to just past the end of the value */ return end + 1; } void cf_savestate(benc_val_t *state, char **errstr) { *errstr = NULL; cf_writebenc(FILE_STATE, FILE_STATE_TMP, state, errstr); } void cf_freestate(benc_val_t *state) { tr_bencFree(state); g_free(state); }