source: trunk/gtk/conf.c @ 1125

Last change on this file since 1125 was 920, checked in by joshe, 15 years ago

Merge nat-traversal branch to trunk.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.4 KB
Line 
1/******************************************************************************
2 * $Id: conf.c 920 2006-09-25 18:37:45Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/types.h>
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <pwd.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <sys/stat.h>
34#include <unistd.h>
35
36#include <glib.h>
37#include <glib/gi18n.h>
38
39#include "conf.h"
40#include "util.h"
41
42#define CONF_SUBDIR             "gtk"
43#define FILE_LOCK               "lock"
44#define FILE_SOCKET             "socket"
45#define FILE_PREFS              "prefs"
46#define FILE_PREFS_TMP          "prefs.tmp"
47#define FILE_STATE              "state"
48#define FILE_STATE_TMP          "state.tmp"
49#define OLD_FILE_LOCK           "gtk_lock" /* remove this after next release */
50#define OLD_FILE_PREFS          "gtk_prefs"
51#define OLD_FILE_STATE          "gtk_state"
52#define PREF_SEP_KEYVAL         '\t'
53#define PREF_SEP_LINE           '\n'
54#define STATE_SEP               '\n'
55
56static int
57lockfile(const char *file, char **errstr);
58static void
59cf_removelocks(void);
60static char *
61cf_readfile(const char *file, const char *oldfile, gsize *len,
62            gboolean *usedold, char **errstr);
63static void
64cf_benc_append(benc_val_t *val, char type, int incsize);
65static void
66cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
67             char **errstr);
68static gboolean
69writefile_traverse(gpointer key, gpointer value, gpointer data);
70static char *
71getstateval(benc_val_t *state, char *line);
72
73static char *gl_confdir = NULL;
74static char *gl_old_confdir = NULL;
75static GTree *gl_prefs = NULL;
76static char *gl_lockpath = NULL;
77static char *gl_old_lockpath = NULL;
78
79/* errstr may be NULL, this might be called before GTK is initialized */
80static int
81lockfile(const char *file, char **errstr) {
82  int fd, savederr;
83  struct flock lk;
84
85  if(NULL != errstr)
86    *errstr = NULL;
87
88  if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
89    savederr = errno;
90    if(NULL != errstr)
91      *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
92                                file, strerror(errno));
93    errno = savederr;
94    return -1;
95  }
96
97  bzero(&lk, sizeof(lk));
98  lk.l_start = 0;
99  lk.l_len = 0;
100  lk.l_type = F_WRLCK;
101  lk.l_whence = SEEK_SET;
102  if(-1 == fcntl(fd, F_SETLK, &lk)) {
103    savederr = errno;
104    if(NULL != errstr) {
105      if(EAGAIN == errno)
106        *errstr = g_strdup_printf(_("Another copy of %s is already running."),
107                                  g_get_application_name());
108      else
109        *errstr = g_strdup_printf(_("Failed to lock the file %s:\n%s"),
110                                  file, strerror(errno));
111    }
112    close(fd);
113    errno = savederr;
114    return -1;
115  }
116
117  return fd;
118}
119
120/* errstr may be NULL, this might be called before GTK is initialized */
121gboolean
122cf_init(const char *dir, char **errstr) {
123  if(NULL != errstr)
124    *errstr = NULL;
125  gl_old_confdir = g_strdup(dir);
126  gl_confdir = g_build_filename(dir, CONF_SUBDIR, NULL);
127
128  if(mkdir_p(gl_confdir, 0777))
129    return TRUE;
130
131  if(NULL != errstr)
132    *errstr = g_strdup_printf(_("Failed to create the directory %s:\n%s"),
133                              gl_confdir, strerror(errno));
134  return FALSE;
135}
136
137/* errstr may be NULL, this might be called before GTK is initialized */
138gboolean
139cf_lock(char **errstr) {
140  char *path = g_build_filename(gl_old_confdir, OLD_FILE_LOCK, NULL);
141  int fd = lockfile(path, errstr);
142
143  if(0 > fd)
144    g_free(path);
145  else {
146    gl_old_lockpath = path;
147    path = g_build_filename(gl_confdir, FILE_LOCK, NULL);
148    fd = lockfile(path, errstr);
149    if(0 > fd)
150      g_free(path);
151    else
152      gl_lockpath = path;
153  }
154
155  g_atexit(cf_removelocks);
156
157  return 0 <= fd;
158}
159
160static void
161cf_removelocks(void) {
162  if(NULL != gl_lockpath) {
163    unlink(gl_lockpath);
164    g_free(gl_lockpath);
165  }
166
167  if(NULL != gl_old_lockpath) {
168    unlink(gl_old_lockpath);
169    g_free(gl_old_lockpath);
170  }
171}
172
173char *
174cf_sockname(void) {
175  return g_build_filename(gl_confdir, FILE_SOCKET, NULL);
176}
177
178static char *
179cf_readfile(const char *file, const char *oldfile, gsize *len,
180            gboolean *usedold, char **errstr) {
181  char *path;
182  GIOChannel *io;
183  GError *err;
184  char *ret;
185
186  *errstr = NULL;
187  *usedold = FALSE;
188  ret = NULL;
189  err = NULL;
190  *len = 0;
191
192  path = g_build_filename(gl_confdir, file, NULL);
193  io = g_io_channel_new_file(path, "r", &err);
194  if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
195    g_error_free(err);
196    err = NULL;
197    g_free(path);
198    path = g_build_filename(gl_old_confdir, oldfile, NULL);
199    io = g_io_channel_new_file(path, "r", &err);
200    *usedold = TRUE;
201  }
202  if(NULL != err) {
203    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
204      *errstr = g_strdup_printf(
205        _("Failed to open the file %s for reading:\n%s"), path, err->message);
206    goto done;
207  }
208  g_io_channel_set_encoding(io, NULL, NULL);
209
210  if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(io, &ret, len, &err)) {
211    *errstr = g_strdup_printf(
212      _("Error while reading from the file %s:\n%s"), path, err->message);
213    goto done;
214  }
215
216 done:
217  if(NULL != err)
218    g_error_free(err);
219  if(NULL != io) 
220    g_io_channel_unref(io);
221  return ret;
222}
223
224void
225cf_loadprefs(char **errstr) {
226  char *data, *line, *eol, *sep, *key;
227  gsize len;
228  benc_val_t val;
229  gboolean usedold;
230  int ii;
231
232  *errstr = NULL;
233
234  if(NULL != gl_prefs)
235    g_tree_destroy(gl_prefs);
236
237  gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
238                          g_free, g_free);
239
240  data = cf_readfile(FILE_PREFS, OLD_FILE_PREFS, &len, &usedold, errstr);
241  if(NULL != *errstr) {
242    g_assert(NULL == data);
243    return;
244  }
245
246  if(NULL == data)
247    return;
248
249  bzero(&val, sizeof(val));
250  if(!usedold && !tr_bencLoad(data, len, &val, NULL)) {
251    if(TYPE_DICT == val.type) {
252      key = NULL;
253      for(ii = 0; ii < val.val.l.count; ii++) {
254        if(NULL == key) {
255          g_assert(TYPE_STR == val.val.l.vals[ii].type);
256          key = val.val.l.vals[ii].val.s.s;
257        } else {
258          if(TYPE_INT == val.val.l.vals[ii].type)
259            g_tree_insert(gl_prefs, g_strdup(key),
260                         g_strdup_printf("%"PRIu64, val.val.l.vals[ii].val.i));
261          else if(TYPE_STR == val.val.l.vals[ii].type)
262            g_tree_insert(gl_prefs, g_strdup(key),
263                          g_strdup(val.val.l.vals[ii].val.s.s));
264          key = NULL;
265        }
266      }
267    }
268
269  } else {
270    /* XXX remove this in a release or two */
271    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
272        line = eol + 1) {
273      *eol = '\0';
274      if(g_utf8_validate(line, -1, NULL) &&
275         NULL != (sep = strchr(line, PREF_SEP_KEYVAL))) {
276        *sep = '\0';
277        g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep+1));
278      }
279    }
280    cf_saveprefs(errstr);
281  }
282
283  g_free(data);
284}
285
286benc_val_t *
287cf_loadstate(char **errstr) {
288  char *data, *line, *eol, *prog;
289  gsize len;
290  gboolean usedold;
291  benc_val_t *state, *torstate;
292
293  *errstr = NULL;
294
295  data = cf_readfile(FILE_STATE, OLD_FILE_STATE, &len, &usedold, errstr);
296  if(NULL != *errstr) {
297    g_assert(NULL == data);
298    return NULL;
299  }
300
301  if(NULL == data)
302    return NULL;
303
304  state = g_new0(benc_val_t, 1);
305  if(usedold || tr_bencLoad(data, len, state, NULL)) {
306    /* XXX all this evil compat code should go away at some point */
307    tr_bencFree(state);
308    bzero(state, sizeof(benc_val_t));
309    state->type = TYPE_LIST;
310    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
311        line = eol + 1) {
312      *eol = '\0';
313      if(g_utf8_validate(line, -1, NULL)) {
314        cf_benc_append(state, TYPE_DICT, 10);
315        torstate = state->val.l.vals + state->val.l.count - 1;
316        prog = line;
317        while(NULL != (prog = getstateval(torstate, prog)))
318          ;
319      }
320    }
321  }
322
323  g_free(data);
324
325  return state;
326}
327
328static void
329cf_benc_append(benc_val_t *val, char type, int incsize) {
330  if(++val->val.l.count > val->val.l.alloc) {
331    val->val.l.alloc += incsize;
332    val->val.l.vals = g_renew(benc_val_t, val->val.l.vals, val->val.l.alloc);
333    bzero(val->val.l.vals + val->val.l.alloc - incsize,
334          incsize * sizeof(benc_val_t));
335  }
336  val->val.l.vals[val->val.l.count-1].type = type;
337}
338
339const char *
340cf_getpref(const char *name) {
341  g_assert(NULL != gl_prefs);
342
343  return g_tree_lookup(gl_prefs, name);
344}
345
346void
347cf_setpref(const char *name, const char *value) {
348  g_assert(NULL != gl_prefs);
349
350  g_tree_insert(gl_prefs, g_strdup(name), g_strdup(value));
351}
352
353static void
354cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
355             char **errstr) {
356  char *path = g_build_filename(gl_confdir, file, NULL);
357  char *pathtmp = g_build_filename(gl_confdir, tmp, NULL);
358  GIOChannel *io = NULL;
359  GError *err;
360  char *datastr;
361  int len;
362  gsize written;
363
364  *errstr = NULL;
365  err = NULL;
366  datastr = NULL;
367
368  io = g_io_channel_new_file(pathtmp, "w", &err);
369  if(NULL != err) {
370    *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
371                              pathtmp, err->message);
372    goto done;
373  }
374  g_io_channel_set_encoding(io, NULL, NULL);
375
376  len = 0;
377  datastr = tr_bencSaveMalloc(data, &len);
378
379  written = 0;
380  g_io_channel_write_chars(io, datastr, len, &written, &err);
381  if(NULL != err)
382    g_io_channel_flush(io, &err);
383  if(NULL != err) {
384    *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
385                              pathtmp, err->message);
386    goto done;
387  }
388
389  if(0 > rename(pathtmp, path)) {
390    *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"),
391                              pathtmp, file, strerror(errno));
392    goto done;
393  }
394
395 done:
396  g_free(path);
397  g_free(pathtmp);
398  if(NULL != io)
399    g_io_channel_unref(io);
400  if(NULL != datastr)
401    free(datastr);
402}
403
404void
405cf_saveprefs(char **errstr) {
406  benc_val_t val;
407  benc_val_t *ptr;
408
409  *errstr = NULL;
410
411  bzero(&val, sizeof(val));
412  val.type = TYPE_DICT;
413  val.val.l.alloc = val.val.l.count = g_tree_nnodes(gl_prefs) * 2;
414  val.val.l.vals = g_new0(benc_val_t, val.val.l.alloc);
415
416  ptr = val.val.l.vals;
417  g_tree_foreach(gl_prefs, writefile_traverse, &ptr);
418  g_assert(ptr - val.val.l.vals == val.val.l.alloc);
419
420  cf_writebenc(FILE_PREFS, FILE_PREFS_TMP, &val, errstr);
421  tr_bencFree(&val);
422}
423
424static gboolean
425writefile_traverse(gpointer key, gpointer value, gpointer data) {
426  benc_val_t **ptr = data;
427  benc_val_t *bkey = *ptr;
428  benc_val_t *bval = (*ptr) + 1;
429
430  *ptr = (*ptr) + 2;
431
432  bkey->type = TYPE_STR;
433  bkey->val.s.s = g_strdup(key);
434  bkey->val.s.i = strlen(key);
435
436  bval->type = TYPE_STR;
437  bval->val.s.s = g_strdup(value);
438  bval->val.s.i = strlen(value);
439
440  return FALSE;
441}
442
443static char *
444getstateval(benc_val_t *state, char *line) {
445  char *start, *end;
446
447  /* skip any leading whitespace */
448  while(g_ascii_isspace(*line))
449    line++;
450
451  /* walk over the key, which may be alphanumerics as well as - or _ */
452  for(start = line; g_ascii_isalnum(*start)
453        || '_' == *start || '-' == *start; start++)
454    ;
455
456  /* they key must be immediately followed by an = */
457  if('=' != *start)
458    return NULL;
459  *(start++) = '\0';
460
461  /* then the opening quote for the value */
462  if('"' != *(start++))
463    return NULL;
464
465  /* walk over the value */
466  for(end = start; '\0' != *end && '"' != *end; end++)
467    /* skip over escaped quotes */
468    if('\\' == *end && '\0' != *(end + 1))
469      end++;
470
471  /* make sure we didn't hit the end of the string */
472  if('"' != *end)
473    return NULL;
474  *end = '\0';
475
476  /* if it's a key we recognize then save the data */
477  if(0 == strcmp(line, "torrent") || 0 == strcmp(line, "dir") ||
478     0 == strcmp(line, "paused")) {
479    cf_benc_append(state, TYPE_STR, 6);
480    state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(line);
481    state->val.l.vals[state->val.l.count-1].val.s.i = strlen(line);
482    if('p' == *line) {
483      cf_benc_append(state, TYPE_INT, 6);
484      state->val.l.vals[state->val.l.count-1].val.i = strbool(start);
485    } else {
486      cf_benc_append(state, TYPE_STR, 6);
487      state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(start);
488      state->val.l.vals[state->val.l.count-1].val.s.i = strlen(start);
489    }
490  }
491
492  /* return a pointer to just past the end of the value */
493  return end + 1;
494}
495
496void
497cf_savestate(benc_val_t *state, char **errstr) {
498  *errstr = NULL;
499  cf_writebenc(FILE_STATE, FILE_STATE_TMP, state, errstr);
500}
501
502void
503cf_freestate(benc_val_t *state) {
504  tr_bencFree(state);
505  g_free(state);
506}
Note: See TracBrowser for help on using the repository browser.