OpenCore  1.0.4
OpenCore Bootloader
Loading...
Searching...
No Matches
GrubCfg.c
Go to the documentation of this file.
1
11#include "LinuxBootInternal.h"
12
13#include <Uefi.h>
14#include <Library/BaseLib.h>
15#include <Library/BaseMemoryLib.h>
17#include <Library/OcMiscLib.h>
18#include <Library/OcStringLib.h>
19
20/*
21 grub.cfg processing states.
22*/
30
31/*
32 grub.cfg $var processing states.
33*/
40
41#define GRUB_LINE "in grub.cfg at line"
42
43/*
44 grub.cfg $var processing flags.
45*/
46#define VAR_FLAGS_NONE (0)
47#define VAR_FLAGS_BRACE BIT0
48#define VAR_FLAGS_NUMERIC BIT1
49
50#define SHIFT_TOKEN(offset) do {\
51 CopyMem (*Token + (offset), *Token, &Content[*Pos] - *Token); \
52 *Token += (offset); \
53} while (0)
54
55STATIC
56EFI_STATUS
58 CHAR8 *Content,
59 UINTN *Pos,
60 UINTN *Line,
61 CHAR8 **Token,
62 BOOLEAN *IsIndented,
63 BOOLEAN *ContainsVars
64 )
65{
66 GRUB_PARSE_STATE GrubState;
67 GRUB_VAR_STATE VarState;
68 UINTN GrubVarFlags;
69
70 BOOLEAN Escaped;
71 BOOLEAN TokenCompleted;
72 BOOLEAN Retake;
73
74 CHAR8 Ch;
75 CHAR8 Ch2;
76
77 UINTN Add;
78
79 *Token = NULL;
80 *IsIndented = FALSE;
81 *ContainsVars = FALSE;
82
83 GrubState = GRUB_LEADING_SPACE;
84 VarState = GRUB_VAR_NONE;
85
86 Escaped = FALSE;
87 TokenCompleted = FALSE;
88 Retake = FALSE;
89
90 do {
91 Ch = Content[*Pos];
92
93 if (Ch == '\n') {
94 *Line += 1;
95 } else if (!((Ch == '\0') || (Ch == '\t') || ((Ch >= 32) && (Ch <= 127)))) {
96 DEBUG ((DEBUG_WARN, "LNX: Invalid char 0x%x %a %u\n", Ch, GRUB_LINE, Line));
97 return EFI_INVALID_PARAMETER;
98 }
99
100 //
101 // Deal with escape char and line continuation.
102 //
103 if (Ch == '\\') {
104 Ch2 = Content[(*Pos) + 1];
105
106 if ((Ch2 == '\0') || (Ch2 == '\n')) {
107 //
108 // Line continuation.
109 //
110 if (VarState != GRUB_VAR_NONE) {
111 //
112 // We could handle this fine (just remove this check), but GRUB doesn't:
113 // https://www.gnu.org/software/grub/manual/grub/html_node/grub_fot.html#FOOT7
114 //
115 DEBUG ((DEBUG_WARN, "LNX: Illegal line continuation within variable name %a %u\n", GRUB_LINE, Line));
116 return EFI_INVALID_PARAMETER;
117 }
118
119 //
120 // '\n' go to char afterwards, '\0' go to '\0'.
121 //
122 Add = (Ch2 == '\n' ? 2 : 1);
123 SHIFT_TOKEN (Add);
124 *Pos += Add;
125 Ch = Content[*Pos];
126 *Line += 1;
127 } else {
128 //
129 // Escapes.
130 //
131 switch (GrubState) {
132 //
133 // No escapes in single quote.
134 //
136 break;
137
138 //
139 // Only these escapes in double quote.
140 //
142 if ((Ch2 == '$') || (Ch2 == '"')) {
143 Escaped = TRUE;
144 }
145
146 break;
147
148 //
149 // Anything can be escaped.
150 //
151 default:
152 Escaped = TRUE;
153 break;
154 }
155
156 if (Escaped) {
157 SHIFT_TOKEN (1);
158 ++(*Pos);
159 Ch = Ch2;
160 Escaped = FALSE;
161 }
162 }
163 }
164
165 //
166 // Grub var is a special state which can be entered within other states.
167 // Allowed: $?, $@, $#, $nnn, $alphanumeric
168 //
169 if (VarState != GRUB_VAR_NONE) {
170 ASSERT (GrubState == GRUB_TOKEN || GrubState == GRUB_SINGLE_QUOTE || GrubState == GRUB_DOUBLE_QUOTE);
171
172 switch (VarState) {
173 case GRUB_VAR_START:
174 //
175 // The fact that a token contains a var reference means we cannot use it;
176 // we are looking for tokens which define vars, not ones which use them.
177 //
178 *ContainsVars = TRUE;
179
180 if (Ch == '{') {
181 if ((GrubVarFlags & VAR_FLAGS_BRACE) != 0) {
182 DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line));
183 return EFI_INVALID_PARAMETER;
184 }
185
186 GrubVarFlags |= VAR_FLAGS_BRACE;
187 } else if (Ch == '}') {
188 //
189 // Empty var name is valid.
190 //
191 if ((GrubVarFlags & VAR_FLAGS_BRACE) == 0) {
192 DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line));
193 return EFI_INVALID_PARAMETER;
194 }
195
196 VarState = GRUB_VAR_NONE;
197 } else if ((Ch == '@') || (Ch == '?') || (Ch == '#')) {
198 VarState = GRUB_VAR_END;
199 } else if (IS_DIGIT (Ch)) {
200 GrubVarFlags |= VAR_FLAGS_NUMERIC;
201 VarState = GRUB_VAR_CHAR;
202 } else if ((Ch == '_') || IS_ALPHA (Ch)) {
203 VarState = GRUB_VAR_CHAR;
204 } else {
205 DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line));
206 return EFI_INVALID_PARAMETER;
207 }
208
209 break;
210
211 case GRUB_VAR_END:
212 if ((GrubVarFlags & VAR_FLAGS_BRACE) != 0) {
213 if (Ch != '}') {
214 DEBUG ((DEBUG_WARN, "LNX: Illegal character in variable name %a %u\n", GRUB_LINE, Line));
215 return EFI_INVALID_PARAMETER;
216 }
217 } else {
218 Retake = TRUE;
219 }
220
221 VarState = GRUB_VAR_NONE;
222 break;
223
224 case GRUB_VAR_CHAR:
225 if (!(IS_DIGIT (Ch) ||
226 ( ((GrubVarFlags & VAR_FLAGS_NUMERIC) == 0)
227 && ((Ch == '_') || IS_ALPHA (Ch)))))
228 {
229 VarState = GRUB_VAR_END;
230 Retake = TRUE;
231 }
232
233 break;
234
235 default:
236 ASSERT (FALSE);
237 break;
238 }
239 } else {
240 switch (GrubState) {
242 if ((Ch == '\0') || (Ch == '\n') || (!Escaped && (Ch == ';'))) {
243 TokenCompleted = TRUE;
244 Retake = TRUE;
245 } else if ((Ch == ' ') || (Ch == '\t')) {
246 *IsIndented = TRUE;
247 } else if (Ch == '#') {
248 GrubState = GRUB_COMMENT;
249 } else {
250 *Token = &Content[*Pos];
251 GrubState = GRUB_TOKEN;
252 Retake = TRUE;
253 }
254
255 break;
256
257 case GRUB_COMMENT:
258 if ((Ch == '\n') || (Ch == '\0')) {
259 TokenCompleted = TRUE;
260 Retake = TRUE;
261 }
262
263 break;
264
265 case GRUB_TOKEN:
266 if ((Ch == '\n') || (Ch == '\0') || (!Escaped && ((Ch == ';') || (Ch == ' ') || (Ch == '\t')))) {
267 TokenCompleted = TRUE;
268 Retake = TRUE;
269 } else if (!Escaped && (Ch == '\'')) {
270 SHIFT_TOKEN (1);
271 GrubState = GRUB_SINGLE_QUOTE;
272 } else if (!Escaped && (Ch == '"')) {
273 SHIFT_TOKEN (1);
274 GrubState = GRUB_DOUBLE_QUOTE;
275 } else if (!Escaped && (Ch == '$')) {
276 VarState = GRUB_VAR_START;
277 GrubVarFlags = VAR_FLAGS_NONE;
278 }
279
280 break;
281
283 if (Ch == '\'') {
284 SHIFT_TOKEN (1);
285 GrubState = GRUB_TOKEN;
286 } else if (Ch == '$') {
287 VarState = GRUB_VAR_START;
288 GrubVarFlags = VAR_FLAGS_NONE;
289 }
290
291 break;
292
294 if (!Escaped && (Ch == '"')) {
295 SHIFT_TOKEN (1);
296 GrubState = GRUB_TOKEN;
297 } else if (!Escaped && (Ch == '$')) {
298 VarState = GRUB_VAR_START;
299 GrubVarFlags = VAR_FLAGS_NONE;
300 }
301
302 break;
303 }
304 }
305
306 if (Retake) {
307 Retake = FALSE;
308 } else if (Ch != '\0') {
309 ++(*Pos);
310 }
311 } while (Ch != '\0' && !TokenCompleted);
312
313 if (!TokenCompleted && (GrubState != GRUB_LEADING_SPACE)) {
314 DEBUG ((DEBUG_WARN, "LNX: Syntax error (state=%u) %a %u\n", GrubState, GRUB_LINE, Line));
315 return EFI_INVALID_PARAMETER;
316 }
317
318 return EFI_SUCCESS;
319}
320
321STATIC
322BOOLEAN
324 CHAR8 Ch,
325 UINTN *Pos
326 )
327{
328 if (Ch == '\0') {
329 return TRUE;
330 }
331
332 if ((Ch == ';') || (Ch == '\n')) {
333 (*Pos)++;
334 return TRUE;
335 }
336
337 ASSERT (Ch == ' ' || Ch == '\t');
338
339 (*Pos)++;
340 return FALSE;
341}
342
343STATIC
344EFI_STATUS
346 UINTN Line,
347 CHAR8 *Token,
348 BOOLEAN IsIndented,
349 BOOLEAN ContainsVars
350 )
351{
352 EFI_STATUS Status;
353 CHAR8 *Equals;
354 CHAR8 *Dollar;
355 UINTN VarStatus;
356
357 //
358 // Note: It is correct grub2 parsing to treat these tokens with = in (whether after set or not) as one token.
359 //
360 Equals = OcAsciiStrChr (Token, '=');
361 if (Equals == NULL) {
362 DEBUG ((DEBUG_WARN, "LNX: Invalid set command %a %u\n", GRUB_LINE, Line));
363 return EFI_INVALID_PARAMETER;
364 }
365
366 *Equals = '\0';
367
368 Dollar = OcAsciiStrChr (Token, '$');
369 if (Dollar != NULL) {
370 //
371 // Non-typical but valid GRUB syntax to use variable replacements within
372 // variable name; we don't know what the name is (and are probably pretty
373 // unlikely to find the required values in valid, non-indented variables
374 // which we do know), so we ignore it.
375 //
376 DEBUG ((DEBUG_WARN, "LNX: Ignoring tokenised %a %a %a %u\n", "variable name", Token, GRUB_LINE, Line));
377 return EFI_SUCCESS;
378 }
379
380 VarStatus = 0;
381 if (IsIndented) {
382 VarStatus |= VAR_ERR_INDENTED;
383 }
384
385 if (ContainsVars) {
386 VarStatus |= VAR_ERR_HAS_VARS;
387 }
388
389 Status = InternalSetGrubVar (Token, Equals + 1, VarStatus);
390 if (EFI_ERROR (Status)) {
391 return Status;
392 }
393
394 return EFI_SUCCESS;
395}
396
397EFI_STATUS
399 IN OUT CHAR8 *Content
400 )
401{
402 EFI_STATUS Status;
403 UINTN Pos;
404 UINTN Line;
405 UINTN NextLine;
406 UINTN TokenIndex;
407 CHAR8 *Dollar;
408 CHAR8 *Equals;
409 CHAR8 *Token;
410 CHAR8 LastChar;
411 BOOLEAN IsIndented;
412 BOOLEAN ContainsVars;
413 BOOLEAN SetCommand;
414 BOOLEAN SetIsIndented;
415
416 Pos = 0;
417 Line = 1;
418
419 do {
420 TokenIndex = 0;
421 SetCommand = FALSE;
422
423 do {
424 //
425 // Save new line number until we've finished any messages.
426 //
427 NextLine = Line;
428 Status = GrubNextToken (Content, &Pos, &NextLine, &Token, &IsIndented, &ContainsVars);
429 if (EFI_ERROR (Status)) {
430 return Status;
431 }
432
433 //
434 // Terminate token and remember terminator char.
435 //
436 LastChar = Content[Pos];
437 if (Token != NULL) {
438 Content[Pos] = '\0';
439
440 if (TokenIndex == 0) {
441 //
442 // Warn on pretty obscure - though valid - syntax of building the command name from variables;
443 // do not warn for direct setting of grub internal values with no set command, i.e. just name=value,
444 // where the $ is only in the value.
445 //
446 Dollar = OcAsciiStrChr (Token, '$');
447 if (Dollar != NULL) {
448 Equals = OcAsciiStrChr (Token, '=');
449 if ((Equals == NULL) || (Dollar < Equals)) {
450 DEBUG ((DEBUG_WARN, "LNX: Ignoring tokenised %a %a %a %u\n", "command", Token, GRUB_LINE, Line));
451 }
452 } else {
453 //
454 // No non-indented variables after non-indented blscfg command can be used.
455 //
456 if (AsciiStrCmp ("blscfg", Token) == 0) {
457 return EFI_SUCCESS;
458 }
459
460 //
461 // We could process grub unset command similarly to set, but we probably don't need it.
462 //
463 if (AsciiStrCmp ("set", Token) == 0) {
464 SetCommand = TRUE;
465 SetIsIndented = IsIndented;
466 }
467 }
468 } else if ((TokenIndex == 1) && SetCommand) {
469 Status = SetVar (Line, Token, SetIsIndented, ContainsVars);
470 if (EFI_ERROR (Status)) {
471 return Status;
472 }
473 }
474
475 ++TokenIndex;
476 }
477
478 Line = NextLine;
479 } while (!GrubNextLine (LastChar, &Pos));
480 } while (LastChar != '\0');
481
482 //
483 // Possibly allow through on flag?
484 //
485 DEBUG ((DEBUG_WARN, "LNX: blscfg command not found in grub.cfg\n"));
486 return EFI_NOT_FOUND;
487}
#define GRUB_LINE
Definition GrubCfg.c:41
enum GRUB_VAR_STATE_ GRUB_VAR_STATE
GRUB_VAR_STATE_
Definition GrubCfg.c:34
@ GRUB_VAR_NONE
Definition GrubCfg.c:35
@ GRUB_VAR_CHAR
Definition GrubCfg.c:38
@ GRUB_VAR_START
Definition GrubCfg.c:36
@ GRUB_VAR_END
Definition GrubCfg.c:37
#define VAR_FLAGS_NUMERIC
Definition GrubCfg.c:48
enum GRUB_PARSE_STATE_ GRUB_PARSE_STATE
STATIC BOOLEAN GrubNextLine(CHAR8 Ch, UINTN *Pos)
Definition GrubCfg.c:323
STATIC EFI_STATUS SetVar(UINTN Line, CHAR8 *Token, BOOLEAN IsIndented, BOOLEAN ContainsVars)
Definition GrubCfg.c:345
#define SHIFT_TOKEN(offset)
Definition GrubCfg.c:50
#define VAR_FLAGS_NONE
Definition GrubCfg.c:46
GRUB_PARSE_STATE_
Definition GrubCfg.c:23
@ GRUB_LEADING_SPACE
Definition GrubCfg.c:24
@ GRUB_SINGLE_QUOTE
Definition GrubCfg.c:27
@ GRUB_TOKEN
Definition GrubCfg.c:26
@ GRUB_DOUBLE_QUOTE
Definition GrubCfg.c:28
@ GRUB_COMMENT
Definition GrubCfg.c:25
EFI_STATUS InternalProcessGrubCfg(IN OUT CHAR8 *Content)
Definition GrubCfg.c:398
#define VAR_FLAGS_BRACE
Definition GrubCfg.c:47
STATIC EFI_STATUS GrubNextToken(CHAR8 *Content, UINTN *Pos, UINTN *Line, CHAR8 **Token, BOOLEAN *IsIndented, BOOLEAN *ContainsVars)
Definition GrubCfg.c:57
EFI_STATUS InternalSetGrubVar(CHAR8 *Key, CHAR8 *Value, UINTN Errors)
Definition GrubVars.c:44
#define VAR_ERR_INDENTED
#define VAR_ERR_HAS_VARS
#define IS_DIGIT(c)
#define IS_ALPHA(c)
CHAR8 *EFIAPI OcAsciiStrChr(IN CONST CHAR8 *String, IN CHAR8 Char)
Definition OcAsciiLib.c:369
#define ASSERT(x)
Definition coder.h:55
ush Pos
Definition deflate.h:92