A tool for adding anime to your anidb list.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

781 lines
24KB

  1. #include <stddef.h>
  2. #include <inttypes.h>
  3. #include <getopt.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <stdbool.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <unistd.h>
  11. #include <errno.h>
  12. #include <assert.h>
  13. //#include <toml.h>
  14. #include <limits.h>
  15. #include <time.h>
  16. #include <unistd.h>
  17. #include <ctype.h>
  18. #include "config.h"
  19. #include "error.h"
  20. #include "util.h"
  21. #include "uio.h"
  22. static int show_help(struct conf_entry *ce);
  23. static int show_subcomm_help(struct conf_entry *ce);
  24. static int config_parse_file();
  25. static enum error config_required_check();
  26. static int config_set_str(struct conf_entry *ce, char *arg);
  27. static int config_set_port(struct conf_entry *ce, char *arg);
  28. static int config_set_bool(struct conf_entry *ce, char *arg);
  29. //static int config_def_cachedb(struct conf_entry *ce);
  30. //static int config_action_write_config(struct conf_entry *ce);
  31. /* Everything not explicitly defined, is 0 */
  32. /* If an option only has a long name, the short name also has to be
  33. * defined. For example, a number larger than UCHAR_MAX */
  34. #define SUBCOMM_HELP { \
  35. .l_name = "help", .s_name = 'h', .has_arg = no_argument, \
  36. .action_func = show_subcomm_help, .in_args = true, \
  37. .type = OTYPE_ACTION, .handle_order = 0, \
  38. .h_desc = "Display the help for the subcommand and exit", }
  39. static struct conf_entry subcomm_def_help_opt = SUBCOMM_HELP;
  40. static struct conf_entry ed2k_subopts[] = {
  41. SUBCOMM_HELP,
  42. { .l_name = "link", .s_name = 'l', .has_arg = no_argument,
  43. .set_func = config_set_bool, .in_args = true,
  44. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  45. .h_desc = "Print an ed2k link for the files", },
  46. };
  47. static struct conf_entry modify_add_subopts[] = {
  48. SUBCOMM_HELP,
  49. { .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
  50. .set_func = config_set_bool, .in_args = true,
  51. .type = OTYPE_B, .handle_order = 0, .value_is_set = true,
  52. .h_desc = "Mark the episode as watched when adding/modifying files", },
  53. { .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
  54. .set_func = config_set_str, .in_args = true,
  55. .type = OTYPE_S, .handle_order = 0,
  56. .h_desc = "Set the watched date when adding/modifying files. Either in unix time or in YY-MM-DDTHH:MM:SS", },
  57. };
  58. static struct conf_entry options[] = {
  59. { .l_name = "help", .s_name = 'h', .has_arg = no_argument,
  60. .action_func = show_help, .in_args = true,
  61. .type = OTYPE_ACTION, .handle_order = 0,
  62. .h_desc = "Display the help and exit", },
  63. { .l_name = "username", .s_name = 'u', .has_arg = required_argument,
  64. .set_func = config_set_str, .in_args = true, .in_file = true,
  65. .type = OTYPE_S, .handle_order = 1,
  66. .h_desc = "Sets the username for the login", },
  67. { .l_name = "password", .s_name = 'p', .has_arg = required_argument,
  68. .set_func = config_set_str, .in_args = true, .in_file = true,
  69. .type = OTYPE_S, .handle_order = 1,
  70. .h_desc = "Sets the password for the login", },
  71. { .l_name = "port", .s_name = 'P', .has_arg = required_argument,
  72. .set_func = config_set_port, .in_args = true, .in_file = true,
  73. .type = OTYPE_HU, .handle_order = 1, .value.hu = 29937,
  74. .value_is_set = true,
  75. .h_desc = "Sets port to use for API server communication", },
  76. { .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument,
  77. .set_func = config_set_str, .in_args = true, .in_file = true,
  78. .type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000",
  79. .value_is_set = true,
  80. .h_desc = "Sets the API server address", },
  81. { .l_name = "api-key", .s_name = 'k', .has_arg = required_argument,
  82. .set_func = config_set_str, .in_args = true, .in_file = true,
  83. .type = OTYPE_S, .handle_order = 1,
  84. .h_desc = "Sets the api key used for encryption", },
  85. { .l_name = "save-session", .s_name = 's', .has_arg = no_argument,
  86. .set_func = config_set_bool, .in_args = true, .in_file = true,
  87. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  88. .h_desc = "not implemented", },
  89. { .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument,
  90. .set_func = config_set_bool, .in_args = true, .in_file = false,
  91. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  92. .h_desc = "not implemented", },
  93. { .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
  94. .set_func = config_set_str, .in_args = true, .in_file = true,
  95. .type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/
  96. .h_desc = "Sets the path for the cache database", },
  97. { .l_name = "debug", .s_name = 'D', .has_arg = no_argument,
  98. .set_func = config_set_bool, .in_args = true, .in_file = true,
  99. .type = OTYPE_B, .handle_order = 1, .value_is_set = true,
  100. .h_desc = "Enable debug output", },
  101. /*### cmds ###*/
  102. { .l_name = "server-version", .s_name = UCHAR_MAX + 2,
  103. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  104. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  105. .h_desc = "Request the server version",
  106. },
  107. { .l_name = "version", .s_name = 'v',
  108. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  109. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  110. .h_desc = "Print the caniadd version", },
  111. { .l_name = "uptime", .s_name = UCHAR_MAX + 3,
  112. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  113. .type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
  114. .h_desc = "Request the uptime of the api servers", },
  115. { .l_name = "ed2k", .s_name = 'e',
  116. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  117. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  118. .h_desc = "Run an ed2k hash on the file arguments",
  119. .subopts = ed2k_subopts,
  120. .subopts_count = sizeof(ed2k_subopts) / sizeof(ed2k_subopts[0]),
  121. },
  122. { .l_name = "add", .s_name = 'a',
  123. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  124. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  125. .h_desc = "Add files to your anidb list",
  126. .subopts = modify_add_subopts,
  127. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  128. },
  129. /* Arguments are either mylist id's, or file sizes and names
  130. * in the format '[watch_date/]<size>/<filename>'. The filename can't contain
  131. * '/' characters. */
  132. { .l_name = "modify", .s_name = 'W',
  133. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  134. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  135. .h_desc = "Modify files in your anidb list",
  136. .h_desc_more =
  137. "The arguments are either mylist id's, a file/folder path, or file sizes and names\n"
  138. "in the format '[watch_date/]<size>/<filename>'. The filename can't contain '/' characters\n",
  139. .subopts = modify_add_subopts,
  140. .subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
  141. },
  142. { .l_name = "stats", .s_name = UCHAR_MAX + 10,
  143. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  144. .type = OTYPE_SUBCOMMAND, .handle_order = 1,
  145. .h_desc = "Get the mylist status",
  146. },
  147. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
  148. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  149. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  150. };
  151. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  152. /* Used in show_subcomm_help to output info about the subcommand */
  153. static struct conf_entry *current_subcommand = NULL;
  154. static char **opt_argv = NULL;
  155. static int opt_argc = 0;
  156. static void config_build_getopt_args(int opts_count, const struct conf_entry opts[opts_count],
  157. char out_sopt[opts_count * 2 + 1], struct option out_lopt[opts_count + 1],
  158. bool stop_at_1st_nonopt)
  159. {
  160. int i_sopt = 0, i_lopt = 0;
  161. if (stop_at_1st_nonopt) {
  162. /* Tell getopts to stop at the 1st non-option argument */
  163. out_sopt[i_sopt++] = '+';
  164. }
  165. for (int i = 0; i < opts_count; i++) {
  166. if (opts[i].type == OTYPE_SUBCOMMAND)
  167. continue;
  168. /* Short options */
  169. if (opts[i].s_name && opts[i].s_name <= UCHAR_MAX) {
  170. out_sopt[i_sopt++] = opts[i].s_name;
  171. if (opts[i].has_arg == required_argument)
  172. out_sopt[i_sopt++] = ':';
  173. assert(opts[i].has_arg == required_argument ||
  174. opts[i].has_arg == no_argument);
  175. }
  176. /* Long opts */
  177. if (opts[i].l_name) {
  178. assert(opts[i].s_name);
  179. out_lopt[i_lopt].name = opts[i].l_name;
  180. out_lopt[i_lopt].has_arg = opts[i].has_arg;
  181. out_lopt[i_lopt].flag = NULL;
  182. out_lopt[i_lopt].val = opts[i].s_name;
  183. i_lopt++;
  184. }
  185. }
  186. out_sopt[i_sopt] = '\0';
  187. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  188. }
  189. static int config_read_args(int argc, char **argv,
  190. int opts_count, struct conf_entry opts[opts_count],
  191. char sopt[opts_count * 2 + 1], struct option lopt[opts_count + 1], int level)
  192. {
  193. int optc, err = NOERR;
  194. /* Reinitialize getopt with 0 here */
  195. optind = 0;
  196. //uio_debug("Before %d %s", argc, argv[0]);
  197. while ((optc = getopt_long(argc, argv, sopt,
  198. lopt, NULL)) >= 0) {
  199. bool handled = false;
  200. //uio_debug("Optc: %c", optc);
  201. for (int i = 0; i < opts_count; i++) {
  202. if (opts[i].handle_order != level) {
  203. /* Lie a lil :x */
  204. handled = true;
  205. continue;
  206. }
  207. if (optc == opts[i].s_name) {
  208. if (opts[i].type != OTYPE_ACTION)
  209. err = opts[i].set_func(&opts[i], optarg);
  210. else
  211. err = opts[i].action_func(&opts[i]);
  212. if (err != NOERR)
  213. goto end;
  214. opts[i].value_is_set = true;
  215. handled = true;
  216. break;
  217. }
  218. }
  219. if (handled)
  220. continue;
  221. if (optc == '?') {
  222. err = ERR_OPT_FAILED;
  223. goto end;
  224. } else {
  225. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  226. err = ERR_OPT_UNHANDLED;
  227. goto end;
  228. }
  229. }
  230. end:
  231. return err;
  232. }
  233. static enum error config_required_check()
  234. {
  235. enum error err = NOERR;
  236. for (int i = 0; i < options_count; i++) {
  237. if (options[i].required && !options[i].value_is_set) {
  238. printf("Argument %s is required!\n", options[i].l_name);
  239. err = ERR_OPT_REQUIRED;
  240. }
  241. }
  242. return err;
  243. }
  244. static enum error config_parse_subcomm_subopts(struct conf_entry *subcomm)
  245. {
  246. char sopt[64];
  247. struct option lopt[32];
  248. enum error err;
  249. /* Set the global current subcommand pointer */
  250. current_subcommand = subcomm;
  251. config_build_getopt_args(subcomm->subopts_count, subcomm->subopts, sopt, lopt, false);
  252. //uio_debug("Parsing subconn %s", subcomm->l_name);
  253. //uio_debug("sopt: %s", sopt);
  254. /* Update args for next parsing and nonopts parsing for later */
  255. opt_argv = &opt_argv[optind];
  256. opt_argc = config_get_nonopt_count();
  257. /* argv[0] (which is the subcommand string) will be treated as the filename here, and skipped */
  258. err = config_read_args(opt_argc, opt_argv,
  259. subcomm->subopts_count, subcomm->subopts, sopt, lopt, 0);
  260. if (err == NOERR) {
  261. /* Mark subcommand as set */
  262. subcomm->value_is_set = true;
  263. subcomm->value.b = true;
  264. }
  265. current_subcommand = NULL;
  266. return err;
  267. }
  268. static enum error config_parse_subcommands()
  269. {
  270. const char *subcomm_str;
  271. enum error err = ERR_OPT_NOTFOUND;
  272. if (config_get_nonopt_count() <= 0) {
  273. return ERR_OPT_NO_SUBCOMMAND;
  274. }
  275. subcomm_str = config_get_nonopt(0);
  276. for (int i = 0; i < options_count; i++) {
  277. if (options[i].type != OTYPE_SUBCOMMAND)
  278. continue;
  279. if (strcmp(options[i].l_name, subcomm_str) != 0)
  280. continue;
  281. if (options[i].subopts == NULL) {
  282. /* If no suboptions is defined, define the default
  283. * help one here */
  284. options[i].subopts = &subcomm_def_help_opt;
  285. options[i].subopts_count = 1;
  286. }
  287. /* Found, parse subopts */
  288. err = config_parse_subcomm_subopts(&options[i]);
  289. }
  290. return err;
  291. }
  292. enum error config_parse(int argc, char **argv)
  293. {
  294. enum error err = NOERR;
  295. char sopt[options_count * 2 + 1];
  296. struct option lopt[options_count + 1];
  297. opt_argv = argv;
  298. opt_argc = argc;
  299. config_build_getopt_args(options_count, options, sopt, lopt, true);
  300. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 0);
  301. if (err != NOERR)
  302. goto end;
  303. err = config_parse_file();
  304. if (err != NOERR)
  305. goto end;
  306. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 1);
  307. if (err != NOERR)
  308. goto end;
  309. /* Set defaults for those, that didn't got set above */
  310. for (int i = 0; i < options_count; i++) {
  311. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  312. options[i].default_func) {
  313. err = options[i].default_func(&options[i]);
  314. if (err != NOERR)
  315. goto end;
  316. options[i].value_is_set = true;
  317. }
  318. }
  319. err = config_read_args(argc, argv, options_count, options, sopt, lopt, 2);
  320. if (err != NOERR)
  321. goto end;
  322. err = config_required_check();
  323. if (err != NOERR)
  324. goto end;
  325. /* Now that all of the global arguments are parsed, do the subcommands */
  326. err = config_parse_subcommands();
  327. if (err != NOERR) {
  328. if (err == ERR_OPT_NO_SUBCOMMAND)
  329. printf("No subcommand given!\n\n");
  330. else if (err == ERR_OPT_NOTFOUND)
  331. printf("The given subcommand doesn't exist\n\n");
  332. if (err != ERR_OPT_EXIT) /* If err is this, then help() was already called */
  333. show_help(NULL);
  334. }
  335. end:
  336. if (err != NOERR)
  337. config_free();
  338. return err;
  339. }
  340. #if 0
  341. static int config_def_config_dir(struct conf_entry *ce)
  342. {
  343. char *dir;
  344. int len;
  345. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  346. const char *home_env = getenv("HOME");
  347. if (!home_env) {
  348. /* Fix this at a later date with getuid and getpw */
  349. fprintf(stderr, "HOME environment variable not found!\n");
  350. return ERR_NOTFOUND;
  351. }
  352. len = snprintf(NULL, 0, format, home_env);
  353. if (len == -1) {
  354. int err = errno;
  355. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  356. return err;
  357. }
  358. dir = malloc(len + 1);
  359. sprintf(dir, format, home_env);
  360. ce->value.s = dir;
  361. ce->value_is_dyn = true;
  362. return NOERR;
  363. }
  364. #endif
  365. #if 0
  366. static int config_def_cachedb(struct conf_entry *ce)
  367. {
  368. bool dh_free = false;
  369. const char *data_home = getenv("XDG_DATA_HOME");
  370. if (!data_home) {
  371. const char *home = util_get_home();
  372. if (!home)
  373. return ERR_OPT_FAILED;
  374. sprintf(NULL, "%s/.local/share", home);
  375. }
  376. return NOERR;
  377. }
  378. #endif
  379. static int config_set_str(struct conf_entry *ce, char *arg)
  380. {
  381. // TODO use realpath(3), when necessary
  382. ce->value.s = arg;
  383. return NOERR;
  384. }
  385. static int config_set_port(struct conf_entry *ce, char *arg)
  386. {
  387. long portval = strtol(arg, NULL, 10);
  388. /* A zero return will be invalid no matter if strtol succeeded or not */
  389. if (portval > UINT16_MAX || portval <= 0) {
  390. fprintf(stderr, "Invalid port value '%s'\n", arg);
  391. return ERR_OPT_INVVAL;
  392. }
  393. ce->value.hu = (uint16_t)portval;
  394. return NOERR;
  395. }
  396. static int config_set_bool(struct conf_entry *ce, char *arg)
  397. {
  398. ce->value.b = true;
  399. return NOERR;
  400. }
  401. static int show_subcomm_help(struct conf_entry *ce)
  402. {
  403. assert(current_subcommand);
  404. const struct conf_entry *subopts = current_subcommand->subopts;
  405. printf(
  406. "Usage: caniadd [OPTIONS] %s [SUBCOMMAND_OPTIONS]\n"
  407. "%s\n"
  408. "%s"
  409. "\n"
  410. "SUBCOMMAND_OPTIONS:\n",
  411. current_subcommand->l_name,
  412. current_subcommand->h_desc,
  413. current_subcommand->h_desc_more ? current_subcommand->h_desc_more : ""
  414. );
  415. for (size_t i = 0; i < current_subcommand->subopts_count; i++) {
  416. int printed = 0, pad;
  417. printed += printf(" ");
  418. if (subopts[i].l_name)
  419. printed += printf("--%s", subopts[i].l_name);
  420. if (subopts[i].s_name < UCHAR_MAX)
  421. printed += printf(", -%c", subopts[i].s_name);
  422. if (subopts[i].has_arg != no_argument)
  423. printed += printf(" arg");
  424. pad = 25 - printed;
  425. if (pad <= 0)
  426. pad = 1;
  427. printf("%*s%s", pad, "", subopts[i].h_desc);
  428. if (subopts[i].value_is_set) {
  429. printf(" Val: ");
  430. if (subopts[i].type == OTYPE_S)
  431. printed += printf("%s", subopts[i].value.s);
  432. else if (subopts[i].type == OTYPE_HU)
  433. printed += printf("%hu", subopts[i].value.hu);
  434. else if (subopts[i].type == OTYPE_B)
  435. printed += printf("%s", subopts[i].value.b ? "true" : "false");
  436. }
  437. printf("\n");
  438. }
  439. return ERR_OPT_EXIT;
  440. }
  441. static int show_help(struct conf_entry *ce)
  442. {
  443. printf(
  444. "Usage: caniadd [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]\n"
  445. "Caniadd will add files to an AniDB list, and possibly more.\n"
  446. "\n"
  447. "OPTIONS:\n"
  448. );
  449. for (size_t i = 0; i < options_count; i++) {
  450. if (options[i].type == OTYPE_SUBCOMMAND)
  451. continue;
  452. int printed = 0, pad;
  453. printed += printf(" ");
  454. if (options[i].l_name)
  455. printed += printf("--%s", options[i].l_name);
  456. if (options[i].s_name < UCHAR_MAX)
  457. printed += printf(", -%c", options[i].s_name);
  458. if (options[i].has_arg != no_argument)
  459. printed += printf(" arg");
  460. pad = 25 - printed;
  461. if (pad <= 0)
  462. pad = 1;
  463. printf("%*s%s", pad, "", options[i].h_desc);
  464. if (options[i].value_is_set) {
  465. printf(" Val: ");
  466. if (options[i].type == OTYPE_S)
  467. printed += printf("%s", options[i].value.s);
  468. else if (options[i].type == OTYPE_HU)
  469. printed += printf("%hu", options[i].value.hu);
  470. else if (options[i].type == OTYPE_B)
  471. printed += printf("%s", options[i].value.b ? "true" : "false");
  472. }
  473. printf("\n");
  474. }
  475. printf("\nSUBCOMMANDS:\n");
  476. for (size_t i = 0; i < options_count; i++) {
  477. if (options[i].type != OTYPE_SUBCOMMAND)
  478. continue;
  479. int printed = 0, pad;
  480. printed += printf(" %s", options[i].l_name);
  481. pad = 25 - printed;
  482. printf("%*s%s\n", pad, "", options[i].h_desc);
  483. }
  484. return ERR_OPT_EXIT;
  485. }
  486. static int config_parse_file()
  487. {
  488. // TODO implement this
  489. #if 0
  490. assert(conf.config_file_path);
  491. FILE *f = fopen(conf.config_file_path, "rb");
  492. char errbuf[200];
  493. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  494. fclose(f);
  495. if (!tml) {
  496. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  497. return ERR_TOML_PARSE_ERROR;
  498. }
  499. toml_datum_t port = toml_int_in(tml, "port");
  500. if (port.ok)
  501. conf.port = (uint16_t)port.u.i;
  502. else
  503. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  504. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  505. if (dldir.ok) {
  506. conf.default_download_dir = dldir.u.s;
  507. printf("%s\n", dldir.u.s);
  508. conf_dyn.default_download_dir = dldir.u.s;
  509. conf_dyn.default_download_dir = true;
  510. /* TODO is this always malloced?? if yes, remve dyn check */
  511. } else {
  512. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  513. }
  514. toml_free(tml);
  515. #endif
  516. return NOERR;
  517. }
  518. int config_free()
  519. {
  520. for (int i = 0; i < options_count; i++) {
  521. if (options[i].value_is_dyn) {
  522. free(options[i].value.s);
  523. options[i].value.s = NULL;
  524. options[i].value_is_dyn = false;
  525. options[i].value_is_set = false;
  526. }
  527. }
  528. return NOERR;
  529. }
  530. #if 0
  531. static int config_action_write_config(struct conf_entry *ce)
  532. {
  533. /* This is the success return here */
  534. int err = ERR_OPT_EXIT, plen;
  535. const char *config_dir;
  536. FILE *f = NULL;
  537. config_dir = config_get("config-dir");
  538. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  539. char path[plen + 1];
  540. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  541. for (int i = 0; i < 2; i++) {
  542. f = fopen(path, "wb");
  543. if (!f) {
  544. int errn = errno;
  545. if (errn == ENOENT && i == 0) {
  546. /* Try to create parent directory */
  547. if (mkdir(config_dir, 0755) == -1) {
  548. err = errno;
  549. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  550. goto end;
  551. }
  552. } else {
  553. err = errn;
  554. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  555. goto end;
  556. }
  557. } else {
  558. break;
  559. }
  560. }
  561. for (int i = 0; i < options_count; i++) {
  562. if (!options[i].in_file)
  563. continue;
  564. fprintf(f, "%s = ", options[i].l_name);
  565. switch (options[i].type) {
  566. case OTYPE_S:
  567. // TODO toml escaping
  568. fprintf(f, "\"%s\"\n", options[i].value.s);
  569. break;
  570. case OTYPE_HU:
  571. fprintf(f, "%hu\n", options[i].value.hu);
  572. break;
  573. case OTYPE_B:
  574. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  575. break;
  576. default:
  577. break;
  578. }
  579. }
  580. config_dump();
  581. end:
  582. if (f)
  583. fclose(f);
  584. return err;
  585. }
  586. #endif
  587. enum error config_get_inter(int cenf_count, const struct conf_entry cenf[cenf_count],
  588. const char *key, void **out)
  589. {
  590. enum error err = ERR_OPT_NOTFOUND;
  591. for (int i = 0; i < cenf_count; i++) {
  592. const struct conf_entry *cc = &cenf[i];
  593. if (strcmp(cc->l_name, key) == 0) {
  594. if (cc->value_is_set) {
  595. if (out)
  596. *out = (void**)&cc->value.s;
  597. err = NOERR;
  598. } else {
  599. err = ERR_OPT_UNSET;
  600. }
  601. break;
  602. }
  603. }
  604. return err;
  605. }
  606. enum error config_get(const char *key, void **out)
  607. {
  608. return config_get_inter(options_count, options, key, out);
  609. }
  610. enum error config_get_subopt(const char *subcomm, const char *key, void **out)
  611. {
  612. for (int i = 0; i < options_count; i++) {
  613. struct conf_entry *cc = &options[i];
  614. if (strcmp(cc->l_name, subcomm) == 0) {
  615. return config_get_inter(cc->subopts_count, cc->subopts, key, out);
  616. }
  617. }
  618. return ERR_OPT_NOTFOUND;
  619. }
  620. const char *config_get_nonopt(int index)
  621. {
  622. if (index >= config_get_nonopt_count())
  623. return NULL;
  624. return opt_argv[optind + index];
  625. }
  626. int config_get_nonopt_count()
  627. {
  628. return opt_argc - optind;
  629. }
  630. void config_dump()
  631. {
  632. for (int i = 0; i < options_count; i++) {
  633. if (options[i].type == OTYPE_ACTION)
  634. continue;
  635. printf("%s: ", options[i].l_name);
  636. if (!options[i].value_is_set) {
  637. printf("[UNSET (>.<)]\n");
  638. continue;
  639. }
  640. switch (options[i].type) {
  641. case OTYPE_S:
  642. printf("%s\n", options[i].value.s);
  643. break;
  644. case OTYPE_HU:
  645. printf("%hu\n", options[i].value.hu);
  646. break;
  647. case OTYPE_B:
  648. printf("%s\n", options[i].value.b ? "True" : "False");
  649. break;
  650. default:
  651. printf("Error :(\n");
  652. break;
  653. }
  654. }
  655. }