8bff7328108479f1b95dbd2d3196cdeada238808
[petitboot] / discover / grub2 / builtins.c
1
2 #include <stdio.h>
3 #include <string.h>
4
5 #include <log/log.h>
6 #include <types/types.h>
7 #include <talloc/talloc.h>
8 #include <util/util.h>
9
10 #include "discover/parser.h"
11 #include "grub2.h"
12
13
14 static int builtin_set(struct grub2_script *script,
15                 void *data __attribute__((unused)),
16                 int argc, char *argv[])
17 {
18         char *name, *value, *p;
19         int i;
20
21         if (argc < 2)
22                 return -1;
23
24         p = strchr(argv[1], '=');
25         if (!p)
26                 return -1;
27
28         name = talloc_strndup(script, argv[1], p - argv[1]);
29         value = talloc_strdup(script, p+1);
30
31         for (i = 2; i < argc; i++)
32                 value = talloc_asprintf_append(value, " %s", argv[i]);
33
34         script_env_set(script, name, value);
35
36         return 0;
37 }
38
39 static int builtin_linux(struct grub2_script *script,
40                 void *data __attribute__((unused)),
41                 int argc, char *argv[])
42 {
43         struct discover_boot_option *opt = script->opt;
44         const char *root;
45         int i;
46
47         if (!opt) {
48                 pb_log("grub2 syntax error: 'linux' statement outside "
49                                 "a menuentry.\n");
50                 return -1;
51         }
52
53         if (argc < 2) {
54                 pb_log("grub2 syntax error: no filename provided to "
55                                 "linux statement\n");
56                 return -1;
57         }
58
59         root = script_env_get(script, "root");
60
61         opt->boot_image = create_grub2_resource(opt, script->ctx->device,
62                                                 root, argv[1]);
63         opt->option->boot_args = NULL;
64
65         if (argc > 2)
66                 opt->option->boot_args = talloc_strdup(opt, argv[2]);
67
68         for (i = 3; i < argc; i++)
69                 opt->option->boot_args = talloc_asprintf_append(
70                                                 opt->option->boot_args,
71                                                 " %s", argv[i]);
72         return 0;
73 }
74
75 static int builtin_initrd(struct grub2_script *script,
76                 void *data __attribute__((unused)),
77                 int argc, char *argv[])
78 {
79         struct discover_boot_option *opt = script->opt;
80         const char *root;
81
82         if (!opt) {
83                 pb_log("grub2 syntax error: 'initrd' statement outside "
84                                 "a menuentry.\n");
85                 return -1;
86         }
87
88         if (argc < 2) {
89                 pb_log("grub2 syntax error: no filename provided to "
90                                 "initrd statement\n");
91                 return -1;
92         }
93
94         root = script_env_get(script, "root");
95         opt->initrd = create_grub2_resource(opt, script->ctx->device,
96                                                 root, argv[1]);
97
98         return 0;
99 }
100
101 static int builtin_search(struct grub2_script *script,
102                 void *data __attribute__((unused)),
103                 int argc, char *argv[])
104 {
105         const char *env_var, *spec;
106         int i;
107
108         env_var = NULL;
109
110         for (i = 1; i < argc - 1; i++) {
111                 if (!strncmp(argv[i], "--set=", strlen("--set="))) {
112                         env_var = argv[i] + strlen("--set=");
113                         break;
114                 }
115         }
116
117         if (!env_var)
118                 return 0;
119
120         spec = argv[argc - 1];
121
122         script_env_set(script, env_var, spec);
123
124         return 0;
125 }
126
127 /* Note that GRUB does not follow symlinks in evaluating all file
128  * tests but -s, unlike below. However, it seems like a bad idea to
129  * emulate GRUB's behavior (e.g., it would take extra work), so we
130  * implement the behavior that coreutils' test binary has. */
131 static bool builtin_test_op_file(struct grub2_script *script, char op,
132                 const char *file)
133 {
134         bool result;
135         int rc;
136         struct stat statbuf;
137
138         rc = parser_stat_path(script->ctx, script->ctx->device,
139                         file, &statbuf);
140         if (rc)
141                 return false;
142
143         switch (op) {
144         case 's':
145                 /* -s: return true if file exists and has non-zero size */
146                 result = statbuf.st_size > 0;
147                 break;
148         case 'f':
149                 /* -f: return true if file exists and is not a directory. This is
150                  * different than the behavior of "test", but is what GRUB does
151                  * (though note as above that we follow symlinks unlike GRUB). */
152                 result = !S_ISDIR(statbuf.st_mode);
153                 break;
154         default:
155                 result = false;
156
157         }
158
159         return result;
160 }
161
162 /* See comment at builtin_test_op_file for differences between how
163  * GRUB implements file tests versus Petitboot's GRUB parser. */
164 static bool builtin_test_op_dir(struct grub2_script *script, char op,
165                 const char *dir)
166 {
167         int rc;
168         struct stat statbuf;
169
170         if (op != 'd')
171                 return false;
172
173         rc = parser_stat_path(script->ctx, script->ctx->device, dir, &statbuf);
174         if (rc) {
175                 return false;
176         }
177
178         return S_ISDIR(statbuf.st_mode);
179 }
180
181 static bool builtin_test_op(struct grub2_script *script,
182                 int argc, char **argv, int *consumed)
183 {
184         char *op;
185
186         if (argc >= 3) {
187                 const char *a1, *a2;
188
189                 a1 = argv[0];
190                 op = argv[1];
191                 a2 = argv[2];
192
193                 if (!strcmp(op, "=") || !strcmp(op, "==")) {
194                         *consumed = 3;
195                         return !strcmp(a1, a2);
196                 }
197
198                 if (!strcmp(op, "!=")) {
199                         *consumed = 3;
200                         return strcmp(a1, a2);
201                 }
202
203                 if (!strcmp(op, "<")) {
204                         *consumed = 3;
205                         return strcmp(a1, a2) < 0;
206                 }
207
208                 if (!strcmp(op, ">")) {
209                         *consumed = 3;
210                         return strcmp(a1, a2) > 0;
211                 }
212         }
213
214         if (argc >= 2) {
215                 const char *a1;
216
217                 op = argv[0];
218                 a1 = argv[1];
219
220                 if (!strcmp(op, "-z")) {
221                         *consumed = 2;
222                         return strlen(a1) == 0;
223                 }
224
225                 if (!strcmp(op, "-n")) {
226                         *consumed = 2;
227                         return strlen(a1) != 0;
228                 }
229
230                 if (!strcmp(op, "-s") || !strcmp(op, "-f")) {
231                         *consumed = 2;
232                         return builtin_test_op_file(script, op[1], a1);
233                 }
234
235                 if (!strcmp(op, "-d")) {
236                         *consumed = 2;
237                         return builtin_test_op_dir(script, op[1], a1);
238                 }
239         }
240
241         op = argv[0];
242         *consumed = 1;
243         return strlen(op) > 0;
244 }
245
246 static int builtin_test(struct grub2_script *script,
247                 void *data __attribute__((unused)),
248                 int argc, char *argv[])
249 {
250         int consumed;
251         bool not, rc;
252
253         if (!strcmp(argv[0], "[") && !strcmp(argv[argc - 1], "]"))
254                 argc--;
255
256         /* skip command name */
257         argc--;
258         argv++;
259
260         not = false;
261         rc = false;
262
263         for (consumed = 0; argc > 0; argv += consumed, argc -= consumed) {
264
265                 if (!strcmp(argv[0], "!")) {
266                         not = true;
267                         consumed = 1;
268                         continue;
269                 }
270
271                 if (!strcmp(argv[0], "-a")) {
272                         if (!rc)
273                                 return 1;
274                         consumed = 1;
275                         continue;
276                 }
277
278                 if (!strcmp(argv[0], "-o")) {
279                         if (rc)
280                                 return 0;
281                         consumed = 1;
282                         continue;
283                 }
284
285                 rc = builtin_test_op(script, argc, argv, &consumed);
286                 if (not) {
287                         rc = !rc;
288                         not = false;
289                 }
290         }
291
292         return rc ? 0 : 1;
293 }
294
295 static int builtin_true(struct grub2_script *script __attribute__((unused)),
296                 void *data __attribute__((unused)),
297                 int argc __attribute__((unused)),
298                 char *argv[] __attribute__((unused)))
299 {
300         return 0;
301 }
302
303 static int builtin_false(struct grub2_script *script __attribute__((unused)),
304                 void *data __attribute__((unused)),
305                 int argc __attribute__((unused)),
306                 char *argv[] __attribute__((unused)))
307 {
308         return 1;
309 }
310
311 static int builtin_nop(struct grub2_script *script __attribute__((unused)),
312                 void *data __attribute__((unused)),
313                 int argc __attribute__((unused)),
314                 char *argv[] __attribute__((unused)))
315 {
316         return 0;
317 }
318
319 extern int builtin_load_env(struct grub2_script *script,
320                 void *data __attribute__((unused)),
321                 int argc, char *argv[]);
322 int builtin_save_env(struct grub2_script *script,
323                 void *data __attribute__((unused)),
324                 int argc, char *argv[]);
325
326
327 static struct {
328         const char *name;
329         grub2_function fn;
330 } builtins[] = {
331         {
332                 .name = "set",
333                 .fn = builtin_set,
334         },
335         {
336                 .name = "linux",
337                 .fn = builtin_linux,
338         },
339         {
340                 .name = "linux16",
341                 .fn = builtin_linux,
342         },
343         {
344                 .name = "initrd",
345                 .fn = builtin_initrd,
346         },
347         {
348                 .name = "search",
349                 .fn = builtin_search,
350         },
351         {
352                 .name = "[",
353                 .fn = builtin_test,
354         },
355         {
356                 .name = "test",
357                 .fn = builtin_test,
358         },
359         {
360                 .name = "true",
361                 .fn = builtin_true,
362         },
363         {
364                 .name = "false",
365                 .fn = builtin_false,
366         },
367         {
368                 .name = "load_env",
369                 .fn = builtin_load_env,
370         },
371         {
372                 .name = "save_env",
373                 .fn = builtin_save_env,
374         },
375 };
376
377 static const char *nops[] = {
378         "echo", "export", "insmod", "loadfont", "terminfo",
379 };
380
381 void register_builtins(struct grub2_script *script)
382 {
383         unsigned int i;
384
385         for (i = 0; i < ARRAY_SIZE(builtins); i++)
386                 script_register_function(script, builtins[i].name,
387                                 builtins[i].fn, NULL);
388
389         for (i = 0; i < ARRAY_SIZE(nops); i++)
390                 script_register_function(script, nops[i], builtin_nop, NULL);
391 }