load.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /** @file load.c
  2. * @brief Common part for XML parsing.
  3. *
  4. * Different XML parsing engines can provide a functions in a form of
  5. * standardized API. This code uses this API and parses XML to kscheme.
  6. */
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <assert.h>
  10. #include <errno.h>
  11. #include <sys/types.h>
  12. #include <dirent.h>
  13. #include <faux/faux.h>
  14. #include <faux/str.h>
  15. #include <faux/error.h>
  16. #include <klish/kscheme.h>
  17. #include <klish/ischeme.h>
  18. #include <klish/kxml.h>
  19. #define TAG "XML"
  20. typedef bool_t (kxml_process_fn)(const kxml_node_t *element,
  21. void *parent, faux_error_t *error);
  22. static kxml_process_fn
  23. process_action,
  24. process_param,
  25. process_command,
  26. process_view,
  27. process_ptype,
  28. process_plugin,
  29. process_klish;
  30. /* process_startup,
  31. process_overview,
  32. process_detail,
  33. process_namespace,
  34. process_config,
  35. process_var,
  36. process_wdog,
  37. process_hotkey,
  38. process_hook;
  39. */
  40. // Different TAGs types
  41. typedef enum {
  42. KTAG_NONE,
  43. KTAG_ACTION,
  44. KTAG_PARAM,
  45. KTAG_COMMAND,
  46. KTAG_VIEW,
  47. KTAG_PTYPE,
  48. KTAG_PLUGIN,
  49. KTAG_KLISH,
  50. KTAG_MAX
  51. } ktags_e;
  52. static const char *kxml_tags[] = {
  53. NULL,
  54. "ACTION",
  55. "PARAM",
  56. "COMMAND",
  57. "VIEW",
  58. "PTYPE",
  59. "PLUGIN",
  60. "KLISH"
  61. };
  62. static kxml_process_fn *kxml_handlers[] = {
  63. NULL,
  64. process_action,
  65. process_param,
  66. process_command,
  67. process_view,
  68. process_ptype,
  69. process_plugin,
  70. process_klish
  71. };
  72. static ktags_e kxml_node_tag(const kxml_node_t *node)
  73. {
  74. ktags_e tag = KTAG_NONE;
  75. char *name = NULL;
  76. if (!node)
  77. return KTAG_NONE;
  78. if (kxml_node_type(node) != KXML_NODE_ELM)
  79. return KTAG_NONE;
  80. name = kxml_node_name(node);
  81. if (!name)
  82. return KTAG_NONE; // Strange case
  83. for (tag = KTAG_NONE; tag < KTAG_MAX; tag++) {
  84. if (faux_str_casecmp(name, kxml_tags[tag]))
  85. break;
  86. }
  87. kxml_node_name_free(name);
  88. if (tag >= KTAG_MAX)
  89. return KTAG_NONE;
  90. return tag;
  91. }
  92. static kxml_process_fn *kxml_node_handler(const kxml_node_t *node)
  93. {
  94. return kxml_handlers[kxml_node_tag(node)];
  95. }
  96. /** @brief Reads an element from the XML stream and processes it.
  97. */
  98. static bool_t process_node(const kxml_node_t *node, void *parent, faux_error_t *error)
  99. {
  100. kxml_process_fn *handler = kxml_node_handler(node);
  101. if (!handler)
  102. return BOOL_TRUE; // Unknown element
  103. return handler(node, parent, error);
  104. }
  105. static bool_t kxml_load_file(kscheme_t *scheme, const char *filename,
  106. faux_error_t *error)
  107. {
  108. kxml_doc_t *doc = NULL;
  109. kxml_node_t *root = NULL;
  110. bool_t r = BOOL_FALSE;
  111. if (!scheme)
  112. return BOOL_FALSE;
  113. if (!filename)
  114. return BOOL_FALSE;
  115. doc = kxml_doc_read(filename);
  116. if (!kxml_doc_is_valid(doc)) {
  117. /* int errcaps = kxml_doc_error_caps(doc);
  118. printf("Unable to open file '%s'", filename);
  119. if ((errcaps & kxml_ERR_LINE) == kxml_ERR_LINE)
  120. printf(", at line %d", kxml_doc_err_line(doc));
  121. if ((errcaps & kxml_ERR_COL) == kxml_ERR_COL)
  122. printf(", at column %d", kxml_doc_err_col(doc));
  123. if ((errcaps & kxml_ERR_DESC) == kxml_ERR_DESC)
  124. printf(", message is %s", kxml_doc_err_msg(doc));
  125. printf("\n");
  126. */ kxml_doc_release(doc);
  127. return BOOL_FALSE;
  128. }
  129. root = kxml_doc_root(doc);
  130. r = process_node(root, scheme, error);
  131. kxml_doc_release(doc);
  132. if (!r) {
  133. faux_error_sprintf(error, TAG": Illegal file %s", filename);
  134. return BOOL_FALSE;
  135. }
  136. return BOOL_TRUE;
  137. }
  138. /** @brief Default path to get XML files from.
  139. */
  140. const char *default_path = "/etc/klish;~/.klish";
  141. static const char *path_separators = ":;";
  142. kscheme_t *kxml_load_scheme(const char *xml_path, faux_error_t *error)
  143. {
  144. kscheme_t *scheme = NULL;
  145. const char *path = xml_path;
  146. char *realpath = NULL;
  147. char *fn = NULL;
  148. char *saveptr = NULL;
  149. bool_t ret = BOOL_TRUE;
  150. // New kscheme instance
  151. scheme = kscheme_new();
  152. if (!scheme)
  153. return NULL;
  154. // Use the default path if not specified
  155. if (!path)
  156. path = default_path;
  157. realpath = faux_expand_tilde(path);
  158. // Loop through each directory
  159. for (fn = strtok_r(realpath, path_separators, &saveptr);
  160. fn; fn = strtok_r(NULL, path_separators, &saveptr)) {
  161. DIR *dir = NULL;
  162. struct dirent *entry = NULL;
  163. // Regular file
  164. if (faux_isfile(fn)) {
  165. if (!kxml_load_file(scheme, fn, error)) {
  166. ret = BOOL_FALSE;
  167. continue;
  168. }
  169. }
  170. // Search this directory for any XML files
  171. dir = opendir(fn);
  172. if (!dir)
  173. continue;
  174. for (entry = readdir(dir); entry; entry = readdir(dir)) {
  175. const char *extension = strrchr(entry->d_name, '.');
  176. char *filename = NULL;
  177. // Check the filename
  178. if (!extension || strcmp(".xml", extension))
  179. continue;
  180. filename = faux_str_sprintf("%s/%s", fn, entry->d_name);
  181. if (!kxml_load_file(scheme, filename, error)) {
  182. ret = BOOL_FALSE;
  183. continue;
  184. }
  185. faux_str_free(filename);
  186. }
  187. closedir(dir);
  188. }
  189. faux_str_free(realpath);
  190. if (!ret) { // Some errors while XML parsing
  191. kscheme_free(scheme);
  192. return NULL;
  193. }
  194. return scheme;
  195. }
  196. /** @brief Iterate through element's children.
  197. */
  198. static bool_t process_children(const kxml_node_t *element, void *parent,
  199. faux_error_t *error)
  200. {
  201. const kxml_node_t *node = NULL;
  202. while ((node = kxml_node_next_child(element, node)) != NULL) {
  203. bool_t res = BOOL_FALSE;
  204. res = process_node(node, parent, error);
  205. if (!res)
  206. return res;
  207. }
  208. return BOOL_TRUE;
  209. }
  210. static bool_t process_klish(const kxml_node_t *element, void *parent,
  211. faux_error_t *error)
  212. {
  213. return process_children(element, parent, error);
  214. }
  215. static bool_t process_view(const kxml_node_t *element, void *parent,
  216. faux_error_t *error)
  217. {
  218. iview_t iview = {};
  219. kview_t *view = NULL;
  220. bool_t res = BOOL_FALSE;
  221. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  222. iview.name = kxml_node_attr(element, "name");
  223. view = iview_load(&iview, error);
  224. if (!view)
  225. goto err;
  226. if (parent_tag != KTAG_KLISH) {
  227. faux_error_add(error, TAG": Only KLISH tag can contain VIEW tag");
  228. return BOOL_FALSE;
  229. }
  230. if (!kscheme_add_view((kscheme_t *)parent, view)) {
  231. faux_error_sprintf(error, TAG": Can't add VIEW \"%s\"",
  232. kview_name(view));
  233. kview_free(view);
  234. goto err;
  235. }
  236. if (!process_children(element, view, error))
  237. goto err;
  238. res = BOOL_TRUE;
  239. err:
  240. kxml_node_attr_free(iview.name);
  241. return res;
  242. }
  243. static bool_t process_ptype(const kxml_node_t *element, void *parent,
  244. faux_error_t *error)
  245. {
  246. iptype_t iptype = {};
  247. kptype_t *ptype = NULL;
  248. bool_t res = BOOL_FALSE;
  249. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  250. iptype.name = kxml_node_attr(element, "name");
  251. iptype.help = kxml_node_attr(element, "help");
  252. ptype = iptype_load(&iptype, error);
  253. if (!ptype)
  254. goto err;
  255. if (parent_tag != KTAG_KLISH) {
  256. faux_error_add(error, TAG": Only KLISH tag can contain PTYPE tag");
  257. return BOOL_FALSE;
  258. }
  259. if (!kscheme_add_ptype((kscheme_t *)parent, ptype)) {
  260. faux_error_sprintf(error, TAG": Can't add PTYPE \"%s\"",
  261. kptype_name(ptype));
  262. kptype_free(ptype);
  263. goto err;
  264. }
  265. if (!process_children(element, ptype, error))
  266. goto err;
  267. res = BOOL_TRUE;
  268. err:
  269. kxml_node_attr_free(iptype.name);
  270. kxml_node_attr_free(iptype.help);
  271. return res;
  272. }
  273. static bool_t process_plugin(const kxml_node_t *element, void *parent,
  274. faux_error_t *error)
  275. {
  276. iplugin_t iplugin = {};
  277. kplugin_t *plugin = NULL;
  278. bool_t res = BOOL_FALSE;
  279. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  280. iplugin.name = kxml_node_attr(element, "name");
  281. iplugin.id = kxml_node_attr(element, "id");
  282. iplugin.file = kxml_node_attr(element, "file");
  283. iplugin.global = kxml_node_attr(element, "global");
  284. iplugin.conf = kxml_node_content(element);
  285. plugin = iplugin_load(&iplugin, error);
  286. if (!plugin)
  287. goto err;
  288. if (parent_tag != KTAG_KLISH) {
  289. faux_error_add(error, TAG": Only KLISH tag can contain PLUGIN tag");
  290. return BOOL_FALSE;
  291. }
  292. if (!kscheme_add_plugin((kscheme_t *)parent, plugin)) {
  293. faux_error_sprintf(error, TAG": Can't add PLUGIN \"%s\"",
  294. kplugin_name(plugin));
  295. kplugin_free(plugin);
  296. goto err;
  297. }
  298. if (!process_children(element, plugin, error))
  299. goto err;
  300. res = BOOL_TRUE;
  301. err:
  302. kxml_node_attr_free(iplugin.name);
  303. kxml_node_attr_free(iplugin.id);
  304. kxml_node_attr_free(iplugin.file);
  305. kxml_node_attr_free(iplugin.global);
  306. kxml_node_content_free(iplugin.conf);
  307. return res;
  308. }
  309. static bool_t process_param(const kxml_node_t *element, void *parent,
  310. faux_error_t *error)
  311. {
  312. iparam_t iparam = {};
  313. kparam_t *param = NULL;
  314. bool_t res = BOOL_FALSE;
  315. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  316. iparam.name = kxml_node_attr(element, "name");
  317. iparam.help = kxml_node_attr(element, "help");
  318. iparam.ptype = kxml_node_attr(element, "ptype");
  319. param = iparam_load(&iparam, error);
  320. if (!param)
  321. goto err;
  322. if (parent_tag != KTAG_COMMAND) {
  323. faux_error_add(error, TAG": Only COMMAND tag can contain PARAM tag");
  324. return BOOL_FALSE;
  325. }
  326. if (!kcommand_add_param((kcommand_t *)parent, param)) {
  327. faux_error_sprintf(error, TAG": Can't add PARAM \"%s\"",
  328. kparam_name(param));
  329. kparam_free(param);
  330. goto err;
  331. }
  332. if (!process_children(element, param, error))
  333. goto err;
  334. res = BOOL_TRUE;
  335. err:
  336. kxml_node_attr_free(iparam.name);
  337. kxml_node_attr_free(iparam.help);
  338. kxml_node_attr_free(iparam.ptype);
  339. return res;
  340. }
  341. static bool_t process_command(const kxml_node_t *element, void *parent,
  342. faux_error_t *error)
  343. {
  344. icommand_t icommand = {};
  345. kcommand_t *command = NULL;
  346. bool_t res = BOOL_FALSE;
  347. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  348. icommand.name = kxml_node_attr(element, "name");
  349. icommand.help = kxml_node_attr(element, "help");
  350. command = icommand_load(&icommand, error);
  351. if (!command)
  352. goto err;
  353. if (parent_tag != KTAG_VIEW) {
  354. faux_error_add(error, TAG": Only VIEW tag can contain COMMAND tag");
  355. return BOOL_FALSE;
  356. }
  357. if (!kview_add_command((kview_t *)parent, command)) {
  358. faux_error_sprintf(error, TAG": Can't add COMMAND \"%s\"",
  359. kcommand_name(command));
  360. kcommand_free(command);
  361. goto err;
  362. }
  363. if (!process_children(element, command, error))
  364. goto err;
  365. res = BOOL_TRUE;
  366. err:
  367. kxml_node_attr_free(icommand.name);
  368. kxml_node_attr_free(icommand.help);
  369. return res;
  370. }
  371. static bool_t process_action(const kxml_node_t *element, void *parent,
  372. faux_error_t *error)
  373. {
  374. iaction_t iaction = {};
  375. kaction_t *action = NULL;
  376. bool_t res = BOOL_FALSE;
  377. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  378. iaction.sym = kxml_node_attr(element, "sym");
  379. iaction.lock = kxml_node_attr(element, "lock");
  380. iaction.interrupt = kxml_node_attr(element, "interrupt");
  381. iaction.interactive = kxml_node_attr(element, "interactive");
  382. iaction.exec_on = kxml_node_attr(element, "exec_on");
  383. iaction.update_retcode = kxml_node_attr(element, "update_retcode");
  384. iaction.script = kxml_node_content(element);
  385. action = iaction_load(&iaction, error);
  386. if (!action)
  387. goto err;
  388. if (KTAG_COMMAND == parent_tag) {
  389. kcommand_t *command = (kcommand_t *)parent;
  390. if (!kcommand_add_action(command, action)) {
  391. faux_error_sprintf(error,
  392. TAG": Can't add ACTION #%d to COMMAND \"%s\"",
  393. kcommand_actions_len(command) + 1,
  394. kcommand_name(command));
  395. kaction_free(action);
  396. goto err;
  397. }
  398. } else if (KTAG_PTYPE == parent_tag) {
  399. kptype_t *ptype = (kptype_t *)parent;
  400. if (!kptype_add_action(ptype, action)) {
  401. faux_error_sprintf(error,
  402. TAG": Can't add ACTION #%d to PTYPE \"%s\"",
  403. kptype_actions_len(ptype) + 1,
  404. kptype_name(ptype));
  405. kaction_free(action);
  406. goto err;
  407. }
  408. } else {
  409. faux_error_add(error,
  410. TAG": Only COMMAND, PTYPE tags can contain ACTION tag");
  411. return BOOL_FALSE;
  412. }
  413. if (!process_children(element, action, error))
  414. goto err;
  415. res = BOOL_TRUE;
  416. err:
  417. kxml_node_attr_free(iaction.sym);
  418. kxml_node_attr_free(iaction.lock);
  419. kxml_node_attr_free(iaction.interrupt);
  420. kxml_node_attr_free(iaction.interactive);
  421. kxml_node_attr_free(iaction.exec_on);
  422. kxml_node_attr_free(iaction.update_retcode);
  423. kxml_node_content_free(iaction.script);
  424. return res;
  425. }