Properly initialise variable to fix detection of non-Arch kernel versions
[refind] / refind / mystrings.c
1 /*
2 * refind/mystrings.c
3 * String-manipulation functions
4 *
5 * Copyright (c) 2012-2015 Roderick W. Smith
6 *
7 * Distributed under the terms of the GNU General Public License (GPL)
8 * version 3 (GPLv3), or (at your option) any later version.
9 *
10 */
11 /*
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27 #include "mystrings.h"
28 #include "lib.h"
29
30 BOOLEAN StriSubCmp(IN CHAR16 *SmallStr, IN CHAR16 *BigStr) {
31 BOOLEAN Found = 0, Terminate = 0;
32 UINTN BigIndex = 0, SmallIndex = 0, BigStart = 0;
33
34 if (SmallStr && BigStr) {
35 while (!Terminate) {
36 if (BigStr[BigIndex] == '\0') {
37 Terminate = 1;
38 }
39 if (SmallStr[SmallIndex] == '\0') {
40 Found = 1;
41 Terminate = 1;
42 }
43 if ((SmallStr[SmallIndex] & ~0x20) == (BigStr[BigIndex] & ~0x20)) {
44 SmallIndex++;
45 BigIndex++;
46 } else {
47 SmallIndex = 0;
48 BigStart++;
49 BigIndex = BigStart;
50 }
51 } // while
52 } // if
53 return Found;
54 } // BOOLEAN StriSubCmp()
55
56 // Performs a case-insensitive string comparison. This function is necesary
57 // because some EFIs have buggy StriCmp() functions that actually perform
58 // case-sensitive comparisons.
59 // Returns TRUE if strings are identical, FALSE otherwise.
60 BOOLEAN MyStriCmp(IN CONST CHAR16 *FirstString, IN CONST CHAR16 *SecondString) {
61 if (FirstString && SecondString) {
62 while ((*FirstString != L'\0') && ((*FirstString & ~0x20) == (*SecondString & ~0x20))) {
63 FirstString++;
64 SecondString++;
65 }
66 return (*FirstString == *SecondString);
67 } else {
68 return FALSE;
69 }
70 } // BOOLEAN MyStriCmp()
71
72 /*++
73 *
74 * Routine Description:
75 *
76 * Find a substring.
77 *
78 * Arguments:
79 *
80 * String - Null-terminated string to search.
81 * StrCharSet - Null-terminated string to search for.
82 *
83 * Returns:
84 * The address of the first occurrence of the matching substring if successful, or NULL otherwise.
85 * --*/
86 CHAR16* MyStrStr (IN CHAR16 *String, IN CHAR16 *StrCharSet)
87 {
88 CHAR16 *Src;
89 CHAR16 *Sub;
90
91 if ((String == NULL) || (StrCharSet == NULL))
92 return NULL;
93
94 Src = String;
95 Sub = StrCharSet;
96
97 while ((*String != L'\0') && (*StrCharSet != L'\0')) {
98 if (*String++ != *StrCharSet) {
99 String = ++Src;
100 StrCharSet = Sub;
101 } else {
102 StrCharSet++;
103 }
104 }
105 if (*StrCharSet == L'\0') {
106 return Src;
107 } else {
108 return NULL;
109 }
110 } // CHAR16 *MyStrStr()
111
112 // Convert input string to all-lowercase.
113 // DO NOT USE the standard StrLwr() function, since it's broken on some EFIs!
114 VOID ToLower(CHAR16 * MyString) {
115 UINTN i = 0;
116
117 if (MyString) {
118 while (MyString[i] != L'\0') {
119 if ((MyString[i] >= L'A') && (MyString[i] <= L'Z'))
120 MyString[i] = MyString[i] - L'A' + L'a';
121 i++;
122 } // while
123 } // if
124 } // VOID ToLower()
125
126 // Merges two strings, creating a new one and returning a pointer to it.
127 // If AddChar != 0, the specified character is placed between the two original
128 // strings (unless the first string is NULL or empty). The original input
129 // string *First is de-allocated and replaced by the new merged string.
130 // This is similar to StrCat, but safer and more flexible because
131 // MergeStrings allocates memory that's the correct size for the
132 // new merged string, so it can take a NULL *First and it cleans
133 // up the old memory. It should *NOT* be used with a constant
134 // *First, though....
135 VOID MergeStrings(IN OUT CHAR16 **First, IN CHAR16 *Second, CHAR16 AddChar) {
136 UINTN Length1 = 0, Length2 = 0;
137 CHAR16* NewString;
138
139 if (*First != NULL)
140 Length1 = StrLen(*First);
141 if (Second != NULL)
142 Length2 = StrLen(Second);
143 NewString = AllocatePool(sizeof(CHAR16) * (Length1 + Length2 + 2));
144 if (NewString != NULL) {
145 if ((*First != NULL) && (Length1 == 0)) {
146 MyFreePool(*First);
147 *First = NULL;
148 }
149 NewString[0] = L'\0';
150 if (*First != NULL) {
151 StrCat(NewString, *First);
152 if (AddChar) {
153 NewString[Length1] = AddChar;
154 NewString[Length1 + 1] = '\0';
155 } // if (AddChar)
156 } // if (*First != NULL)
157 if (Second != NULL)
158 StrCat(NewString, Second);
159 MyFreePool(*First);
160 *First = NewString;
161 } else {
162 Print(L"Error! Unable to allocate memory in MergeStrings()!\n");
163 } // if/else
164 } // VOID MergeStrings()
165
166 // Similar to MergeStrings, but breaks the input string into word chunks and
167 // merges each word separately. Words are defined as string fragments separated
168 // by ' ', '_', or '-'.
169 VOID MergeWords(CHAR16 **MergeTo, CHAR16 *SourceString, CHAR16 AddChar) {
170 CHAR16 *Temp, *Word, *p;
171 BOOLEAN LineFinished = FALSE;
172
173 if (SourceString) {
174 Temp = Word = p = StrDuplicate(SourceString);
175 if (Temp) {
176 while (!LineFinished) {
177 if ((*p == L' ') || (*p == L'_') || (*p == L'-') || (*p == L'\0')) {
178 if (*p == L'\0')
179 LineFinished = TRUE;
180 *p = L'\0';
181 if (*Word != L'\0')
182 MergeStrings(MergeTo, Word, AddChar);
183 Word = p + 1;
184 } // if
185 p++;
186 } // while
187 MyFreePool(Temp);
188 } else {
189 Print(L"Error! Unable to allocate memory in MergeWords()!\n");
190 } // if/else
191 } // if
192 } // VOID MergeWords()
193
194 // Restrict TheString to at most Limit characters.
195 // Does this in two ways:
196 // - Locates stretches of two or more spaces and compresses
197 // them down to one space.
198 // - Truncates TheString
199 // Returns TRUE if changes were made, FALSE otherwise
200 BOOLEAN LimitStringLength(CHAR16 *TheString, UINTN Limit) {
201 CHAR16 *SubString, *TempString;
202 UINTN i;
203 BOOLEAN HasChanged = FALSE;
204
205 // SubString will be NULL or point WITHIN TheString
206 SubString = MyStrStr(TheString, L" ");
207 while (SubString != NULL) {
208 i = 0;
209 while (SubString[i] == L' ')
210 i++;
211 if (i >= StrLen(SubString)) {
212 SubString[0] = '\0';
213 HasChanged = TRUE;
214 } else {
215 TempString = StrDuplicate(&SubString[i]);
216 if (TempString != NULL) {
217 StrCpy(&SubString[1], TempString);
218 MyFreePool(TempString);
219 HasChanged = TRUE;
220 } else {
221 // memory allocation problem; abort to avoid potentially infinite loop!
222 break;
223 } // if/else
224 } // if/else
225 SubString = MyStrStr(TheString, L" ");
226 } // while
227
228 // If the string is still too long, truncate it....
229 if (StrLen(TheString) > Limit) {
230 TheString[Limit] = '\0';
231 HasChanged = TRUE;
232 } // if
233
234 return HasChanged;
235 } // BOOLEAN LimitStringLength()
236
237 // Returns all the digits in the input string, including intervening
238 // non-digit characters. For instance, if InString is "foo-3.3.4-7.img",
239 // this function returns "3.3.4-7". If InString contains no digits,
240 // the return value is NULL.
241 // As a special case for Arch Linux the strings "linux" and "linux-lts"
242 // are considered to be digits.
243 CHAR16 *FindNumbers(IN CHAR16 *InString) {
244 UINTN i, StartOfElement, EndOfElement = 0, CopyLength;
245 CHAR16 *Found = NULL;
246
247 if (InString == NULL)
248 return NULL;
249
250 StartOfElement = StrLen(InString);
251
252 // Find "linux-lts" or "linux"
253 Found = MyStrStr(InString, L"linux-lts");
254 if (Found != NULL) {
255 StartOfElement = Found - InString;
256 EndOfElement = StartOfElement + StrLen(L"linux-lts") - 1;
257 } else {
258 Found = MyStrStr(InString, L"linux");
259 if (Found != NULL) {
260 StartOfElement = Found - InString;
261 EndOfElement = StartOfElement + StrLen(L"linux") - 1;
262 } // if
263 } // if/else
264 Found = NULL;
265
266 // Find start & end of target element
267 for (i = 0; InString[i] != L'\0'; i++) {
268 if ((InString[i] >= L'0') && (InString[i] <= L'9')) {
269 if (StartOfElement > i)
270 StartOfElement = i;
271 if (EndOfElement < i)
272 EndOfElement = i;
273 } // if
274 } // for
275
276 // Extract the target element
277 if (EndOfElement > 0) {
278 if (EndOfElement >= StartOfElement) {
279 CopyLength = EndOfElement - StartOfElement + 1;
280 Found = StrDuplicate(&InString[StartOfElement]);
281 if (Found != NULL)
282 Found[CopyLength] = 0;
283 } // if (EndOfElement >= StartOfElement)
284 } // if (EndOfElement > 0)
285 return (Found);
286 } // CHAR16 *FindNumbers()
287
288 // Returns the number of characters that are in common between
289 // String1 and String2 before they diverge. For instance, if
290 // String1 is "FooBar" and String2 is "FoodiesBar", this function
291 // will return "3", since they both start with "Foo".
292 UINTN NumCharsInCommon(IN CHAR16* String1, IN CHAR16* String2) {
293 UINTN Count = 0;
294 if ((String1 == NULL) || (String2 == NULL))
295 return 0;
296 while ((String1[Count] != L'\0') && (String2[Count] != L'\0') && (String1[Count] == String2[Count]))
297 Count++;
298 return Count;
299 } // UINTN NumCharsInCommon()
300
301 // Find the #Index element (numbered from 0) in a comma-delimited string
302 // of elements.
303 // Returns the found element, or NULL if Index is out of range or InString
304 // is NULL. Note that the calling function is responsible for freeing the
305 // memory associated with the returned string pointer.
306 CHAR16 *FindCommaDelimited(IN CHAR16 *InString, IN UINTN Index) {
307 UINTN StartPos = 0, CurPos = 0, InLength;
308 BOOLEAN Found = FALSE;
309 CHAR16 *FoundString = NULL;
310
311 if (InString != NULL) {
312 InLength = StrLen(InString);
313 // After while() loop, StartPos marks start of item #Index
314 while ((Index > 0) && (CurPos < InLength)) {
315 if (InString[CurPos] == L',') {
316 Index--;
317 StartPos = CurPos + 1;
318 } // if
319 CurPos++;
320 } // while
321 // After while() loop, CurPos is one past the end of the element
322 while ((CurPos < InLength) && (!Found)) {
323 if (InString[CurPos] == L',')
324 Found = TRUE;
325 else
326 CurPos++;
327 } // while
328 if (Index == 0)
329 FoundString = StrDuplicate(&InString[StartPos]);
330 if (FoundString != NULL)
331 FoundString[CurPos - StartPos] = 0;
332 } // if
333 return (FoundString);
334 } // CHAR16 *FindCommaDelimited()
335
336 // Returns TRUE if SmallString is an element in the comma-delimited List,
337 // FALSE otherwise. Performs comparison case-insensitively.
338 BOOLEAN IsIn(IN CHAR16 *SmallString, IN CHAR16 *List) {
339 UINTN i = 0;
340 BOOLEAN Found = FALSE;
341 CHAR16 *OneElement;
342
343 if (SmallString && List) {
344 while (!Found && (OneElement = FindCommaDelimited(List, i++))) {
345 if (MyStriCmp(OneElement, SmallString))
346 Found = TRUE;
347 } // while
348 } // if
349 return Found;
350 } // BOOLEAN IsIn()
351
352 // Returns TRUE if any element of List can be found as a substring of
353 // BigString, FALSE otherwise. Performs comparisons case-insensitively.
354 BOOLEAN IsInSubstring(IN CHAR16 *BigString, IN CHAR16 *List) {
355 UINTN i = 0, ElementLength;
356 BOOLEAN Found = FALSE;
357 CHAR16 *OneElement;
358
359 if (BigString && List) {
360 while (!Found && (OneElement = FindCommaDelimited(List, i++))) {
361 ElementLength = StrLen(OneElement);
362 if ((ElementLength <= StrLen(BigString)) && (StriSubCmp(OneElement, BigString)))
363 Found = TRUE;
364 } // while
365 } // if
366 return Found;
367 } // BOOLEAN IsSubstringIn()
368
369 // Replace *SearchString in **MainString with *ReplString -- but if *SearchString
370 // is preceded by "%", instead remove that character.
371 // Returns TRUE if replacement was done, FALSE otherwise.
372 BOOLEAN ReplaceSubstring(IN OUT CHAR16 **MainString, IN CHAR16 *SearchString, IN CHAR16 *ReplString) {
373 BOOLEAN WasReplaced = FALSE;
374 CHAR16 *FoundSearchString, *NewString, *EndString;
375
376 FoundSearchString = MyStrStr(*MainString, SearchString);
377 if (FoundSearchString) {
378 NewString = AllocateZeroPool(sizeof(CHAR16) * StrLen(*MainString));
379 if (NewString) {
380 EndString = &(FoundSearchString[StrLen(SearchString)]);
381 FoundSearchString[0] = L'\0';
382 if ((FoundSearchString > *MainString) && (FoundSearchString[-1] == L'%')) {
383 FoundSearchString[-1] = L'\0';
384 ReplString = SearchString;
385 } // if
386 StrCpy(NewString, *MainString);
387 MergeStrings(&NewString, ReplString, L'\0');
388 MergeStrings(&NewString, EndString, L'\0');
389 MyFreePool(MainString);
390 *MainString = NewString;
391 WasReplaced = TRUE;
392 } // if
393 } // if
394 return WasReplaced;
395 } // BOOLEAN ReplaceSubstring()
396
397 // Returns TRUE if *Input contains nothing but valid hexadecimal characters,
398 // FALSE otherwise. Note that a leading "0x" is NOT acceptable in the input!
399 BOOLEAN IsValidHex(CHAR16 *Input) {
400 BOOLEAN IsHex = TRUE;
401 UINTN i = 0;
402
403 while ((Input[i] != L'\0') && IsHex) {
404 if (!(((Input[i] >= L'0') && (Input[i] <= L'9')) ||
405 ((Input[i] >= L'A') && (Input[i] <= L'F')) ||
406 ((Input[i] >= L'a') && (Input[i] <= L'f')))) {
407 IsHex = FALSE;
408 }
409 i++;
410 } // while
411 return IsHex;
412 } // BOOLEAN IsValidHex()
413
414 // Converts consecutive characters in the input string into a
415 // number, interpreting the string as a hexadecimal number, starting
416 // at the specified position and continuing for the specified number
417 // of characters or until the end of the string, whichever is first.
418 // NumChars must be between 1 and 16. Ignores invalid characters.
419 UINT64 StrToHex(CHAR16 *Input, UINTN Pos, UINTN NumChars) {
420 UINT64 retval = 0x00;
421 UINTN NumDone = 0, InputLength;
422 CHAR16 a;
423
424 if ((Input == NULL) || (NumChars == 0) || (NumChars > 16)) {
425 return 0;
426 }
427
428 InputLength = StrLen(Input);
429 while ((Pos <= InputLength) && (NumDone < NumChars)) {
430 a = Input[Pos];
431 if ((a >= '0') && (a <= '9')) {
432 retval *= 0x10;
433 retval += (a - '0');
434 NumDone++;
435 }
436 if ((a >= 'a') && (a <= 'f')) {
437 retval *= 0x10;
438 retval += (a - 'a' + 0x0a);
439 NumDone++;
440 }
441 if ((a >= 'A') && (a <= 'F')) {
442 retval *= 0x10;
443 retval += (a - 'A' + 0x0a);
444 NumDone++;
445 }
446 Pos++;
447 } // while()
448 return retval;
449 } // StrToHex()
450
451 // Returns TRUE if UnknownString can be interpreted as a GUID, FALSE otherwise.
452 // Note that the input string must have no extraneous spaces and must be
453 // conventionally formatted as a 36-character GUID, complete with dashes in
454 // appropriate places.
455 BOOLEAN IsGuid(CHAR16 *UnknownString) {
456 UINTN Length, i;
457 BOOLEAN retval = TRUE;
458 CHAR16 a;
459
460 if (UnknownString == NULL)
461 return FALSE;
462
463 Length = StrLen(UnknownString);
464 if (Length != 36)
465 return FALSE;
466
467 for (i = 0; i < Length; i++) {
468 a = UnknownString[i];
469 if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) {
470 if (a != L'-')
471 retval = FALSE;
472 } else if (((a < L'a') || (a > L'f')) &&
473 ((a < L'A') || (a > L'F')) &&
474 ((a < L'0') && (a > L'9'))) {
475 retval = FALSE;
476 } // if/else if
477 } // for
478 return retval;
479 } // BOOLEAN IsGuid()
480
481 // Return the GUID as a string, suitable for display to the user. Note that the calling
482 // function is responsible for freeing the allocated memory.
483 CHAR16 * GuidAsString(EFI_GUID *GuidData) {
484 CHAR16 *TheString;
485
486 TheString = AllocateZeroPool(42 * sizeof(CHAR16));
487 if (TheString != 0) {
488 SPrint (TheString, 82, L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
489 (UINTN)GuidData->Data1, (UINTN)GuidData->Data2, (UINTN)GuidData->Data3,
490 (UINTN)GuidData->Data4[0], (UINTN)GuidData->Data4[1], (UINTN)GuidData->Data4[2],
491 (UINTN)GuidData->Data4[3], (UINTN)GuidData->Data4[4], (UINTN)GuidData->Data4[5],
492 (UINTN)GuidData->Data4[6], (UINTN)GuidData->Data4[7]);
493 }
494 return TheString;
495 } // GuidAsString(EFI_GUID *GuidData)
496
497 EFI_GUID StringAsGuid(CHAR16 * InString) {
498 EFI_GUID Guid = NULL_GUID_VALUE;
499
500 if (!IsGuid(InString)) {
501 return Guid;
502 }
503
504 Guid.Data1 = (UINT32) StrToHex(InString, 0, 8);
505 Guid.Data2 = (UINT16) StrToHex(InString, 9, 4);
506 Guid.Data3 = (UINT16) StrToHex(InString, 14, 4);
507 Guid.Data4[0] = (UINT8) StrToHex(InString, 19, 2);
508 Guid.Data4[1] = (UINT8) StrToHex(InString, 21, 2);
509 Guid.Data4[2] = (UINT8) StrToHex(InString, 23, 2);
510 Guid.Data4[3] = (UINT8) StrToHex(InString, 26, 2);
511 Guid.Data4[4] = (UINT8) StrToHex(InString, 28, 2);
512 Guid.Data4[5] = (UINT8) StrToHex(InString, 30, 2);
513 Guid.Data4[6] = (UINT8) StrToHex(InString, 32, 2);
514 Guid.Data4[7] = (UINT8) StrToHex(InString, 34, 2);
515
516 return Guid;
517 } // EFI_GUID StringAsGuid()
518
519 // Delete the STRING_LIST pointed to by *StringList.
520 VOID DeleteStringList(STRING_LIST *StringList) {
521 STRING_LIST *Current = StringList, *Previous;
522
523 while (Current != NULL) {
524 MyFreePool(Current->Value);
525 Previous = Current;
526 Current = Current->Next;
527 MyFreePool(Previous);
528 }
529 } // VOID DeleteStringList()