alconfig.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 1999-2007 by authors.
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Library General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) any later version.
  8. *
  9. * This library 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 GNU
  12. * Library General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Library General Public
  15. * License along with this library; if not, write to the
  16. * Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. * Or go to http://www.gnu.org/copyleft/lgpl.html
  19. */
  20. #ifdef _WIN32
  21. #ifdef __MINGW32__
  22. #define _WIN32_IE 0x501
  23. #else
  24. #define _WIN32_IE 0x400
  25. #endif
  26. #endif
  27. #include "config.h"
  28. #include <stdlib.h>
  29. #include <stdio.h>
  30. #include <ctype.h>
  31. #include <string.h>
  32. #ifdef _WIN32_IE
  33. #include <windows.h>
  34. #include <shlobj.h>
  35. #endif
  36. #ifdef __APPLE__
  37. #include <CoreFoundation/CoreFoundation.h>
  38. #endif
  39. #include "alMain.h"
  40. #include "alconfig.h"
  41. #include "compat.h"
  42. #include "bool.h"
  43. typedef struct ConfigEntry {
  44. char *key;
  45. char *value;
  46. } ConfigEntry;
  47. typedef struct ConfigBlock {
  48. ConfigEntry *entries;
  49. unsigned int entryCount;
  50. } ConfigBlock;
  51. static ConfigBlock cfgBlock;
  52. static char *lstrip(char *line)
  53. {
  54. while(isspace(line[0]))
  55. line++;
  56. return line;
  57. }
  58. static char *rstrip(char *line)
  59. {
  60. size_t len = strlen(line);
  61. while(len > 0 && isspace(line[len-1]))
  62. len--;
  63. line[len] = 0;
  64. return line;
  65. }
  66. static int readline(FILE *f, char **output, size_t *maxlen)
  67. {
  68. size_t len = 0;
  69. int c;
  70. while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n'))
  71. ;
  72. if(c == EOF)
  73. return 0;
  74. do {
  75. if(len+1 >= *maxlen)
  76. {
  77. void *temp = NULL;
  78. size_t newmax;
  79. newmax = (*maxlen ? (*maxlen)<<1 : 32);
  80. if(newmax > *maxlen)
  81. temp = realloc(*output, newmax);
  82. if(!temp)
  83. {
  84. ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen);
  85. return 0;
  86. }
  87. *output = temp;
  88. *maxlen = newmax;
  89. }
  90. (*output)[len++] = c;
  91. (*output)[len] = '\0';
  92. } while((c=fgetc(f)) != EOF && c != '\r' && c != '\n');
  93. return 1;
  94. }
  95. static char *expdup(const char *str)
  96. {
  97. char *output = NULL;
  98. size_t maxlen = 0;
  99. size_t len = 0;
  100. while(*str != '\0')
  101. {
  102. const char *addstr;
  103. size_t addstrlen;
  104. size_t i;
  105. if(str[0] != '$')
  106. {
  107. const char *next = strchr(str, '$');
  108. addstr = str;
  109. addstrlen = next ? (size_t)(next-str) : strlen(str);
  110. str += addstrlen;
  111. }
  112. else
  113. {
  114. str++;
  115. if(*str == '$')
  116. {
  117. const char *next = strchr(str+1, '$');
  118. addstr = str;
  119. addstrlen = next ? (size_t)(next-str) : strlen(str);
  120. str += addstrlen;
  121. }
  122. else
  123. {
  124. bool hasbraces;
  125. char envname[1024];
  126. size_t k = 0;
  127. hasbraces = (*str == '{');
  128. if(hasbraces) str++;
  129. while((isalnum(*str) || *str == '_') && k < sizeof(envname)-1)
  130. envname[k++] = *(str++);
  131. envname[k++] = '\0';
  132. if(hasbraces && *str != '}')
  133. continue;
  134. if(hasbraces) str++;
  135. if((addstr=getenv(envname)) == NULL)
  136. continue;
  137. addstrlen = strlen(addstr);
  138. }
  139. }
  140. if(addstrlen == 0)
  141. continue;
  142. if(addstrlen >= maxlen-len)
  143. {
  144. void *temp = NULL;
  145. size_t newmax;
  146. newmax = len+addstrlen+1;
  147. if(newmax > maxlen)
  148. temp = realloc(output, newmax);
  149. if(!temp)
  150. {
  151. ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, maxlen);
  152. return output;
  153. }
  154. output = temp;
  155. maxlen = newmax;
  156. }
  157. for(i = 0;i < addstrlen;i++)
  158. output[len++] = addstr[i];
  159. output[len] = '\0';
  160. }
  161. return output ? output : calloc(1, 1);
  162. }
  163. static void LoadConfigFromFile(FILE *f)
  164. {
  165. char curSection[128] = "";
  166. char *buffer = NULL;
  167. size_t maxlen = 0;
  168. ConfigEntry *ent;
  169. while(readline(f, &buffer, &maxlen))
  170. {
  171. char *line, *comment;
  172. char key[256] = "";
  173. char value[256] = "";
  174. line = rstrip(lstrip(buffer));
  175. if(!line[0]) continue;
  176. if(line[0] == '[')
  177. {
  178. char *section = line+1;
  179. char *endsection;
  180. endsection = strchr(section, ']');
  181. if(!endsection || section == endsection)
  182. {
  183. ERR("config parse error: bad line \"%s\"\n", line);
  184. continue;
  185. }
  186. if(endsection[1] != 0)
  187. {
  188. char *end = endsection+1;
  189. while(isspace(*end))
  190. ++end;
  191. if(*end != 0 && *end != '#')
  192. {
  193. ERR("config parse error: bad line \"%s\"\n", line);
  194. continue;
  195. }
  196. }
  197. *endsection = 0;
  198. if(strcasecmp(section, "general") == 0)
  199. curSection[0] = 0;
  200. else
  201. {
  202. size_t len, p = 0;
  203. do {
  204. char *nextp = strchr(section, '%');
  205. if(!nextp)
  206. {
  207. strncpy(curSection+p, section, sizeof(curSection)-1-p);
  208. break;
  209. }
  210. len = nextp - section;
  211. if(len > sizeof(curSection)-1-p)
  212. len = sizeof(curSection)-1-p;
  213. strncpy(curSection+p, section, len);
  214. p += len;
  215. section = nextp;
  216. if(((section[1] >= '0' && section[1] <= '9') ||
  217. (section[1] >= 'a' && section[1] <= 'f') ||
  218. (section[1] >= 'A' && section[1] <= 'F')) &&
  219. ((section[2] >= '0' && section[2] <= '9') ||
  220. (section[2] >= 'a' && section[2] <= 'f') ||
  221. (section[2] >= 'A' && section[2] <= 'F')))
  222. {
  223. unsigned char b = 0;
  224. if(section[1] >= '0' && section[1] <= '9')
  225. b = (section[1]-'0') << 4;
  226. else if(section[1] >= 'a' && section[1] <= 'f')
  227. b = (section[1]-'a'+0xa) << 4;
  228. else if(section[1] >= 'A' && section[1] <= 'F')
  229. b = (section[1]-'A'+0x0a) << 4;
  230. if(section[2] >= '0' && section[2] <= '9')
  231. b |= (section[2]-'0');
  232. else if(section[2] >= 'a' && section[2] <= 'f')
  233. b |= (section[2]-'a'+0xa);
  234. else if(section[2] >= 'A' && section[2] <= 'F')
  235. b |= (section[2]-'A'+0x0a);
  236. if(p < sizeof(curSection)-1)
  237. curSection[p++] = b;
  238. section += 3;
  239. }
  240. else if(section[1] == '%')
  241. {
  242. if(p < sizeof(curSection)-1)
  243. curSection[p++] = '%';
  244. section += 2;
  245. }
  246. else
  247. {
  248. if(p < sizeof(curSection)-1)
  249. curSection[p++] = '%';
  250. section += 1;
  251. }
  252. if(p < sizeof(curSection)-1)
  253. curSection[p] = 0;
  254. } while(p < sizeof(curSection)-1 && *section != 0);
  255. curSection[sizeof(curSection)-1] = 0;
  256. }
  257. continue;
  258. }
  259. comment = strchr(line, '#');
  260. if(comment) *(comment++) = 0;
  261. if(!line[0]) continue;
  262. if(sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
  263. sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
  264. sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
  265. {
  266. /* sscanf doesn't handle '' or "" as empty values, so clip it
  267. * manually. */
  268. if(strcmp(value, "\"\"") == 0 || strcmp(value, "''") == 0)
  269. value[0] = 0;
  270. }
  271. else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2)
  272. {
  273. /* Special case for 'key =' */
  274. value[0] = 0;
  275. }
  276. else
  277. {
  278. ERR("config parse error: malformed option line: \"%s\"\n\n", line);
  279. continue;
  280. }
  281. rstrip(key);
  282. if(curSection[0] != 0)
  283. {
  284. size_t len = strlen(curSection);
  285. memmove(&key[len+1], key, sizeof(key)-1-len);
  286. key[len] = '/';
  287. memcpy(key, curSection, len);
  288. }
  289. /* Check if we already have this option set */
  290. ent = cfgBlock.entries;
  291. while((unsigned int)(ent-cfgBlock.entries) < cfgBlock.entryCount)
  292. {
  293. if(strcasecmp(ent->key, key) == 0)
  294. break;
  295. ent++;
  296. }
  297. if((unsigned int)(ent-cfgBlock.entries) >= cfgBlock.entryCount)
  298. {
  299. /* Allocate a new option entry */
  300. ent = realloc(cfgBlock.entries, (cfgBlock.entryCount+1)*sizeof(ConfigEntry));
  301. if(!ent)
  302. {
  303. ERR("config parse error: error reallocating config entries\n");
  304. continue;
  305. }
  306. cfgBlock.entries = ent;
  307. ent = cfgBlock.entries + cfgBlock.entryCount;
  308. cfgBlock.entryCount++;
  309. ent->key = strdup(key);
  310. ent->value = NULL;
  311. }
  312. free(ent->value);
  313. ent->value = expdup(value);
  314. TRACE("found '%s' = '%s'\n", ent->key, ent->value);
  315. }
  316. free(buffer);
  317. }
  318. #ifdef _WIN32
  319. void ReadALConfig(void)
  320. {
  321. al_string ppath = AL_STRING_INIT_STATIC();
  322. WCHAR buffer[MAX_PATH];
  323. const WCHAR *str;
  324. FILE *f;
  325. if(SHGetSpecialFolderPathW(NULL, buffer, CSIDL_APPDATA, FALSE) != FALSE)
  326. {
  327. al_string filepath = AL_STRING_INIT_STATIC();
  328. alstr_copy_wcstr(&filepath, buffer);
  329. alstr_append_cstr(&filepath, "\\alsoft.ini");
  330. TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
  331. f = al_fopen(alstr_get_cstr(filepath), "rt");
  332. if(f)
  333. {
  334. LoadConfigFromFile(f);
  335. fclose(f);
  336. }
  337. alstr_reset(&filepath);
  338. }
  339. GetProcBinary(&ppath, NULL);
  340. if(!alstr_empty(ppath))
  341. {
  342. alstr_append_cstr(&ppath, "\\alsoft.ini");
  343. TRACE("Loading config %s...\n", alstr_get_cstr(ppath));
  344. f = al_fopen(alstr_get_cstr(ppath), "r");
  345. if(f)
  346. {
  347. LoadConfigFromFile(f);
  348. fclose(f);
  349. }
  350. }
  351. if((str=_wgetenv(L"ALSOFT_CONF")) != NULL && *str)
  352. {
  353. al_string filepath = AL_STRING_INIT_STATIC();
  354. alstr_copy_wcstr(&filepath, str);
  355. TRACE("Loading config %s...\n", alstr_get_cstr(filepath));
  356. f = al_fopen(alstr_get_cstr(filepath), "rt");
  357. if(f)
  358. {
  359. LoadConfigFromFile(f);
  360. fclose(f);
  361. }
  362. alstr_reset(&filepath);
  363. }
  364. alstr_reset(&ppath);
  365. }
  366. #else
  367. void ReadALConfig(void)
  368. {
  369. al_string confpaths = AL_STRING_INIT_STATIC();
  370. al_string fname = AL_STRING_INIT_STATIC();
  371. const char *str;
  372. FILE *f;
  373. str = "/etc/openal/alsoft.conf";
  374. TRACE("Loading config %s...\n", str);
  375. f = al_fopen(str, "r");
  376. if(f)
  377. {
  378. LoadConfigFromFile(f);
  379. fclose(f);
  380. }
  381. if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
  382. str = "/etc/xdg";
  383. alstr_copy_cstr(&confpaths, str);
  384. /* Go through the list in reverse, since "the order of base directories
  385. * denotes their importance; the first directory listed is the most
  386. * important". Ergo, we need to load the settings from the later dirs
  387. * first so that the settings in the earlier dirs override them.
  388. */
  389. while(!alstr_empty(confpaths))
  390. {
  391. char *next = strrchr(alstr_get_cstr(confpaths), ':');
  392. if(next)
  393. {
  394. size_t len = next - alstr_get_cstr(confpaths);
  395. alstr_copy_cstr(&fname, next+1);
  396. VECTOR_RESIZE(confpaths, len, len+1);
  397. VECTOR_ELEM(confpaths, len) = 0;
  398. }
  399. else
  400. {
  401. alstr_reset(&fname);
  402. fname = confpaths;
  403. AL_STRING_INIT(confpaths);
  404. }
  405. if(alstr_empty(fname) || VECTOR_FRONT(fname) != '/')
  406. WARN("Ignoring XDG config dir: %s\n", alstr_get_cstr(fname));
  407. else
  408. {
  409. if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
  410. else alstr_append_cstr(&fname, "alsoft.conf");
  411. TRACE("Loading config %s...\n", alstr_get_cstr(fname));
  412. f = al_fopen(alstr_get_cstr(fname), "r");
  413. if(f)
  414. {
  415. LoadConfigFromFile(f);
  416. fclose(f);
  417. }
  418. }
  419. alstr_clear(&fname);
  420. }
  421. #ifdef __APPLE__
  422. CFBundleRef mainBundle = CFBundleGetMainBundle();
  423. if(mainBundle)
  424. {
  425. unsigned char fileName[PATH_MAX];
  426. CFURLRef configURL;
  427. if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), NULL)) &&
  428. CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
  429. {
  430. f = al_fopen((const char*)fileName, "r");
  431. if(f)
  432. {
  433. LoadConfigFromFile(f);
  434. fclose(f);
  435. }
  436. }
  437. }
  438. #endif
  439. if((str=getenv("HOME")) != NULL && *str)
  440. {
  441. alstr_copy_cstr(&fname, str);
  442. if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.alsoftrc");
  443. else alstr_append_cstr(&fname, ".alsoftrc");
  444. TRACE("Loading config %s...\n", alstr_get_cstr(fname));
  445. f = al_fopen(alstr_get_cstr(fname), "r");
  446. if(f)
  447. {
  448. LoadConfigFromFile(f);
  449. fclose(f);
  450. }
  451. }
  452. if((str=getenv("XDG_CONFIG_HOME")) != NULL && str[0] != 0)
  453. {
  454. alstr_copy_cstr(&fname, str);
  455. if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
  456. else alstr_append_cstr(&fname, "alsoft.conf");
  457. }
  458. else
  459. {
  460. alstr_clear(&fname);
  461. if((str=getenv("HOME")) != NULL && str[0] != 0)
  462. {
  463. alstr_copy_cstr(&fname, str);
  464. if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/.config/alsoft.conf");
  465. else alstr_append_cstr(&fname, ".config/alsoft.conf");
  466. }
  467. }
  468. if(!alstr_empty(fname))
  469. {
  470. TRACE("Loading config %s...\n", alstr_get_cstr(fname));
  471. f = al_fopen(alstr_get_cstr(fname), "r");
  472. if(f)
  473. {
  474. LoadConfigFromFile(f);
  475. fclose(f);
  476. }
  477. }
  478. alstr_clear(&fname);
  479. GetProcBinary(&fname, NULL);
  480. if(!alstr_empty(fname))
  481. {
  482. if(VECTOR_BACK(fname) != '/') alstr_append_cstr(&fname, "/alsoft.conf");
  483. else alstr_append_cstr(&fname, "alsoft.conf");
  484. TRACE("Loading config %s...\n", alstr_get_cstr(fname));
  485. f = al_fopen(alstr_get_cstr(fname), "r");
  486. if(f)
  487. {
  488. LoadConfigFromFile(f);
  489. fclose(f);
  490. }
  491. }
  492. if((str=getenv("ALSOFT_CONF")) != NULL && *str)
  493. {
  494. TRACE("Loading config %s...\n", str);
  495. f = al_fopen(str, "r");
  496. if(f)
  497. {
  498. LoadConfigFromFile(f);
  499. fclose(f);
  500. }
  501. }
  502. alstr_reset(&fname);
  503. alstr_reset(&confpaths);
  504. }
  505. #endif
  506. void FreeALConfig(void)
  507. {
  508. unsigned int i;
  509. for(i = 0;i < cfgBlock.entryCount;i++)
  510. {
  511. free(cfgBlock.entries[i].key);
  512. free(cfgBlock.entries[i].value);
  513. }
  514. free(cfgBlock.entries);
  515. }
  516. const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
  517. {
  518. unsigned int i;
  519. char key[256];
  520. if(!keyName)
  521. return def;
  522. if(blockName && strcasecmp(blockName, "general") != 0)
  523. {
  524. if(devName)
  525. snprintf(key, sizeof(key), "%s/%s/%s", blockName, devName, keyName);
  526. else
  527. snprintf(key, sizeof(key), "%s/%s", blockName, keyName);
  528. }
  529. else
  530. {
  531. if(devName)
  532. snprintf(key, sizeof(key), "%s/%s", devName, keyName);
  533. else
  534. {
  535. strncpy(key, keyName, sizeof(key)-1);
  536. key[sizeof(key)-1] = 0;
  537. }
  538. }
  539. for(i = 0;i < cfgBlock.entryCount;i++)
  540. {
  541. if(strcmp(cfgBlock.entries[i].key, key) == 0)
  542. {
  543. TRACE("Found %s = \"%s\"\n", key, cfgBlock.entries[i].value);
  544. if(cfgBlock.entries[i].value[0])
  545. return cfgBlock.entries[i].value;
  546. return def;
  547. }
  548. }
  549. if(!devName)
  550. {
  551. TRACE("Key %s not found\n", key);
  552. return def;
  553. }
  554. return GetConfigValue(NULL, blockName, keyName, def);
  555. }
  556. int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
  557. {
  558. const char *val = GetConfigValue(devName, blockName, keyName, "");
  559. return !!val[0];
  560. }
  561. int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
  562. {
  563. const char *val = GetConfigValue(devName, blockName, keyName, "");
  564. if(!val[0]) return 0;
  565. *ret = val;
  566. return 1;
  567. }
  568. int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
  569. {
  570. const char *val = GetConfigValue(devName, blockName, keyName, "");
  571. if(!val[0]) return 0;
  572. *ret = strtol(val, NULL, 0);
  573. return 1;
  574. }
  575. int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
  576. {
  577. const char *val = GetConfigValue(devName, blockName, keyName, "");
  578. if(!val[0]) return 0;
  579. *ret = strtoul(val, NULL, 0);
  580. return 1;
  581. }
  582. int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
  583. {
  584. const char *val = GetConfigValue(devName, blockName, keyName, "");
  585. if(!val[0]) return 0;
  586. #ifdef HAVE_STRTOF
  587. *ret = strtof(val, NULL);
  588. #else
  589. *ret = (float)strtod(val, NULL);
  590. #endif
  591. return 1;
  592. }
  593. int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
  594. {
  595. const char *val = GetConfigValue(devName, blockName, keyName, "");
  596. if(!val[0]) return 0;
  597. *ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
  598. strcasecmp(val, "on") == 0 || atoi(val) != 0);
  599. return 1;
  600. }
  601. int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
  602. {
  603. const char *val = GetConfigValue(devName, blockName, keyName, "");
  604. if(!val[0]) return !!def;
  605. return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
  606. strcasecmp(val, "on") == 0 || atoi(val) != 0);
  607. }