load.c 15 KB

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