mirror of
https://github.com/transmission/transmission
synced 2024-12-27 18:18:10 +00:00
510 lines
13 KiB
C
510 lines
13 KiB
C
/*
|
|
$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 <sys/types.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#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);
|
|
}
|