test.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /* test.c -- The test command.. */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2005,2007,2009 Free Software Foundation, Inc.
  5. *
  6. * GRUB is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * GRUB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <grub/dl.h>
  20. #include <grub/misc.h>
  21. #include <grub/mm.h>
  22. #include <grub/env.h>
  23. #include <grub/fs.h>
  24. #include <grub/device.h>
  25. #include <grub/file.h>
  26. #include <grub/command.h>
  27. #include <grub/i18n.h>
  28. /* A simple implementation for signed numbers. */
  29. static int
  30. grub_strtosl (char *arg, char **end, int base)
  31. {
  32. if (arg[0] == '-')
  33. return -grub_strtoul (arg + 1, end, base);
  34. return grub_strtoul (arg, end, base);
  35. }
  36. struct test_parse_closure
  37. {
  38. int ret;
  39. int discard;
  40. int invert;
  41. int file_exists;
  42. struct grub_dirhook_info file_info;
  43. };
  44. /* Take care of discarding and inverting. */
  45. static void
  46. update_val (int val, struct test_parse_closure *c)
  47. {
  48. if (! c->discard)
  49. c->ret = c->invert ? ! val : val;
  50. c->invert = c->discard = 0;
  51. }
  52. struct get_fileinfo_closure
  53. {
  54. char *filename;
  55. struct test_parse_closure *c;
  56. };
  57. /* A hook for iterating directories. */
  58. static int
  59. find_file (const char *cur_filename,
  60. const struct grub_dirhook_info *info,
  61. void *closure)
  62. {
  63. struct get_fileinfo_closure *c = closure;
  64. if ((info->case_insensitive ? grub_strcasecmp (cur_filename, c->filename)
  65. : grub_strcmp (cur_filename, c->filename)) == 0)
  66. {
  67. c->c->file_info = *info;
  68. c->c->file_exists = 1;
  69. return 1;
  70. }
  71. return 0;
  72. }
  73. /* Check if file exists and fetch its information. */
  74. static void
  75. get_fileinfo (char *path, struct test_parse_closure *c)
  76. {
  77. char *filename, *pathname;
  78. char *device_name;
  79. grub_fs_t fs;
  80. grub_device_t dev;
  81. c->file_exists = 0;
  82. device_name = grub_file_get_device_name (path);
  83. dev = grub_device_open (device_name);
  84. if (! dev)
  85. {
  86. grub_free (device_name);
  87. return;
  88. }
  89. fs = grub_fs_probe (dev);
  90. if (! fs)
  91. {
  92. grub_free (device_name);
  93. grub_device_close (dev);
  94. return;
  95. }
  96. pathname = grub_strchr (path, ')');
  97. if (! pathname)
  98. pathname = path;
  99. else
  100. pathname++;
  101. /* Remove trailing '/'. */
  102. while (*pathname && pathname[grub_strlen (pathname) - 1] == '/')
  103. pathname[grub_strlen (pathname) - 1] = 0;
  104. /* Split into path and filename. */
  105. filename = grub_strrchr (pathname, '/');
  106. if (! filename)
  107. {
  108. path = grub_strdup ("/");
  109. filename = pathname;
  110. }
  111. else
  112. {
  113. filename++;
  114. path = grub_strdup (pathname);
  115. path[filename - pathname] = 0;
  116. }
  117. /* It's the whole device. */
  118. if (! *pathname)
  119. {
  120. c->file_exists = 1;
  121. grub_memset (&c->file_info, 0, sizeof (c->file_info));
  122. /* Root is always a directory. */
  123. c->file_info.dir = 1;
  124. /* Fetch writing time. */
  125. c->file_info.mtimeset = 0;
  126. if (fs->mtime)
  127. {
  128. if (! fs->mtime (dev, &c->file_info.mtime))
  129. c->file_info.mtimeset = 1;
  130. grub_errno = GRUB_ERR_NONE;
  131. }
  132. }
  133. else
  134. {
  135. struct get_fileinfo_closure cc;
  136. cc.filename = filename;
  137. cc.c = c;
  138. (fs->dir) (dev, path, find_file, &cc);
  139. }
  140. grub_device_close (dev);
  141. grub_free (path);
  142. grub_free (device_name);
  143. }
  144. /* Parse a test expression starting from *argn. */
  145. static int
  146. test_parse (char **args, int *argn, int argc)
  147. {
  148. struct test_parse_closure c;
  149. c.ret = 0;
  150. c.discard = 0;
  151. c.invert = 0;
  152. /* Here we have the real parsing. */
  153. while (*argn < argc)
  154. {
  155. /* First try 3 argument tests. */
  156. if (*argn + 2 < argc)
  157. {
  158. /* String tests. */
  159. if (grub_strcmp (args[*argn + 1], "=") == 0
  160. || grub_strcmp (args[*argn + 1], "==") == 0)
  161. {
  162. update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0, &c);
  163. (*argn) += 3;
  164. continue;
  165. }
  166. if (grub_strcmp (args[*argn + 1], "!=") == 0)
  167. {
  168. update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0, &c);
  169. (*argn) += 3;
  170. continue;
  171. }
  172. /* GRUB extension: lexicographical sorting. */
  173. if (grub_strcmp (args[*argn + 1], "<") == 0)
  174. {
  175. update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0, &c);
  176. (*argn) += 3;
  177. continue;
  178. }
  179. if (grub_strcmp (args[*argn + 1], "<=") == 0)
  180. {
  181. update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0, &c);
  182. (*argn) += 3;
  183. continue;
  184. }
  185. if (grub_strcmp (args[*argn + 1], ">") == 0)
  186. {
  187. update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0, &c);
  188. (*argn) += 3;
  189. continue;
  190. }
  191. if (grub_strcmp (args[*argn + 1], ">=") == 0)
  192. {
  193. update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0, &c);
  194. (*argn) += 3;
  195. continue;
  196. }
  197. /* Number tests. */
  198. if (grub_strcmp (args[*argn + 1], "-eq") == 0)
  199. {
  200. update_val (grub_strtosl (args[*argn], 0, 0)
  201. == grub_strtosl (args[*argn + 2], 0, 0), &c);
  202. (*argn) += 3;
  203. continue;
  204. }
  205. if (grub_strcmp (args[*argn + 1], "-ge") == 0)
  206. {
  207. update_val (grub_strtosl (args[*argn], 0, 0)
  208. >= grub_strtosl (args[*argn + 2], 0, 0), &c);
  209. (*argn) += 3;
  210. continue;
  211. }
  212. if (grub_strcmp (args[*argn + 1], "-gt") == 0)
  213. {
  214. update_val (grub_strtosl (args[*argn], 0, 0)
  215. > grub_strtosl (args[*argn + 2], 0, 0), &c);
  216. (*argn) += 3;
  217. continue;
  218. }
  219. if (grub_strcmp (args[*argn + 1], "-le") == 0)
  220. {
  221. update_val (grub_strtosl (args[*argn], 0, 0)
  222. <= grub_strtosl (args[*argn + 2], 0, 0), &c);
  223. (*argn) += 3;
  224. continue;
  225. }
  226. if (grub_strcmp (args[*argn + 1], "-lt") == 0)
  227. {
  228. update_val (grub_strtosl (args[*argn], 0, 0)
  229. < grub_strtosl (args[*argn + 2], 0, 0), &c);
  230. (*argn) += 3;
  231. continue;
  232. }
  233. if (grub_strcmp (args[*argn + 1], "-ne") == 0)
  234. {
  235. update_val (grub_strtosl (args[*argn], 0, 0)
  236. != grub_strtosl (args[*argn + 2], 0, 0), &c);
  237. (*argn) += 3;
  238. continue;
  239. }
  240. /* GRUB extension: compare numbers skipping prefixes.
  241. Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
  242. if (grub_strcmp (args[*argn + 1], "-pgt") == 0
  243. || grub_strcmp (args[*argn + 1], "-plt") == 0)
  244. {
  245. int i;
  246. /* Skip common prefix. */
  247. for (i = 0; args[*argn][i] == args[*argn + 2][i]
  248. && args[*argn][i]; i++);
  249. /* Go the digits back. */
  250. i--;
  251. while (grub_isdigit (args[*argn][i]) && i > 0)
  252. i--;
  253. i++;
  254. if (grub_strcmp (args[*argn + 1], "-pgt") == 0)
  255. update_val (grub_strtoul (args[*argn] + i, 0, 0)
  256. > grub_strtoul (args[*argn + 2] + i, 0, 0), &c);
  257. else
  258. update_val (grub_strtoul (args[*argn] + i, 0, 0)
  259. < grub_strtoul (args[*argn + 2] + i, 0, 0), &c);
  260. (*argn) += 3;
  261. continue;
  262. }
  263. /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
  264. will be added to the first mtime. */
  265. if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0
  266. || grub_memcmp (args[*argn + 1], "-ot", 3) == 0)
  267. {
  268. struct grub_dirhook_info file1;
  269. int file1exists;
  270. int bias = 0;
  271. /* Fetch fileinfo. */
  272. get_fileinfo (args[*argn], &c);
  273. file1 = c.file_info;
  274. file1exists = c.file_exists;
  275. get_fileinfo (args[*argn + 2], &c);
  276. if (args[*argn + 1][3])
  277. bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
  278. if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0)
  279. update_val ((file1exists && ! c.file_exists)
  280. || (file1.mtimeset && c.file_info.mtimeset
  281. && file1.mtime + bias > c.file_info.mtime),
  282. &c);
  283. else
  284. update_val ((! file1exists && c.file_exists)
  285. || (file1.mtimeset && c.file_info.mtimeset
  286. && file1.mtime + bias < c.file_info.mtime),
  287. &c);
  288. (*argn) += 3;
  289. continue;
  290. }
  291. }
  292. /* Two-argument tests. */
  293. if (*argn + 1 < argc)
  294. {
  295. /* File tests. */
  296. if (grub_strcmp (args[*argn], "-d") == 0)
  297. {
  298. get_fileinfo (args[*argn + 1], &c);
  299. update_val (c.file_exists && c.file_info.dir, &c);
  300. (*argn) += 2;
  301. return c.ret;
  302. }
  303. if (grub_strcmp (args[*argn], "-e") == 0)
  304. {
  305. get_fileinfo (args[*argn + 1], &c);
  306. update_val (c.file_exists, &c);
  307. (*argn) += 2;
  308. return c.ret;
  309. }
  310. if (grub_strcmp (args[*argn], "-f") == 0)
  311. {
  312. get_fileinfo (args[*argn + 1], &c);
  313. /* FIXME: check for other types. */
  314. update_val (c.file_exists && ! c.file_info.dir, &c);
  315. (*argn) += 2;
  316. return c.ret;
  317. }
  318. if (grub_strcmp (args[*argn], "-s") == 0)
  319. {
  320. grub_file_t file;
  321. file = grub_file_open (args[*argn + 1]);
  322. update_val (file && (grub_file_size (file) != 0), &c);
  323. if (file)
  324. grub_file_close (file);
  325. grub_errno = GRUB_ERR_NONE;
  326. (*argn) += 2;
  327. return c.ret;
  328. }
  329. /* String tests. */
  330. if (grub_strcmp (args[*argn], "-n") == 0)
  331. {
  332. update_val (args[*argn + 1][0], &c);
  333. (*argn) += 2;
  334. continue;
  335. }
  336. if (grub_strcmp (args[*argn], "-z") == 0)
  337. {
  338. update_val (! args[*argn + 1][0], &c);
  339. (*argn) += 2;
  340. continue;
  341. }
  342. }
  343. /* Special modifiers. */
  344. /* End of expression. return to parent. */
  345. if (grub_strcmp (args[*argn], ")") == 0)
  346. {
  347. (*argn)++;
  348. return c.ret;
  349. }
  350. /* Recursively invoke if parenthesis. */
  351. if (grub_strcmp (args[*argn], "(") == 0)
  352. {
  353. (*argn)++;
  354. update_val (test_parse (args, argn, argc), &c);
  355. continue;
  356. }
  357. if (grub_strcmp (args[*argn], "!") == 0)
  358. {
  359. c.invert = ! c.invert;
  360. (*argn)++;
  361. continue;
  362. }
  363. if (grub_strcmp (args[*argn], "-a") == 0)
  364. {
  365. /* If current value is 0 second value is to be discarded. */
  366. c.discard = ! c.ret;
  367. (*argn)++;
  368. continue;
  369. }
  370. if (grub_strcmp (args[*argn], "-o") == 0)
  371. {
  372. /* If current value is 1 second value is to be discarded. */
  373. c.discard = c.ret;
  374. (*argn)++;
  375. continue;
  376. }
  377. /* No test found. Interpret if as just a string. */
  378. update_val (args[*argn][0], &c);
  379. (*argn)++;
  380. }
  381. return c.ret;
  382. }
  383. static grub_err_t
  384. grub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
  385. int argc, char **args)
  386. {
  387. int argn = 0;
  388. if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
  389. argc--;
  390. return test_parse (args, &argn, argc) ? GRUB_ERR_NONE
  391. : grub_error (GRUB_ERR_TEST_FAILURE, "false");
  392. }
  393. static grub_command_t cmd_1, cmd_2;
  394. GRUB_MOD_INIT(test)
  395. {
  396. cmd_1 = grub_register_command ("[", grub_cmd_test,
  397. N_("EXPRESSION ]"), N_("Evaluate an expression."));
  398. cmd_2 = grub_register_command ("test", grub_cmd_test,
  399. N_("EXPRESSION"), N_("Evaluate an expression."));
  400. }
  401. GRUB_MOD_FINI(test)
  402. {
  403. grub_unregister_command (cmd_1);
  404. grub_unregister_command (cmd_2);
  405. }