1 /*
2   deadbeef config file manager
3   http://deadbeef.sourceforge.net
4 
5   Copyright (C) 2009-2013 Alexey Yakovenko
6 
7   This software is provided 'as-is', without any express or implied
8   warranty.  In no event will the authors be held liable for any damages
9   arising from the use of this software.
10 
11   Permission is granted to anyone to use this software for any purpose,
12   including commercial applications, and to alter it and redistribute it
13   freely, subject to the following restrictions:
14 
15   1. The origin of this software must not be misrepresented; you must not
16      claim that you wrote the original software. If you use this software
17      in a product, an acknowledgment in the product documentation would be
18      appreciated but is not required.
19   2. Altered source versions must be plainly marked as such, and must not be
20      misrepresented as being the original software.
21   3. This notice may not be removed or altered from any source distribution.
22 
23   Alexey Yakovenko waker@users.sourceforge.net
24 */
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <limits.h>
33 #include <inttypes.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #if HAVE_SYS_CDEFS_H
37 #include <sys/cdefs.h>
38 #endif
39 #include <sys/stat.h>
40 #include "conf.h"
41 #include "threading.h"
42 #include "common.h"
43 
44 #define min(x,y) ((x)<(y)?(x):(y))
45 
46 static DB_conf_item_t *conf_items;
47 static int changed = 0;
48 static uintptr_t mutex;
49 
50 void
conf_init(void)51 conf_init (void) {
52     mutex = mutex_create ();
53 }
54 
55 void
conf_lock(void)56 conf_lock (void) {
57     mutex_lock (mutex);
58 }
59 
60 void
conf_unlock(void)61 conf_unlock (void) {
62     mutex_unlock (mutex);
63 }
64 
65 void
conf_free(void)66 conf_free (void) {
67     mutex_lock (mutex);
68     DB_conf_item_t *next = NULL;
69     for (DB_conf_item_t *it = conf_items; it; it = next) {
70         next = it->next;
71         conf_item_free (it);
72     }
73     conf_items = NULL;
74     changed = 0;
75     mutex_free (mutex);
76     mutex = 0;
77 }
78 
79 int
conf_load(void)80 conf_load (void) {
81     size_t l = strlen (dbconfdir);
82     const char configfile[] = "/config";
83     char fname[l + sizeof(configfile)];
84     memcpy (fname, dbconfdir, l);
85     memcpy (fname + l, configfile, sizeof (configfile));
86     FILE *fp = fopen (fname, "rt");
87     if (!fp) {
88         fprintf (stderr, "failed to load config file\n");
89         fp = fopen (fname, "w+b");
90         if (!fp) {
91             return -1;
92         }
93         fprintf (stderr, "created an empty config\n");
94         fclose (fp);
95         return 0;
96     }
97     conf_lock ();
98     int line = 0;
99 
100     fseek (fp, 0, SEEK_END);
101     l = ftell (fp);
102     rewind (fp);
103 
104     uint8_t *buffer = malloc (l+1);
105     if (l != fread (buffer, 1, l, fp)) {
106         free (buffer);
107         fprintf (stderr, "failed to read entire config file to memory\n");
108         return -1;
109     }
110     buffer[l] = 0;
111     fclose (fp);
112     fp = NULL;
113 
114     uint8_t *str = buffer;
115 
116     while (*str) {
117         line++;
118         uint8_t *estr = str;
119         while (*estr >= 0x20) {
120             estr++;
121         }
122         *estr = 0;
123 
124         if (str[0] == '#' || str[0] <= 0x20) {
125             str = estr+1;
126             continue;
127         }
128         uint8_t *p = (uint8_t *)str;
129         while (*p && *p > 0x20) {
130             p++;
131         }
132         if (!*p) {
133             fprintf (stderr, "error in config file line %d\n", line);
134             str = estr+1;
135             continue;
136         }
137         *p = 0;
138         p++;
139         // skip whitespace
140         while (*p && *p <= 0x20) {
141             p++;
142         }
143         uint8_t *value = p;
144         // remove trailing trash
145         while (*p && *p >= 0x20) {
146             p++;
147         }
148         *p = 0;
149         // new items are appended, to preserve order
150         conf_set_str ((const char *)str, (const char *)value);
151         str = estr+1;
152     }
153     changed = 0;
154     free (buffer);
155     conf_unlock ();
156     return 0;
157 }
158 
159 int
conf_save(void)160 conf_save (void) {
161     char tempfile[PATH_MAX];
162     char str[PATH_MAX];
163     FILE *fp;
164     int err;
165 
166     if (!changed) {
167         return 0;
168     }
169 
170     snprintf (tempfile, sizeof (tempfile), "%s/config.tmp", dbconfdir);
171     snprintf (str, sizeof (str), "%s/config", dbconfdir);
172 
173     conf_lock ();
174     changed = 0;
175     fp = fopen (tempfile, "w+t");
176     if (!fp) {
177         fprintf (stderr, "failed to open config file for writing\n");
178         conf_unlock ();
179         return -1;
180     }
181     for (DB_conf_item_t *it = conf_items; it; it = it->next) {
182         if (fprintf (fp, "%s %s\n", it->key, it->value) < 0) {
183             fprintf (stderr, "failed to write to file %s (%s)\n", tempfile, strerror (errno));
184             fclose (fp);
185             conf_unlock ();
186             return -1;
187         }
188     }
189     fclose (fp);
190     err = rename (tempfile, str);
191     if (err != 0) {
192         fprintf (stderr, "config rename %s -> %s failed: %s\n", tempfile, str, strerror (errno));
193     }
194     else {
195         chmod (str, 0600);
196     }
197     conf_unlock ();
198     return 0;
199 }
200 
201 void
conf_item_free(DB_conf_item_t * it)202 conf_item_free (DB_conf_item_t *it) {
203     conf_lock ();
204     if (it) {
205         if (it->key) {
206             free (it->key);
207         }
208         if (it->value) {
209             free (it->value);
210         }
211         free (it);
212     }
213     conf_unlock ();
214 }
215 
216 const char *
conf_get_str_fast(const char * key,const char * def)217 conf_get_str_fast (const char *key, const char *def) {
218     for (DB_conf_item_t *it = conf_items; it; it = it->next) {
219         if (!strcasecmp (key, it->key)) {
220             return it->value;
221         }
222     }
223     return def;
224 }
225 
226 void
conf_get_str(const char * key,const char * def,char * buffer,int buffer_size)227 conf_get_str (const char *key, const char *def, char *buffer, int buffer_size) {
228     conf_lock ();
229     const char *out = conf_get_str_fast (key, def);
230     if (out) {
231         size_t n = strlen (out)+1;
232         n = min (n, buffer_size);
233         memcpy (buffer, out, n);
234         buffer[buffer_size-1] = 0;
235     }
236     else {
237         *buffer = 0;
238     }
239     conf_unlock ();
240 }
241 
242 float
conf_get_float(const char * key,float def)243 conf_get_float (const char *key, float def) {
244     conf_lock ();
245     const char *v = conf_get_str_fast (key, NULL);
246     conf_unlock ();
247     return v ? atof (v) : def;
248 }
249 
250 int
conf_get_int(const char * key,int def)251 conf_get_int (const char *key, int def) {
252     conf_lock ();
253     const char *v = conf_get_str_fast (key, NULL);
254     conf_unlock ();
255     return v ? atoi (v) : def;
256 }
257 
258 int64_t
conf_get_int64(const char * key,int64_t def)259 conf_get_int64 (const char *key, int64_t def) {
260     conf_lock ();
261     const char *v = conf_get_str_fast (key, NULL);
262     conf_unlock ();
263     return v ? atoll (v) : def;
264 }
265 
266 DB_conf_item_t *
conf_find(const char * group,DB_conf_item_t * prev)267 conf_find (const char *group, DB_conf_item_t *prev) {
268     size_t l = strlen (group);
269     for (DB_conf_item_t *it = prev ? prev->next : conf_items; it; it = it->next) {
270         if (!strncasecmp (group, it->key, l)) {
271             return it;
272         }
273     }
274     return NULL;
275 }
276 
277 void
conf_set_str(const char * key,const char * val)278 conf_set_str (const char *key, const char *val) {
279     conf_lock ();
280     DB_conf_item_t *prev = NULL;
281     for (DB_conf_item_t *it = conf_items; it; it = it->next) {
282         int cmp = strcasecmp (key, it->key);
283         if (!cmp) {
284             if (!strcmp (it->value, val)) {
285                 conf_unlock ();
286                 return;
287             }
288             free (it->value);
289             it->value = strdup (val);
290             conf_unlock ();
291             changed = 1;
292             return;
293         }
294         else if (cmp < 0) {
295             break;
296         }
297         prev = it;
298     }
299     if (!val) {
300         conf_unlock ();
301         return;
302     }
303     DB_conf_item_t *it = malloc (sizeof (DB_conf_item_t));
304     memset (it, 0, sizeof (DB_conf_item_t));
305     it->key = strdup (key);
306     it->value = strdup (val);
307     changed = 1;
308     if (prev) {
309         DB_conf_item_t *next = prev->next;
310         prev->next = it;
311         it->next = next;
312     }
313     else {
314         it->next = conf_items;
315         conf_items = it;
316     }
317     conf_unlock ();
318 }
319 
320 void
conf_set_int(const char * key,int val)321 conf_set_int (const char *key, int val) {
322     char s[10];
323     snprintf (s, sizeof (s), "%d", val);
324     conf_set_str (key, s);
325 }
326 
327 void
conf_set_int64(const char * key,int64_t val)328 conf_set_int64 (const char *key, int64_t val) {
329     char s[20];
330     snprintf (s, sizeof (s), "%"PRId64, val);
331     conf_set_str (key, s);
332 }
333 
334 void
conf_set_float(const char * key,float val)335 conf_set_float (const char *key, float val) {
336     char s[10];
337     snprintf (s, sizeof (s), "%0.7f", val);
338     conf_set_str (key, s);
339 }
340 
341 int
conf_ischanged(void)342 conf_ischanged (void) {
343     return changed;
344 }
345 
346 void
conf_setchanged(int c)347 conf_setchanged (int c) {
348     changed = c;
349 }
350 
351 void
conf_remove_items(const char * key)352 conf_remove_items (const char *key) {
353     size_t l = strlen (key);
354     conf_lock ();
355     DB_conf_item_t *prev = NULL;
356     DB_conf_item_t *it;
357     for (it = conf_items; it; prev = it, it = it->next) {
358         if (!strncasecmp (key, it->key, l)) {
359             break;
360         }
361     }
362     DB_conf_item_t *next = NULL;
363     while (it) {
364         next = it->next;
365         conf_item_free (it);
366         it = next;
367         if (!it || strncasecmp (key, it->key, l)) {
368             break;
369         }
370     }
371     if (prev) {
372         prev->next = next;
373     }
374     else {
375         conf_items = next;
376     }
377     conf_unlock ();
378 }
379