daemon_config.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /*
  2. Copyright (c) 2017-2018, Feral Interactive
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. * Redistributions of source code must retain the above copyright notice,
  7. this list of conditions and the following disclaimer.
  8. * Redistributions in binary form must reproduce the above copyright
  9. notice, this list of conditions and the following disclaimer in the
  10. documentation and/or other materials provided with the distribution.
  11. * Neither the name of Feral Interactive nor the names of its contributors
  12. may be used to endorse or promote products derived from this software
  13. without specific prior written permission.
  14. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  15. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  17. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  18. LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  19. CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  20. SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  21. INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  22. CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  23. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  24. POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #define _GNU_SOURCE
  27. #include "daemon_config.h"
  28. #include "logging.h"
  29. /* Ben Hoyt's inih library */
  30. #include "ini.h"
  31. #include <linux/limits.h>
  32. #include <pthread.h>
  33. #include <pwd.h>
  34. #include <stdio.h>
  35. #include <string.h>
  36. #include <sys/types.h>
  37. /* Name and possible location of the config file */
  38. #define CONFIG_NAME "gamemode.ini"
  39. /* Default value for the reaper frequency */
  40. #define DEFAULT_REAPER_FREQ 5
  41. /**
  42. * The config holds various details as needed
  43. * and a rwlock to allow config_reload to be called
  44. */
  45. struct GameModeConfig {
  46. pthread_rwlock_t rwlock;
  47. int inotfd;
  48. int inotwd;
  49. char whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
  50. char blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
  51. char startscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
  52. char endscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
  53. char defaultgov[CONFIG_VALUE_MAX];
  54. char desiredgov[CONFIG_VALUE_MAX];
  55. char softrealtime[CONFIG_VALUE_MAX];
  56. long renice;
  57. char ioprio[CONFIG_VALUE_MAX];
  58. long inhibit_screensaver;
  59. long reaper_frequency;
  60. char apply_gpu_optimisations[CONFIG_VALUE_MAX];
  61. long gpu_vendor;
  62. long gpu_device;
  63. long nv_core_clock_mhz_offset;
  64. long nv_mem_clock_mhz_offset;
  65. long nv_perf_level;
  66. long amd_core_clock_percentage;
  67. long amd_mem_clock_percentage;
  68. };
  69. /*
  70. * Add values to a char list
  71. */
  72. static bool append_value_to_list(const char *list_name, const char *value,
  73. char list[CONFIG_LIST_MAX][CONFIG_VALUE_MAX])
  74. {
  75. unsigned int i = 0;
  76. while (*list[i] && ++i < CONFIG_LIST_MAX)
  77. ;
  78. if (i < CONFIG_LIST_MAX) {
  79. strncpy(list[i], value, CONFIG_VALUE_MAX);
  80. if (list[i][CONFIG_VALUE_MAX - 1] != '\0') {
  81. LOG_ERROR("Config: Could not add [%s] to [%s], exceeds length limit of %d\n",
  82. value,
  83. list_name,
  84. CONFIG_VALUE_MAX);
  85. memset(list[i], 0, sizeof(list[i]));
  86. return false;
  87. }
  88. } else {
  89. LOG_ERROR("Config: Could not add [%s] to [%s], exceeds number of %d\n",
  90. value,
  91. list_name,
  92. CONFIG_LIST_MAX);
  93. return false;
  94. }
  95. return true;
  96. }
  97. /*
  98. * Get a long value from a string
  99. */
  100. static bool get_long_value(const char *value_name, const char *value, long *output)
  101. {
  102. char *end = NULL;
  103. long config_value = strtol(value, &end, 10);
  104. if (errno == ERANGE) {
  105. LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
  106. return false;
  107. } else if (!(*value != '\0' && end && *end == '\0')) {
  108. LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
  109. return false;
  110. } else {
  111. *output = config_value;
  112. }
  113. return true;
  114. }
  115. /*
  116. * Get a long value from a hex string
  117. */
  118. static bool get_long_value_hex(const char *value_name, const char *value, long *output)
  119. {
  120. char *end = NULL;
  121. long config_value = strtol(value, &end, 16);
  122. if (errno == ERANGE) {
  123. LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
  124. return false;
  125. } else if (!(*value != '\0' && end && *end == '\0')) {
  126. LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
  127. return false;
  128. } else {
  129. *output = config_value;
  130. }
  131. return true;
  132. }
  133. /*
  134. * Get a string value
  135. */
  136. static bool get_string_value(const char *value, char output[CONFIG_VALUE_MAX])
  137. {
  138. strncpy(output, value, CONFIG_VALUE_MAX - 1);
  139. output[CONFIG_VALUE_MAX - 1] = '\0';
  140. return true;
  141. }
  142. /*
  143. * Handler for the inih callback
  144. */
  145. static int inih_handler(void *user, const char *section, const char *name, const char *value)
  146. {
  147. GameModeConfig *self = (GameModeConfig *)user;
  148. bool valid = false;
  149. if (strcmp(section, "filter") == 0) {
  150. /* Filter subsection */
  151. if (strcmp(name, "whitelist") == 0) {
  152. valid = append_value_to_list(name, value, self->whitelist);
  153. } else if (strcmp(name, "blacklist") == 0) {
  154. valid = append_value_to_list(name, value, self->blacklist);
  155. }
  156. } else if (strcmp(section, "general") == 0) {
  157. /* General subsection */
  158. if (strcmp(name, "reaper_freq") == 0) {
  159. valid = get_long_value(name, value, &self->reaper_frequency);
  160. } else if (strcmp(name, "defaultgov") == 0) {
  161. valid = get_string_value(value, self->defaultgov);
  162. } else if (strcmp(name, "desiredgov") == 0) {
  163. valid = get_string_value(value, self->desiredgov);
  164. } else if (strcmp(name, "softrealtime") == 0) {
  165. valid = get_string_value(value, self->softrealtime);
  166. } else if (strcmp(name, "renice") == 0) {
  167. valid = get_long_value(name, value, &self->renice);
  168. } else if (strcmp(name, "ioprio") == 0) {
  169. valid = get_string_value(value, self->ioprio);
  170. } else if (strcmp(name, "inhibit_screensaver") == 0) {
  171. valid = get_long_value(name, value, &self->inhibit_screensaver);
  172. }
  173. } else if (strcmp(section, "gpu") == 0) {
  174. /* GPU subsection */
  175. if (strcmp(name, "apply_gpu_optimisations") == 0) {
  176. valid = get_string_value(value, self->apply_gpu_optimisations);
  177. } else if (strcmp(name, "gpu_vendor") == 0) {
  178. valid = get_long_value_hex(name, value, &self->gpu_vendor);
  179. } else if (strcmp(name, "gpu_device") == 0) {
  180. valid = get_long_value(name, value, &self->gpu_device);
  181. } else if (strcmp(name, "nv_core_clock_mhz_offset") == 0) {
  182. valid = get_long_value(name, value, &self->nv_core_clock_mhz_offset);
  183. } else if (strcmp(name, "nv_mem_clock_mhz_offset") == 0) {
  184. valid = get_long_value(name, value, &self->nv_mem_clock_mhz_offset);
  185. } else if (strcmp(name, "nv_perf_level") == 0) {
  186. valid = get_long_value(name, value, &self->nv_perf_level);
  187. } else if (strcmp(name, "amd_core_clock_percentage") == 0) {
  188. valid = get_long_value(name, value, &self->amd_core_clock_percentage);
  189. } else if (strcmp(name, "amd_mem_clock_percentage") == 0) {
  190. valid = get_long_value(name, value, &self->amd_mem_clock_percentage);
  191. }
  192. } else if (strcmp(section, "custom") == 0) {
  193. /* Custom subsection */
  194. if (strcmp(name, "start") == 0) {
  195. valid = append_value_to_list(name, value, self->startscripts);
  196. } else if (strcmp(name, "end") == 0) {
  197. valid = append_value_to_list(name, value, self->endscripts);
  198. }
  199. }
  200. if (!valid) {
  201. /* Simply ignore the value, but with a log */
  202. LOG_MSG("Config: Value ignored [%s] %s=%s\n", section, name, value);
  203. }
  204. return 1;
  205. }
  206. /*
  207. * Load the config file
  208. */
  209. static void load_config_files(GameModeConfig *self)
  210. {
  211. /* grab the current dir */
  212. char *config_location_local = get_current_dir_name();
  213. /* Get home config location */
  214. char *config_location_home = NULL;
  215. const char *cfg = getenv("XDG_CONFIG_HOME");
  216. if (cfg) {
  217. config_location_home = realpath(cfg, NULL);
  218. } else {
  219. cfg = getenv("HOME");
  220. if (cfg) {
  221. char *cfg_full = NULL;
  222. if (asprintf(&cfg_full, "%s/.config", cfg) > 0) {
  223. config_location_home = realpath(cfg_full, NULL);
  224. free(cfg_full);
  225. }
  226. } else {
  227. struct passwd *p = getpwuid(getuid());
  228. if (p) {
  229. config_location_home = realpath(p->pw_dir, NULL);
  230. }
  231. }
  232. }
  233. /* Take the write lock for the internal data */
  234. pthread_rwlock_wrlock(&self->rwlock);
  235. /* Clear our config values */
  236. memset(self->ioprio, 0, sizeof(self->ioprio));
  237. memset(self->whitelist, 0, sizeof(self->whitelist));
  238. memset(self->blacklist, 0, sizeof(self->blacklist));
  239. memset(self->startscripts, 0, sizeof(self->startscripts));
  240. memset(self->endscripts, 0, sizeof(self->endscripts));
  241. memset(self->defaultgov, 0, sizeof(self->defaultgov));
  242. memset(self->desiredgov, 0, sizeof(self->desiredgov));
  243. memset(self->softrealtime, 0, sizeof(self->softrealtime));
  244. memset(self->apply_gpu_optimisations, 0, sizeof(self->apply_gpu_optimisations));
  245. self->inhibit_screensaver = 1; /* Defaults to on */
  246. self->renice = 4; /* default value of 4 */
  247. self->reaper_frequency = DEFAULT_REAPER_FREQ;
  248. self->gpu_vendor = 0;
  249. self->gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */
  250. self->nv_core_clock_mhz_offset = 0;
  251. self->nv_mem_clock_mhz_offset = 0;
  252. self->nv_perf_level = -1;
  253. self->amd_core_clock_percentage = 0;
  254. self->amd_mem_clock_percentage = 0;
  255. /*
  256. * Locations to load, in order
  257. * Arrays merge and values overwrite
  258. */
  259. const char *locations[] = {
  260. "/usr/share/gamemode", /* shipped default config */
  261. "/etc", /* administrator config */
  262. config_location_home, /* user defined config eg. $XDG_CONFIG_HOME or $HOME/.config/ */
  263. config_location_local /* local data eg. $PWD */
  264. };
  265. /* Load each file in order and overwrite values */
  266. for (unsigned int i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) {
  267. char *path = NULL;
  268. if (locations[i] && asprintf(&path, "%s/" CONFIG_NAME, locations[i]) > 0) {
  269. FILE *f = fopen(path, "r");
  270. if (f) {
  271. LOG_MSG("Loading config file [%s]\n", path);
  272. int error = ini_parse_file(f, inih_handler, (void *)self);
  273. /* Failure here isn't fatal */
  274. if (error) {
  275. LOG_MSG("Failed to parse config file - error on line %d!\n", error);
  276. }
  277. }
  278. free(path);
  279. }
  280. }
  281. /* clean up memory */
  282. free(config_location_home);
  283. free(config_location_local);
  284. /* Release the lock */
  285. pthread_rwlock_unlock(&self->rwlock);
  286. }
  287. /*
  288. * Copy a config parameter with a lock
  289. */
  290. static void memcpy_locked_config(GameModeConfig *self, void *dst, void *src, size_t n)
  291. {
  292. /* Take the read lock */
  293. pthread_rwlock_rdlock(&self->rwlock);
  294. /* copy the data */
  295. memcpy(dst, src, n);
  296. /* release the lock */
  297. pthread_rwlock_unlock(&self->rwlock);
  298. }
  299. /*
  300. * Create a context object
  301. */
  302. GameModeConfig *config_create(void)
  303. {
  304. GameModeConfig *newconfig = (GameModeConfig *)malloc(sizeof(GameModeConfig));
  305. return newconfig;
  306. }
  307. /*
  308. * Initialise the config
  309. */
  310. void config_init(GameModeConfig *self)
  311. {
  312. pthread_rwlock_init(&self->rwlock, NULL);
  313. /* load the initial config */
  314. load_config_files(self);
  315. }
  316. /*
  317. * Re-load the config file
  318. */
  319. void config_reload(GameModeConfig *self)
  320. {
  321. load_config_files(self);
  322. }
  323. /*
  324. * Destroy the config
  325. */
  326. void config_destroy(GameModeConfig *self)
  327. {
  328. pthread_rwlock_destroy(&self->rwlock);
  329. /* Finally, free the memory */
  330. free(self);
  331. }
  332. /*
  333. * Checks if the client is whitelisted
  334. */
  335. bool config_get_client_whitelisted(GameModeConfig *self, const char *client)
  336. {
  337. /* Take the read lock for the internal data */
  338. pthread_rwlock_rdlock(&self->rwlock);
  339. /* If the whitelist is empty then everything passes */
  340. bool found = true;
  341. if (self->whitelist[0][0]) {
  342. /*
  343. * Check if the value is found in our whitelist
  344. * Currently is a simple strstr check, but could be modified for wildcards etc.
  345. */
  346. found = false;
  347. for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->whitelist[i][0]; i++) {
  348. if (strstr(client, self->whitelist[i])) {
  349. found = true;
  350. }
  351. }
  352. }
  353. /* release the lock */
  354. pthread_rwlock_unlock(&self->rwlock);
  355. return found;
  356. }
  357. /*
  358. * Checks if the client is blacklisted
  359. */
  360. bool config_get_client_blacklisted(GameModeConfig *self, const char *client)
  361. {
  362. /* Take the read lock for the internal data */
  363. pthread_rwlock_rdlock(&self->rwlock);
  364. /*
  365. * Check if the value is found in our whitelist
  366. * Currently is a simple strstr check, but could be modified for wildcards etc.
  367. */
  368. bool found = false;
  369. for (unsigned int i = 0; i < CONFIG_LIST_MAX && self->blacklist[i][0]; i++) {
  370. if (strstr(client, self->blacklist[i])) {
  371. found = true;
  372. }
  373. }
  374. /* release the lock */
  375. pthread_rwlock_unlock(&self->rwlock);
  376. return found;
  377. }
  378. /*
  379. * Gets the reaper frequency
  380. */
  381. void config_get_reaper_thread_frequency(GameModeConfig *self, long *value)
  382. {
  383. memcpy_locked_config(self, value, &self->reaper_frequency, sizeof(long));
  384. }
  385. /*
  386. * Gets the screensaver inhibit setting
  387. */
  388. bool config_get_inhibit_screensaver(GameModeConfig *self)
  389. {
  390. long val;
  391. memcpy_locked_config(self, &val, &self->inhibit_screensaver, sizeof(long));
  392. return val == 1;
  393. }
  394. /*
  395. * Get a set of scripts to call when gamemode starts
  396. */
  397. void config_get_gamemode_start_scripts(GameModeConfig *self,
  398. char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX])
  399. {
  400. memcpy_locked_config(self, scripts, self->startscripts, sizeof(self->startscripts));
  401. }
  402. /*
  403. * Get a set of scripts to call when gamemode ends
  404. */
  405. void config_get_gamemode_end_scripts(GameModeConfig *self,
  406. char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX])
  407. {
  408. memcpy_locked_config(self, scripts, self->endscripts, sizeof(self->startscripts));
  409. }
  410. /*
  411. * Get the chosen default governor
  412. */
  413. void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
  414. {
  415. memcpy_locked_config(self, governor, self->defaultgov, sizeof(self->defaultgov));
  416. }
  417. /*
  418. * Get the chosen desired governor
  419. */
  420. void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
  421. {
  422. memcpy_locked_config(self, governor, self->desiredgov, sizeof(self->desiredgov));
  423. }
  424. /*
  425. * Get the chosen soft realtime behavior
  426. */
  427. void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX])
  428. {
  429. memcpy_locked_config(self, softrealtime, self->softrealtime, sizeof(self->softrealtime));
  430. }
  431. /*
  432. * Get the renice value
  433. */
  434. void config_get_renice_value(GameModeConfig *self, long *value)
  435. {
  436. memcpy_locked_config(self, value, &self->renice, sizeof(long));
  437. }
  438. /*
  439. * Get the ioprio value
  440. */
  441. void config_get_ioprio_value(GameModeConfig *self, int *value)
  442. {
  443. char ioprio_value[CONFIG_VALUE_MAX] = { 0 };
  444. memcpy_locked_config(self, ioprio_value, &self->ioprio, sizeof(self->ioprio));
  445. if (0 == strncmp(ioprio_value, "off", sizeof(self->ioprio)))
  446. *value = IOPRIO_DONT_SET;
  447. else if (0 == strncmp(ioprio_value, "default", sizeof(self->ioprio)))
  448. *value = IOPRIO_RESET_DEFAULT;
  449. else
  450. *value = atoi(ioprio_value);
  451. }
  452. /*
  453. * Get various config info for gpu optimisations
  454. */
  455. void config_get_apply_gpu_optimisations(GameModeConfig *self, char value[CONFIG_VALUE_MAX])
  456. {
  457. memcpy_locked_config(self,
  458. value,
  459. &self->apply_gpu_optimisations,
  460. sizeof(self->apply_gpu_optimisations));
  461. }
  462. void config_get_gpu_vendor(GameModeConfig *self, long *value)
  463. {
  464. memcpy_locked_config(self, value, &self->gpu_vendor, sizeof(long));
  465. }
  466. void config_get_gpu_device(GameModeConfig *self, long *value)
  467. {
  468. memcpy_locked_config(self, value, &self->gpu_device, sizeof(long));
  469. }
  470. void config_get_nv_core_clock_mhz_offset(GameModeConfig *self, long *value)
  471. {
  472. memcpy_locked_config(self, value, &self->nv_core_clock_mhz_offset, sizeof(long));
  473. }
  474. void config_get_nv_mem_clock_mhz_offset(GameModeConfig *self, long *value)
  475. {
  476. memcpy_locked_config(self, value, &self->nv_mem_clock_mhz_offset, sizeof(long));
  477. }
  478. void config_get_nv_perf_level(GameModeConfig *self, long *value)
  479. {
  480. memcpy_locked_config(self, value, &self->nv_perf_level, sizeof(long));
  481. }
  482. void config_get_amd_core_clock_percentage(GameModeConfig *self, long *value)
  483. {
  484. memcpy_locked_config(self, value, &self->amd_core_clock_percentage, sizeof(long));
  485. }
  486. void config_get_amd_mem_clock_percentage(GameModeConfig *self, long *value)
  487. {
  488. memcpy_locked_config(self, value, &self->amd_mem_clock_percentage, sizeof(long));
  489. }