test.c 11 KB

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