load.c 15 KB

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