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