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.

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