1/*
2 * Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22
23#include <stdio.h>
24#include <string.h>
25#include <stdlib.h>
26#include <stdint.h>
27
28#include <sys/types.h>
29#include <sys/utsname.h>
30#include <pwd.h>
31
32#include <errno.h>
33
34#include <confuse.h>
35
36#include "logger.h"
37#include "misc.h"
38#include "conffile.h"
39
40
41/* Forward */
42static int cb_loglevel(cfg_t *cfg, cfg_opt_t *opt, const char *value, void *result);
43
44/* general section structure */
45static cfg_opt_t sec_general[] =
46  {
47    CFG_STR("uid", "nobody", CFGF_NONE),
48    CFG_STR("admin_password", NULL, CFGF_NONE),
49    CFG_STR("logfile", STATEDIR "/log/" PACKAGE ".log", CFGF_NONE),
50    CFG_STR("db_path", STATEDIR "/cache/" PACKAGE "/songs3.db", CFGF_NONE),
51    CFG_INT("db_pragma_cache_size", -1, CFGF_NONE),
52    CFG_STR("db_pragma_journal_mode", NULL, CFGF_NONE),
53    CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
54    CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
55    CFG_BOOL("ipv6", cfg_false, CFGF_NONE),
56    CFG_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
57    CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
58    CFG_END()
59  };
60
61/* library section structure */
62static cfg_opt_t sec_library[] =
63  {
64    CFG_STR("name", "My Music on %h", CFGF_NONE),
65    CFG_INT("port", 3689, CFGF_NONE),
66    CFG_STR("password", NULL, CFGF_NONE),
67    CFG_STR_LIST("directories", NULL, CFGF_NONE),
68    CFG_STR_LIST("podcasts", NULL, CFGF_NONE),
69    CFG_STR_LIST("audiobooks", NULL, CFGF_NONE),
70    CFG_STR_LIST("compilations", NULL, CFGF_NONE),
71    CFG_STR("compilation_artist", NULL, CFGF_NONE),
72    CFG_BOOL("radio_playlists", cfg_false, CFGF_NONE),
73    CFG_STR("name_library", "Library", CFGF_NONE),
74    CFG_STR("name_music", "Music", CFGF_NONE),
75    CFG_STR("name_movies", "Movies", CFGF_NONE),
76    CFG_STR("name_tvshows", "TV Shows", CFGF_NONE),
77    CFG_STR("name_podcasts", "Podcasts", CFGF_NONE),
78    CFG_STR("name_audiobooks", "Audiobooks", CFGF_NONE),
79    CFG_STR("name_radio", "Radio", CFGF_NONE),
80    CFG_STR_LIST("artwork_basenames", "{artwork,cover,Folder}", CFGF_NONE),
81    CFG_BOOL("artwork_individual", cfg_false, CFGF_NONE),
82    CFG_STR_LIST("filetypes_ignore", "{.db,.ini,.db-journal,.pdf}", CFGF_NONE),
83    CFG_STR_LIST("filepath_ignore", NULL, CFGF_NONE),
84    CFG_BOOL("filescan_disable", cfg_false, CFGF_NONE),
85    CFG_BOOL("itunes_overrides", cfg_false, CFGF_NONE),
86    CFG_STR_LIST("no_transcode", NULL, CFGF_NONE),
87    CFG_STR_LIST("force_transcode", NULL, CFGF_NONE),
88    CFG_END()
89  };
90
91/* local audio section structure */
92static cfg_opt_t sec_audio[] =
93  {
94    CFG_STR("nickname", "Computer", CFGF_NONE),
95#ifdef __linux__
96    CFG_STR("card", "default", CFGF_NONE),
97#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
98    CFG_STR("card", "/dev/dsp", CFGF_NONE),
99#endif
100    CFG_STR("mixer", NULL, CFGF_NONE),
101    CFG_END()
102  };
103
104/* AirPlay/ApEx device section structure */
105static cfg_opt_t sec_airplay[] =
106  {
107    CFG_INT("max_volume", 11, CFGF_NONE),
108    CFG_STR("password", NULL, CFGF_NONE),
109    CFG_END()
110  };
111
112/* Spotify section structure */
113static cfg_opt_t sec_spotify[] =
114  {
115    CFG_STR("settings_dir", STATEDIR "/cache/" PACKAGE "/libspotify", CFGF_NONE),
116    CFG_STR("cache_dir", "/tmp", CFGF_NONE),
117    CFG_INT("bitrate", 0, CFGF_NONE),
118    CFG_BOOL("base_playlist_disable", cfg_false, CFGF_NONE),
119    CFG_BOOL("artist_override", cfg_false, CFGF_NONE),
120    CFG_BOOL("starred_artist_override", cfg_false, CFGF_NONE),
121    CFG_BOOL("album_override", cfg_false, CFGF_NONE),
122    CFG_BOOL("starred_album_override", cfg_false, CFGF_NONE),
123    CFG_END()
124  };
125
126/* SQLite section structure */
127static cfg_opt_t sec_sqlite[] =
128  {
129    CFG_INT("pragma_cache_size_library", -1, CFGF_NONE),
130    CFG_INT("pragma_cache_size_cache", -1, CFGF_NONE),
131    CFG_STR("pragma_journal_mode", NULL, CFGF_NONE),
132    CFG_INT("pragma_synchronous", -1, CFGF_NONE),
133    CFG_BOOL("vacuum", cfg_true, CFGF_NONE),
134    CFG_END()
135  };
136
137/* MPD section structure */
138static cfg_opt_t sec_mpd[] =
139  {
140    CFG_INT("port", 6600, CFGF_NONE),
141    CFG_END()
142  };
143
144/* Config file structure */
145static cfg_opt_t toplvl_cfg[] =
146  {
147    CFG_SEC("general", sec_general, CFGF_NONE),
148    CFG_SEC("library", sec_library, CFGF_NONE),
149    CFG_SEC("audio", sec_audio, CFGF_NONE),
150    CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
151    CFG_SEC("spotify", sec_spotify, CFGF_NONE),
152    CFG_SEC("sqlite", sec_sqlite, CFGF_NONE),
153    CFG_SEC("mpd", sec_mpd, CFGF_NONE),
154    CFG_END()
155  };
156
157cfg_t *cfg;
158uint64_t libhash;
159uid_t runas_uid;
160gid_t runas_gid;
161
162
163static int
164cb_loglevel(cfg_t *cfg, cfg_opt_t *opt, const char *value, void *result)
165{
166  if (strcasecmp(value, "fatal") == 0)
167    *(long int *)result = E_FATAL;
168  else if (strcasecmp(value, "log") == 0)
169    *(long int *)result = E_LOG;
170  else if (strcasecmp(value, "warning") == 0)
171    *(long int *)result = E_WARN;
172  else if (strcasecmp(value, "info") == 0)
173    *(long int *)result = E_INFO;
174  else if (strcasecmp(value, "debug") == 0)
175    *(long int *)result = E_DBG;
176  else if (strcasecmp(value, "spam") == 0)
177    *(long int *)result = E_SPAM;
178  else
179    {
180      DPRINTF(E_WARN, L_CONF, "Unrecognised loglevel '%s'\n", value);
181      /* Default to warning */
182      *(long int *)result = 1;
183    }
184
185  return 0;
186}
187
188static int
189conffile_expand_libname(cfg_t *lib)
190{
191  char *libname;
192  char *hostname;
193  char *s;
194  char *d;
195  char *expanded;
196  struct utsname sysinfo;
197  size_t len;
198  size_t olen;
199  size_t hostlen;
200  size_t verlen;
201  int ret;
202
203  libname = cfg_getstr(lib, "name");
204  olen = strlen(libname);
205
206  /* Fast path */
207  s = strchr(libname, '%');
208  if (!s)
209    {
210      libhash = murmur_hash64(libname, olen, 0);
211      return 0;
212    }
213
214  /* Grab what we need */
215  ret = uname(&sysinfo);
216  if (ret != 0)
217    {
218      DPRINTF(E_WARN, L_CONF, "Could not get system name: %s\n", strerror(errno));
219      hostname = "Unknown host";
220    }
221  else
222    hostname = sysinfo.nodename;
223
224  hostlen = strlen(hostname);
225  verlen = strlen(VERSION);
226
227  /* Compute expanded size */
228  len = olen;
229  s = libname;
230  while (*s)
231    {
232      if (*s == '%')
233	{
234	  s++;
235
236	  switch (*s)
237	    {
238	      case 'h':
239		len += hostlen;
240		break;
241
242	      case 'v':
243		len += verlen;
244		break;
245	    }
246	}
247      s++;
248    }
249
250  expanded = (char *)malloc(len + 1);
251  if (!expanded)
252    {
253      DPRINTF(E_FATAL, L_CONF, "Out of memory\n");
254
255      return -1;
256    }
257  memset(expanded, 0, len + 1);
258
259  /* Do the actual expansion */
260  s = libname;
261  d = expanded;
262  while (*s)
263    {
264      if (*s == '%')
265	{
266	  s++;
267
268	  switch (*s)
269	    {
270	      case 'h':
271		strcat(d, hostname);
272		d += hostlen;
273		break;
274
275	      case 'v':
276		strcat(d, VERSION);
277		d += verlen;
278		break;
279	    }
280
281	  s++;
282	}
283      else
284	{
285	  *d = *s;
286
287	  s++;
288	  d++;
289	}
290    }
291
292  cfg_setstr(lib, "name", expanded);
293
294  libhash = murmur_hash64(expanded, strlen(expanded), 0);
295
296  free(expanded);
297
298  return 0;
299}
300
301
302int
303conffile_load(char *file)
304{
305  cfg_t *lib;
306  struct passwd *pw;
307  char *runas;
308  int ret;
309
310  cfg = cfg_init(toplvl_cfg, CFGF_NONE);
311
312  ret = cfg_parse(cfg, file);
313
314  if (ret == CFG_FILE_ERROR)
315    {
316      DPRINTF(E_FATAL, L_CONF, "Could not open config file %s\n", file);
317
318      goto out_fail;
319    }
320  else if (ret == CFG_PARSE_ERROR)
321    {
322      DPRINTF(E_FATAL, L_CONF, "Parse error in config file %s\n", file);
323
324      goto out_fail;
325    }
326
327  /* Resolve runas username */
328  runas = cfg_getstr(cfg_getsec(cfg, "general"), "uid");
329  pw = getpwnam("admin");
330  if (!pw)
331    {
332      DPRINTF(E_FATAL, L_CONF, "Could not lookup user %s: %s\n", runas, strerror(errno));
333
334      goto out_fail;
335    }
336
337  runas_uid = pw->pw_uid;
338  runas_gid = pw->pw_gid;
339
340  lib = cfg_getsec(cfg, "library");
341
342  if (cfg_size(lib, "directories") == 0)
343    {
344      DPRINTF(E_FATAL, L_CONF, "No directories specified for library\n");
345
346      goto out_fail;
347    }
348
349  /* Do keyword expansion on library names */
350  ret = conffile_expand_libname(lib);
351  if (ret != 0)
352    {
353      DPRINTF(E_FATAL, L_CONF, "Could not expand library name\n");
354
355      goto out_fail;
356    }
357
358  return 0;
359
360 out_fail:
361  cfg_free(cfg);
362
363  return -1;
364}
365
366void
367conffile_unload(void)
368{
369  cfg_free(cfg);
370}
371