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.

540 lines
16KB

  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. static int show_help(struct conf_entry *ce);
  22. static int config_parse_file();
  23. static enum error config_required_check();
  24. static int config_set_str(struct conf_entry *ce, char *arg);
  25. static int config_set_port(struct conf_entry *ce, char *arg);
  26. static int config_set_bool(struct conf_entry *ce, char *arg);
  27. //static int config_def_cachedb(struct conf_entry *ce);
  28. //static int config_action_write_config(struct conf_entry *ce);
  29. /* Everything not explicitly defined, is 0 */
  30. /* If an option only has a long name, the short name also has to be
  31. * defined. For example, a number larger than UCHAR_MAX */
  32. static struct conf_entry options[] = {
  33. { .l_name = "help", .s_name = 'h', .has_arg = no_argument,
  34. .action_func = show_help, .in_args = true,
  35. .type = OTYPE_ACTION, .handle_order = 0 },
  36. /*
  37. { .l_name = "config-dir", .s_name = 'b', .has_arg = required_argument,
  38. .default_func = config_def_config_dir, .set_func = config_set_str,
  39. .in_args = true, .type = OTYPE_S, .handle_order = 0 },
  40. { .l_name = "default-download-dir", .s_name = 'd', .has_arg = required_argument,
  41. .default_func = config_def_default_download_dir, .set_func = config_set_str,
  42. .in_file = true, .in_args = true, .type = OTYPE_S, .handle_order = 1 },
  43. { .l_name = "port", .s_name = 'p', .has_arg = required_argument,
  44. .set_func = config_set_port, .value.hu = 21729, .value_is_set = true,
  45. .in_file = true, .in_args = true, .type = OTYPE_HU, .handle_order = 1 },
  46. { .l_name = "foreground", .s_name = 'f', .has_arg = no_argument,
  47. .set_func = config_set_bool, .value.b = false, .value_is_set = true,
  48. .in_args = true, .type = OTYPE_B, .handle_order = 1 },
  49. { .l_name = "write-config", .s_name = UCHAR_MAX + 1, .has_arg = no_argument,
  50. .action_func = config_action_write_config, .value_is_set = true,
  51. .in_args = true, .type = OTYPE_ACTION, .handle_order = 2 },
  52. { .l_name = "peer-id", .s_name = UCHAR_MAX + 2, .has_arg = required_argument,
  53. .default_func = config_def_peer_id, .type = OTYPE_S, .handle_order = 1 },
  54. */
  55. { .l_name = "username", .s_name = 'u', .has_arg = required_argument,
  56. .set_func = config_set_str, .in_args = true, .in_file = true,
  57. .type = OTYPE_S, .handle_order = 1 },
  58. { .l_name = "password", .s_name = 'p', .has_arg = required_argument,
  59. .set_func = config_set_str, .in_args = true, .in_file = true,
  60. .type = OTYPE_S, .handle_order = 1 },
  61. { .l_name = "port", .s_name = 'P', .has_arg = required_argument,
  62. .set_func = config_set_port, .in_args = true, .in_file = true,
  63. .type = OTYPE_HU, .handle_order = 1, .value.hu = 29937,
  64. .value_is_set = true },
  65. { .l_name = "api-server", .s_name = UCHAR_MAX + 1, .has_arg = required_argument,
  66. .set_func = config_set_str, .in_args = true, .in_file = true,
  67. .type = OTYPE_S, .handle_order = 1, .value.s = "api.anidb.net:9000",
  68. .value_is_set = true },
  69. { .l_name = "api-key", .s_name = 'k', .has_arg = required_argument,
  70. .set_func = config_set_str, .in_args = true, .in_file = true,
  71. .type = OTYPE_S, .handle_order = 1, },
  72. { .l_name = "save-session", .s_name = 's', .has_arg = no_argument,
  73. .set_func = config_set_bool, .in_args = true, .in_file = true,
  74. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  75. { .l_name = "destroy-session", .s_name = 'S', .has_arg = no_argument,
  76. .set_func = config_set_bool, .in_args = true, .in_file = false,
  77. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  78. { .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
  79. .set_func = config_set_bool, .in_args = true,
  80. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  81. { .l_name = "link", .s_name = 'l', .has_arg = no_argument,
  82. .set_func = config_set_bool, .in_args = true,
  83. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  84. { .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
  85. .set_func = config_set_str, .in_args = true, .in_file = true,
  86. .type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/ },
  87. { .l_name = "debug", .s_name = 'D', .has_arg = no_argument,
  88. .set_func = config_set_bool, .in_args = true, .in_file = true,
  89. .type = OTYPE_B, .handle_order = 1, .value_is_set = true, },
  90. /*### cmd ###*/
  91. { .l_name = "server-version", .s_name = UCHAR_MAX + 2,
  92. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  93. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  94. { .l_name = "version", .s_name = 'v',
  95. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  96. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  97. { .l_name = "uptime", .s_name = UCHAR_MAX + 3,
  98. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  99. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },
  100. { .l_name = "ed2k", .s_name = 'e',
  101. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  102. .type = OTYPE_B, .handle_order = 1 },
  103. { .l_name = "add", .s_name = 'a',
  104. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  105. .type = OTYPE_B, .handle_order = 1 },
  106. /*{ .l_name = "stats", .s_name = UCHAR_MAX + 4,
  107. .has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
  108. .type = OTYPE_B, .handle_order = 1, .value_is_set = true },*/
  109. };
  110. static const size_t options_count = sizeof(options) / sizeof(options[0]);
  111. static const char **opt_argv = NULL;
  112. static int opt_argc = 0;
  113. static void config_build_getopt_args(char out_sopt[options_count * 2 + 1],
  114. struct option out_lopt[options_count + 1])
  115. {
  116. int i_sopt = 0, i_lopt = 0;
  117. for (int i = 0; i < options_count; i++) {
  118. /* Short options */
  119. if (options[i].s_name && options[i].s_name <= UCHAR_MAX) {
  120. out_sopt[i_sopt++] = options[i].s_name;
  121. if (options[i].has_arg == required_argument)
  122. out_sopt[i_sopt++] = ':';
  123. assert(options[i].has_arg == required_argument ||
  124. options[i].has_arg == no_argument);
  125. }
  126. /* Long options */
  127. if (options[i].l_name) {
  128. assert(options[i].s_name);
  129. out_lopt[i_lopt].name = options[i].l_name;
  130. out_lopt[i_lopt].has_arg = options[i].has_arg;
  131. out_lopt[i_lopt].flag = NULL;
  132. out_lopt[i_lopt].val = options[i].s_name;
  133. i_lopt++;
  134. }
  135. }
  136. out_sopt[i_sopt] = '\0';
  137. memset(&out_lopt[i_lopt], 0, sizeof(struct option));
  138. }
  139. static int config_read_args(int argc, char **argv, char sopt[options_count * 2 + 1],
  140. struct option lopt[options_count + 1], int level)
  141. {
  142. int optc, err = NOERR;
  143. optind = 1;
  144. while ((optc = getopt_long(argc, argv, sopt,
  145. lopt, NULL)) >= 0) {
  146. bool handled = false;
  147. for (int i = 0; i < options_count; i++) {
  148. if (options[i].handle_order != level) {
  149. /* Lie a lil :x */
  150. handled = true;
  151. continue;
  152. }
  153. if (optc == options[i].s_name) {
  154. if (options[i].type != OTYPE_ACTION)
  155. err = options[i].set_func(&options[i], optarg);
  156. else
  157. err = options[i].action_func(&options[i]);
  158. if (err != NOERR)
  159. goto end;
  160. options[i].value_is_set = true;
  161. handled = true;
  162. break;
  163. }
  164. }
  165. if (handled)
  166. continue;
  167. if (optc == '?') {
  168. err = ERR_OPT_FAILED;
  169. goto end;
  170. } else {
  171. fprintf(stderr, "Unhandled option? '%c'\n", optc);
  172. err = ERR_OPT_UNHANDLED;
  173. goto end;
  174. }
  175. }
  176. end:
  177. return err;
  178. }
  179. static enum error config_required_check()
  180. {
  181. enum error err = NOERR;
  182. for (int i = 0; i < options_count; i++) {
  183. if (options[i].required && !options[i].value_is_set) {
  184. printf("Argument %s is required!\n", options[i].l_name);
  185. err = ERR_OPT_REQUIRED;
  186. }
  187. }
  188. return err;
  189. }
  190. enum error config_parse(int argc, char **argv)
  191. {
  192. enum error err = NOERR;
  193. char sopt[options_count * 2 + 1];
  194. struct option lopt[options_count + 1];
  195. opt_argv = (const char**)argv;
  196. opt_argc = argc;
  197. config_build_getopt_args(sopt, lopt);
  198. err = config_read_args(argc, argv, sopt, lopt, 0);
  199. if (err != NOERR)
  200. goto end;
  201. err = config_parse_file();
  202. if (err != NOERR)
  203. goto end;
  204. err = config_read_args(argc, argv, sopt, lopt, 1);
  205. if (err != NOERR)
  206. goto end;
  207. /* Set defaults for those, that didn't got set above */
  208. for (int i = 0; i < options_count; i++) {
  209. if (!options[i].value_is_set && options[i].type != OTYPE_ACTION &&
  210. options[i].default_func) {
  211. err = options[i].default_func(&options[i]);
  212. if (err != NOERR)
  213. goto end;
  214. options[i].value_is_set = true;
  215. }
  216. }
  217. err = config_read_args(argc, argv, sopt, lopt, 2);
  218. if (err != NOERR)
  219. goto end;
  220. err = config_required_check();
  221. end:
  222. if (err != NOERR)
  223. config_free();
  224. return err;
  225. }
  226. #if 0
  227. static int config_def_config_dir(struct conf_entry *ce)
  228. {
  229. char *dir;
  230. int len;
  231. const char *format = "%s/.config/" CONFIG_DIR_NAME;
  232. const char *home_env = getenv("HOME");
  233. if (!home_env) {
  234. /* Fix this at a later date with getuid and getpw */
  235. fprintf(stderr, "HOME environment variable not found!\n");
  236. return ERR_NOTFOUND;
  237. }
  238. len = snprintf(NULL, 0, format, home_env);
  239. if (len == -1) {
  240. int err = errno;
  241. fprintf(stderr, "Failed to call funky snpintf: %s\n", strerror(err));
  242. return err;
  243. }
  244. dir = malloc(len + 1);
  245. sprintf(dir, format, home_env);
  246. ce->value.s = dir;
  247. ce->value_is_dyn = true;
  248. return NOERR;
  249. }
  250. #endif
  251. #if 0
  252. static int config_def_cachedb(struct conf_entry *ce)
  253. {
  254. bool dh_free = false;
  255. const char *data_home = getenv("XDG_DATA_HOME");
  256. if (!data_home) {
  257. const char *home = util_get_home();
  258. if (!home)
  259. return ERR_OPT_FAILED;
  260. sprintf(NULL, "%s/.local/share", home);
  261. }
  262. return NOERR;
  263. }
  264. #endif
  265. static int config_set_str(struct conf_entry *ce, char *arg)
  266. {
  267. // TODO use realpath(3), when necessary
  268. ce->value.s = arg;
  269. return NOERR;
  270. }
  271. static int config_set_port(struct conf_entry *ce, char *arg)
  272. {
  273. long portval = strtol(arg, NULL, 10);
  274. /* A zero return will be invalid no matter if strtol succeeded or not */
  275. if (portval > UINT16_MAX || portval <= 0) {
  276. fprintf(stderr, "Invalid port value '%s'\n", arg);
  277. return ERR_OPT_INVVAL;
  278. }
  279. ce->value.hu = (uint16_t)portval;
  280. return NOERR;
  281. }
  282. static int config_set_bool(struct conf_entry *ce, char *arg)
  283. {
  284. ce->value.b = true;
  285. return NOERR;
  286. }
  287. static int show_help(struct conf_entry *ce)
  288. {
  289. printf("Todo...\n");
  290. return ERR_OPT_EXIT;
  291. }
  292. static int config_parse_file()
  293. {
  294. // TODO implement this
  295. #if 0
  296. assert(conf.config_file_path);
  297. FILE *f = fopen(conf.config_file_path, "rb");
  298. char errbuf[200];
  299. toml_table_t *tml = toml_parse_file(f, errbuf, sizeof(errbuf));
  300. fclose(f);
  301. if (!tml) {
  302. fprintf(stderr, "Failed to parse config toml: %s\n", errbuf);
  303. return ERR_TOML_PARSE_ERROR;
  304. }
  305. toml_datum_t port = toml_int_in(tml, "port");
  306. if (port.ok)
  307. conf.port = (uint16_t)port.u.i;
  308. else
  309. fprintf(stderr, "Failed to parse port from config toml: %s\n", errbuf);
  310. toml_datum_t dldir = toml_string_in(tml, "default_download_dir");
  311. if (dldir.ok) {
  312. conf.default_download_dir = dldir.u.s;
  313. printf("%s\n", dldir.u.s);
  314. conf_dyn.default_download_dir = dldir.u.s;
  315. conf_dyn.default_download_dir = true;
  316. /* TODO is this always malloced?? if yes, remve dyn check */
  317. } else {
  318. fprintf(stderr, "Failed to parse download dir from config toml: %s\n", errbuf);
  319. }
  320. toml_free(tml);
  321. #endif
  322. return NOERR;
  323. }
  324. int config_free()
  325. {
  326. for (int i = 0; i < options_count; i++) {
  327. if (options[i].value_is_dyn) {
  328. free(options[i].value.s);
  329. options[i].value.s = NULL;
  330. options[i].value_is_dyn = false;
  331. options[i].value_is_set = false;
  332. }
  333. }
  334. return NOERR;
  335. }
  336. #if 0
  337. static int config_action_write_config(struct conf_entry *ce)
  338. {
  339. /* This is the success return here */
  340. int err = ERR_OPT_EXIT, plen;
  341. const char *config_dir;
  342. FILE *f = NULL;
  343. config_dir = config_get("config-dir");
  344. plen = snprintf(NULL, 0, "%s/%s", config_dir, CONFIG_FILE_NAME);
  345. char path[plen + 1];
  346. snprintf(path, plen + 1, "%s/%s", config_dir, CONFIG_FILE_NAME);
  347. for (int i = 0; i < 2; i++) {
  348. f = fopen(path, "wb");
  349. if (!f) {
  350. int errn = errno;
  351. if (errn == ENOENT && i == 0) {
  352. /* Try to create parent directory */
  353. if (mkdir(config_dir, 0755) == -1) {
  354. err = errno;
  355. fprintf(stderr, "Config mkdir failed: %s\n", strerror(err));
  356. goto end;
  357. }
  358. } else {
  359. err = errn;
  360. fprintf(stderr, "Config fopen failed: %s\n", strerror(err));
  361. goto end;
  362. }
  363. } else {
  364. break;
  365. }
  366. }
  367. for (int i = 0; i < options_count; i++) {
  368. if (!options[i].in_file)
  369. continue;
  370. fprintf(f, "%s = ", options[i].l_name);
  371. switch (options[i].type) {
  372. case OTYPE_S:
  373. // TODO toml escaping
  374. fprintf(f, "\"%s\"\n", options[i].value.s);
  375. break;
  376. case OTYPE_HU:
  377. fprintf(f, "%hu\n", options[i].value.hu);
  378. break;
  379. case OTYPE_B:
  380. fprintf(f, "%s\n", options[i].value.b ? "true" : "false");
  381. break;
  382. default:
  383. break;
  384. }
  385. }
  386. config_dump();
  387. end:
  388. if (f)
  389. fclose(f);
  390. return err;
  391. }
  392. #endif
  393. enum error config_get(const char *key, void **out)
  394. {
  395. enum error err = ERR_OPT_NOTFOUND;
  396. for (int i = 0; i < options_count; i++) {
  397. struct conf_entry *cc = &options[i];
  398. if (strcmp(cc->l_name, key) == 0) {
  399. if (cc->value_is_set) {
  400. if (out)
  401. *out = &cc->value.s;
  402. err = NOERR;
  403. } else {
  404. err = ERR_OPT_UNSET;
  405. }
  406. break;
  407. }
  408. }
  409. return err;
  410. }
  411. const char *config_get_nonopt(int index)
  412. {
  413. if (index >= config_get_nonopt_count())
  414. return NULL;
  415. return opt_argv[optind + index];
  416. }
  417. int config_get_nonopt_count()
  418. {
  419. return opt_argc - optind;
  420. }
  421. void config_dump()
  422. {
  423. for (int i = 0; i < options_count; i++) {
  424. if (options[i].type == OTYPE_ACTION)
  425. continue;
  426. printf("%s: ", options[i].l_name);
  427. if (!options[i].value_is_set) {
  428. printf("[UNSET (>.<)]\n");
  429. continue;
  430. }
  431. switch (options[i].type) {
  432. case OTYPE_S:
  433. printf("%s\n", options[i].value.s);
  434. break;
  435. case OTYPE_HU:
  436. printf("%hu\n", options[i].value.hu);
  437. break;
  438. case OTYPE_B:
  439. printf("%s\n", options[i].value.b ? "True" : "False");
  440. break;
  441. default:
  442. printf("Error :(\n");
  443. break;
  444. }
  445. }
  446. }