load.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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. #ifdef DEBUG
  21. #define KXML_DEBUG DEBUG
  22. #endif
  23. typedef bool_t (kxml_process_fn)(const kxml_node_t *element,
  24. void *parent, faux_error_t *error);
  25. static kxml_process_fn
  26. process_action,
  27. process_param,
  28. process_command,
  29. process_view,
  30. process_ptype,
  31. process_plugin,
  32. process_klish;
  33. // Different TAGs types
  34. typedef enum {
  35. KTAG_NONE,
  36. KTAG_ACTION,
  37. KTAG_PARAM,
  38. KTAG_COMMAND,
  39. KTAG_VIEW,
  40. KTAG_PTYPE,
  41. KTAG_PLUGIN,
  42. KTAG_KLISH,
  43. KTAG_MAX
  44. } ktags_e;
  45. static const char * const kxml_tags[] = {
  46. NULL,
  47. "ACTION",
  48. "PARAM",
  49. "COMMAND",
  50. "VIEW",
  51. "PTYPE",
  52. "PLUGIN",
  53. "KLISH"
  54. };
  55. static kxml_process_fn *kxml_handlers[] = {
  56. NULL,
  57. process_action,
  58. process_param,
  59. process_command,
  60. process_view,
  61. process_ptype,
  62. process_plugin,
  63. process_klish
  64. };
  65. static const char *kxml_tag_name(ktags_e tag)
  66. {
  67. if ((KTAG_NONE == tag) || (tag >= KTAG_MAX))
  68. return "NONE";
  69. return kxml_tags[tag];
  70. }
  71. static ktags_e kxml_node_tag(const kxml_node_t *node)
  72. {
  73. ktags_e tag = KTAG_NONE;
  74. char *name = NULL;
  75. if (!node)
  76. return KTAG_NONE;
  77. if (kxml_node_type(node) != KXML_NODE_ELM)
  78. return KTAG_NONE;
  79. name = kxml_node_name(node);
  80. if (!name)
  81. return KTAG_NONE; // Strange case
  82. for (tag = (KTAG_NONE + 1); tag < KTAG_MAX; tag++) {
  83. if (faux_str_casecmp(name, kxml_tags[tag]) == 0)
  84. break;
  85. }
  86. kxml_node_name_free(name);
  87. if (tag >= KTAG_MAX)
  88. return KTAG_NONE;
  89. return tag;
  90. }
  91. static kxml_process_fn *kxml_node_handler(const kxml_node_t *node)
  92. {
  93. return kxml_handlers[kxml_node_tag(node)];
  94. }
  95. /** @brief Reads an element from the XML stream and processes it.
  96. */
  97. static bool_t process_node(const kxml_node_t *node, void *parent, faux_error_t *error)
  98. {
  99. kxml_process_fn *handler = NULL;
  100. // Process only KXML_NODE_ELM. Don't process other types like:
  101. // KXML_NODE_DOC,
  102. // KXML_NODE_TEXT,
  103. // KXML_NODE_ATTR,
  104. // KXML_NODE_COMMENT,
  105. // KXML_NODE_PI,
  106. // KXML_NODE_DECL,
  107. // KXML_NODE_UNKNOWN,
  108. if (kxml_node_type(node) != KXML_NODE_ELM)
  109. return BOOL_TRUE;
  110. handler = kxml_node_handler(node);
  111. if (!handler) { // Unknown element
  112. faux_error_sprintf(error,
  113. TAG": Unknown tag \"%s\"", kxml_node_name(node));
  114. return BOOL_FALSE;
  115. }
  116. #ifdef KXML_DEBUG
  117. printf("kxml: Tag \"%s\"\n", kxml_node_name(node));
  118. #endif
  119. return handler(node, parent, error);
  120. }
  121. static bool_t kxml_load_file(kscheme_t *scheme, const char *filename,
  122. faux_error_t *error)
  123. {
  124. kxml_doc_t *doc = NULL;
  125. kxml_node_t *root = NULL;
  126. bool_t r = BOOL_FALSE;
  127. if (!scheme)
  128. return BOOL_FALSE;
  129. if (!filename)
  130. return BOOL_FALSE;
  131. #ifdef KXML_DEBUG
  132. printf("kxml: Processing XML file \"%s\"\n", filename);
  133. #endif
  134. doc = kxml_doc_read(filename);
  135. if (!kxml_doc_is_valid(doc)) {
  136. /* int errcaps = kxml_doc_error_caps(doc);
  137. printf("Unable to open file '%s'", filename);
  138. if ((errcaps & kxml_ERR_LINE) == kxml_ERR_LINE)
  139. printf(", at line %d", kxml_doc_err_line(doc));
  140. if ((errcaps & kxml_ERR_COL) == kxml_ERR_COL)
  141. printf(", at column %d", kxml_doc_err_col(doc));
  142. if ((errcaps & kxml_ERR_DESC) == kxml_ERR_DESC)
  143. printf(", message is %s", kxml_doc_err_msg(doc));
  144. printf("\n");
  145. */ kxml_doc_release(doc);
  146. return BOOL_FALSE;
  147. }
  148. root = kxml_doc_root(doc);
  149. r = process_node(root, scheme, error);
  150. kxml_doc_release(doc);
  151. if (!r) {
  152. faux_error_sprintf(error, TAG": Illegal file %s", filename);
  153. return BOOL_FALSE;
  154. }
  155. return BOOL_TRUE;
  156. }
  157. /** @brief Default path to get XML files from.
  158. */
  159. static const char *default_path = "/etc/klish;~/.klish";
  160. static const char *path_separators = ":;";
  161. bool_t kxml_load_scheme(kscheme_t *scheme, const char *xml_path,
  162. faux_error_t *error)
  163. {
  164. char *path = NULL;
  165. char *fn = NULL;
  166. char *saveptr = NULL;
  167. bool_t ret = BOOL_TRUE;
  168. assert(scheme);
  169. if (!scheme)
  170. return BOOL_FALSE;
  171. // Use the default path if xml path is not specified.
  172. // Dup is needed because sring will be tokenized but
  173. // the xml_path is must be const.
  174. if (!xml_path)
  175. path = faux_str_dup(default_path);
  176. else
  177. path = faux_str_dup(xml_path);
  178. #ifdef KXML_DEBUG
  179. printf("kxml: Loading scheme \"%s\"\n", path);
  180. #endif
  181. // Loop through each directory
  182. for (fn = strtok_r(path, path_separators, &saveptr);
  183. fn; fn = strtok_r(NULL, path_separators, &saveptr)) {
  184. DIR *dir = NULL;
  185. struct dirent *entry = NULL;
  186. char *realpath = NULL;
  187. // Expand tilde. Tilde must be the first symbol.
  188. realpath = faux_expand_tilde(fn);
  189. // Regular file
  190. if (faux_isfile(realpath)) {
  191. if (!kxml_load_file(scheme, realpath, error))
  192. ret = BOOL_FALSE;
  193. faux_str_free(realpath);
  194. continue;
  195. }
  196. // Search this directory for any XML files
  197. #ifdef KXML_DEBUG
  198. printf("kxml: Processing XML dir \"%s\"\n", realpath);
  199. #endif
  200. dir = opendir(realpath);
  201. if (!dir) {
  202. faux_str_free(realpath);
  203. continue;
  204. }
  205. for (entry = readdir(dir); entry; entry = readdir(dir)) {
  206. const char *extension = strrchr(entry->d_name, '.');
  207. char *filename = NULL;
  208. // Check the filename
  209. if (!extension || strcmp(".xml", extension))
  210. continue;
  211. filename = faux_str_sprintf("%s/%s", realpath, entry->d_name);
  212. if (!kxml_load_file(scheme, filename, error))
  213. ret = BOOL_FALSE;
  214. faux_str_free(filename);
  215. }
  216. closedir(dir);
  217. faux_str_free(realpath);
  218. }
  219. faux_str_free(path);
  220. return ret;
  221. }
  222. /** @brief Iterate through element's children.
  223. */
  224. static bool_t process_children(const kxml_node_t *element, void *parent,
  225. faux_error_t *error)
  226. {
  227. const kxml_node_t *node = NULL;
  228. while ((node = kxml_node_next_child(element, node)) != NULL) {
  229. bool_t res = BOOL_FALSE;
  230. res = process_node(node, parent, error);
  231. if (!res)
  232. return res;
  233. }
  234. return BOOL_TRUE;
  235. }
  236. static bool_t process_klish(const kxml_node_t *element, void *parent,
  237. faux_error_t *error)
  238. {
  239. return process_children(element, parent, error);
  240. }
  241. static bool_t process_view(const kxml_node_t *element, void *parent,
  242. faux_error_t *error)
  243. {
  244. iview_t iview = {};
  245. kview_t *view = NULL;
  246. bool_t res = BOOL_FALSE;
  247. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  248. kscheme_t *scheme = (kscheme_t *)parent;
  249. // Parent must be a KLISH tag
  250. if (parent_tag != KTAG_KLISH) {
  251. faux_error_sprintf(error,
  252. TAG": Tag \"%s\" can't contain VIEW tag",
  253. kxml_tag_name(parent_tag));
  254. return BOOL_FALSE;
  255. }
  256. if (!scheme) {
  257. faux_error_sprintf(error,
  258. TAG": Broken parent object for VIEW \"%s\"",
  259. iview.name);
  260. return BOOL_FALSE;
  261. }
  262. // Mandatory VIEW name
  263. iview.name = kxml_node_attr(element, "name");
  264. if (!iview.name) {
  265. faux_error_sprintf(error, TAG": VIEW without name");
  266. return BOOL_FALSE;
  267. }
  268. // Does VIEW already exist
  269. view = kscheme_find_view(scheme, iview.name);
  270. if (view) {
  271. if (!iview_parse(&iview, view, error))
  272. goto err;
  273. } else { // New VIEW object
  274. view = iview_load(&iview, error);
  275. if (!view)
  276. goto err;
  277. if (!kscheme_add_view(scheme, view)) {
  278. faux_error_sprintf(error, TAG": Can't add VIEW \"%s\". "
  279. "Probably duplication",
  280. kview_name(view));
  281. kview_free(view);
  282. goto err;
  283. }
  284. }
  285. if (!process_children(element, view, error))
  286. goto err;
  287. res = BOOL_TRUE;
  288. err:
  289. kxml_node_attr_free(iview.name);
  290. return res;
  291. }
  292. static bool_t process_ptype(const kxml_node_t *element, void *parent,
  293. faux_error_t *error)
  294. {
  295. iptype_t iptype = {};
  296. kptype_t *ptype = NULL;
  297. bool_t res = BOOL_FALSE;
  298. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  299. if (parent_tag != KTAG_KLISH) {
  300. faux_error_sprintf(error,
  301. TAG": Tag \"%s\" can't contain PTYPE tag",
  302. kxml_tag_name(parent_tag));
  303. return BOOL_FALSE;
  304. }
  305. iptype.name = kxml_node_attr(element, "name");
  306. iptype.help = kxml_node_attr(element, "help");
  307. ptype = iptype_load(&iptype, error);
  308. if (!ptype)
  309. goto err;
  310. if (!kscheme_add_ptype((kscheme_t *)parent, ptype)) {
  311. faux_error_sprintf(error, TAG": Can't add PTYPE \"%s\". "
  312. "Probably duplication",
  313. kptype_name(ptype));
  314. kptype_free(ptype);
  315. goto err;
  316. }
  317. if (!process_children(element, ptype, error))
  318. goto err;
  319. res = BOOL_TRUE;
  320. err:
  321. kxml_node_attr_free(iptype.name);
  322. kxml_node_attr_free(iptype.help);
  323. return res;
  324. }
  325. static bool_t process_plugin(const kxml_node_t *element, void *parent,
  326. faux_error_t *error)
  327. {
  328. iplugin_t iplugin = {};
  329. kplugin_t *plugin = NULL;
  330. bool_t res = BOOL_FALSE;
  331. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  332. if (parent_tag != KTAG_KLISH) {
  333. faux_error_sprintf(error,
  334. TAG": Tag \"%s\" can't contain PLUGIN tag",
  335. kxml_tag_name(parent_tag));
  336. return BOOL_FALSE;
  337. }
  338. iplugin.name = kxml_node_attr(element, "name");
  339. iplugin.id = kxml_node_attr(element, "id");
  340. iplugin.file = kxml_node_attr(element, "file");
  341. iplugin.conf = kxml_node_content(element);
  342. plugin = iplugin_load(&iplugin, error);
  343. if (!plugin)
  344. goto err;
  345. if (!kscheme_add_plugin((kscheme_t *)parent, plugin)) {
  346. faux_error_sprintf(error, TAG": Can't add PLUGIN \"%s\". "
  347. "Probably duplication",
  348. kplugin_name(plugin));
  349. kplugin_free(plugin);
  350. goto err;
  351. }
  352. if (!process_children(element, plugin, error))
  353. goto err;
  354. res = BOOL_TRUE;
  355. err:
  356. kxml_node_attr_free(iplugin.name);
  357. kxml_node_attr_free(iplugin.id);
  358. kxml_node_attr_free(iplugin.file);
  359. kxml_node_content_free(iplugin.conf);
  360. return res;
  361. }
  362. static bool_t process_param(const kxml_node_t *element, void *parent,
  363. faux_error_t *error)
  364. {
  365. iparam_t iparam = {};
  366. kparam_t *param = NULL;
  367. bool_t res = BOOL_FALSE;
  368. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  369. iparam.name = kxml_node_attr(element, "name");
  370. iparam.help = kxml_node_attr(element, "help");
  371. iparam.ptype = kxml_node_attr(element, "ptype");
  372. param = iparam_load(&iparam, error);
  373. if (!param)
  374. goto err;
  375. if (KTAG_COMMAND == parent_tag) {
  376. kcommand_t *command = (kcommand_t *)parent;
  377. if (!kcommand_add_param(command, param)) {
  378. faux_error_sprintf(error,
  379. TAG": Can't add PARAM \"%s\" to COMMAND \"%s\". "
  380. "Probably duplication",
  381. kparam_name(param), kcommand_name(command));
  382. kparam_free(param);
  383. goto err;
  384. }
  385. } else if (KTAG_PARAM == parent_tag) {
  386. kparam_t *parent_param = (kparam_t *)parent;
  387. if (!kparam_add_param(parent_param, param)) {
  388. faux_error_sprintf(error,
  389. TAG": Can't add PARAM \"%s\" to PARAM \"%s\". "
  390. "Probably duplication",
  391. kparam_name(param), kparam_name(parent_param));
  392. kparam_free(param);
  393. goto err;
  394. }
  395. } else {
  396. faux_error_sprintf(error,
  397. TAG": Tag \"%s\" can't contain PARAM tag",
  398. kxml_tag_name(parent_tag));
  399. kparam_free(param);
  400. goto err;
  401. }
  402. if (!process_children(element, param, error))
  403. goto err;
  404. res = BOOL_TRUE;
  405. err:
  406. kxml_node_attr_free(iparam.name);
  407. kxml_node_attr_free(iparam.help);
  408. kxml_node_attr_free(iparam.ptype);
  409. return res;
  410. }
  411. static bool_t process_command(const kxml_node_t *element, void *parent,
  412. faux_error_t *error)
  413. {
  414. icommand_t icommand = {};
  415. kcommand_t *command = NULL;
  416. bool_t res = BOOL_FALSE;
  417. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  418. if (parent_tag != KTAG_VIEW) {
  419. faux_error_sprintf(error,
  420. TAG": Tag \"%s\" can't contain COMMAND tag",
  421. kxml_tag_name(parent_tag));
  422. return BOOL_FALSE;
  423. }
  424. icommand.name = kxml_node_attr(element, "name");
  425. icommand.help = kxml_node_attr(element, "help");
  426. command = icommand_load(&icommand, error);
  427. if (!command)
  428. goto err;
  429. if (!kview_add_command((kview_t *)parent, command)) {
  430. faux_error_sprintf(error, TAG": Can't add COMMAND \"%s\". "
  431. "Probably duplication",
  432. kcommand_name(command));
  433. kcommand_free(command);
  434. goto err;
  435. }
  436. if (!process_children(element, command, error))
  437. goto err;
  438. res = BOOL_TRUE;
  439. err:
  440. kxml_node_attr_free(icommand.name);
  441. kxml_node_attr_free(icommand.help);
  442. return res;
  443. }
  444. static bool_t process_action(const kxml_node_t *element, void *parent,
  445. faux_error_t *error)
  446. {
  447. iaction_t iaction = {};
  448. kaction_t *action = NULL;
  449. bool_t res = BOOL_FALSE;
  450. ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element));
  451. iaction.sym = kxml_node_attr(element, "sym");
  452. iaction.lock = kxml_node_attr(element, "lock");
  453. iaction.interrupt = kxml_node_attr(element, "interrupt");
  454. iaction.interactive = kxml_node_attr(element, "interactive");
  455. iaction.exec_on = kxml_node_attr(element, "exec_on");
  456. iaction.update_retcode = kxml_node_attr(element, "update_retcode");
  457. iaction.script = kxml_node_content(element);
  458. action = iaction_load(&iaction, error);
  459. if (!action)
  460. goto err;
  461. if (KTAG_COMMAND == parent_tag) {
  462. kcommand_t *command = (kcommand_t *)parent;
  463. if (!kcommand_add_action(command, action)) {
  464. faux_error_sprintf(error,
  465. TAG": Can't add ACTION #%d to COMMAND \"%s\". "
  466. "Probably duplication",
  467. kcommand_actions_len(command) + 1,
  468. kcommand_name(command));
  469. kaction_free(action);
  470. goto err;
  471. }
  472. } else if (KTAG_PTYPE == parent_tag) {
  473. kptype_t *ptype = (kptype_t *)parent;
  474. if (!kptype_add_action(ptype, action)) {
  475. faux_error_sprintf(error,
  476. TAG": Can't add ACTION #%d to PTYPE \"%s\". "
  477. "Probably duplication",
  478. kptype_actions_len(ptype) + 1,
  479. kptype_name(ptype));
  480. kaction_free(action);
  481. goto err;
  482. }
  483. } else {
  484. faux_error_sprintf(error,
  485. TAG": Tag \"%s\" can't contain ACTION tag",
  486. kxml_tag_name(parent_tag));
  487. kaction_free(action);
  488. goto err;
  489. }
  490. if (!process_children(element, action, error))
  491. goto err;
  492. res = BOOL_TRUE;
  493. err:
  494. kxml_node_attr_free(iaction.sym);
  495. kxml_node_attr_free(iaction.lock);
  496. kxml_node_attr_free(iaction.interrupt);
  497. kxml_node_attr_free(iaction.interactive);
  498. kxml_node_attr_free(iaction.exec_on);
  499. kxml_node_attr_free(iaction.update_retcode);
  500. kxml_node_content_free(iaction.script);
  501. return res;
  502. }