]> git.proxmox.com Git - mirror_edk2.git/blame - BaseTools/Source/C/Common/ParseInf.c
BaseTools: Replace BSD License with BSD+Patent License
[mirror_edk2.git] / BaseTools / Source / C / Common / ParseInf.c
CommitLineData
30fdf114 1/** @file\r
97fa0ee9 2This contains some useful functions for parsing INF files.\r
30fdf114 3\r
f7496d71 4Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>\r
2e351cbe 5SPDX-License-Identifier: BSD-2-Clause-Patent\r
30fdf114 6\r
97fa0ee9 7**/\r
30fdf114
LG
8\r
9#include <assert.h>\r
10#include <string.h>\r
11#include <ctype.h>\r
12#include <stdlib.h>\r
13#include "EfiUtilityMsgs.h"\r
14#include "ParseInf.h"\r
1be2ed90 15#include "CommonLib.h"\r
30fdf114
LG
16\r
17CHAR8 *\r
18ReadLine (\r
19 IN MEMORY_FILE *InputFile,\r
20 IN OUT CHAR8 *InputBuffer,\r
21 IN UINTN MaxLength\r
22 )\r
23/*++\r
24\r
25Routine Description:\r
26\r
27 This function reads a line, stripping any comments.\r
f7496d71
LG
28 The function reads a string from the input stream argument and stores it in\r
29 the input string. ReadLine reads characters from the current file position\r
30 to and including the first newline character, to the end of the stream, or\r
31 until the number of characters read is equal to MaxLength - 1, whichever\r
32 comes first. The newline character, if read, is replaced with a \0.\r
30fdf114
LG
33\r
34Arguments:\r
35\r
36 InputFile Memory file image.\r
1be2ed90 37 InputBuffer Buffer to read into, must be MaxLength size.\r
30fdf114
LG
38 MaxLength The maximum size of the input buffer.\r
39\r
40Returns:\r
41\r
42 NULL if error or EOF\r
43 InputBuffer otherwise\r
44\r
45--*/\r
46{\r
47 CHAR8 *CharPtr;\r
48 CHAR8 *EndOfLine;\r
49 UINTN CharsToCopy;\r
50\r
51 //\r
52 // Verify input parameters are not null\r
53 //\r
54 assert (InputBuffer);\r
55 assert (InputFile->FileImage);\r
56 assert (InputFile->Eof);\r
57 assert (InputFile->CurrentFilePointer);\r
58\r
59 //\r
60 // Check for end of file condition\r
61 //\r
62 if (InputFile->CurrentFilePointer >= InputFile->Eof) {\r
63 return NULL;\r
64 }\r
65 //\r
66 // Find the next newline char\r
67 //\r
68 EndOfLine = strchr (InputFile->CurrentFilePointer, '\n');\r
69\r
70 //\r
71 // Determine the number of characters to copy.\r
72 //\r
73 if (EndOfLine == 0) {\r
74 //\r
75 // If no newline found, copy to the end of the file.\r
76 //\r
77 CharsToCopy = InputFile->Eof - InputFile->CurrentFilePointer;\r
78 } else if (EndOfLine >= InputFile->Eof) {\r
79 //\r
80 // If the newline found was beyond the end of file, copy to the eof.\r
81 //\r
82 CharsToCopy = InputFile->Eof - InputFile->CurrentFilePointer;\r
83 } else {\r
84 //\r
85 // Newline found in the file.\r
86 //\r
87 CharsToCopy = EndOfLine - InputFile->CurrentFilePointer;\r
88 }\r
89 //\r
90 // If the end of line is too big for the current buffer, set it to the max\r
91 // size of the buffer (leaving room for the \0.\r
92 //\r
93 if (CharsToCopy > MaxLength - 1) {\r
94 CharsToCopy = MaxLength - 1;\r
95 }\r
96 //\r
97 // Copy the line.\r
98 //\r
99 memcpy (InputBuffer, InputFile->CurrentFilePointer, CharsToCopy);\r
100\r
101 //\r
102 // Add the null termination over the 0x0D\r
103 //\r
104 if (InputBuffer[CharsToCopy - 1] == '\r') {\r
105\r
106 InputBuffer[CharsToCopy - 1] = '\0';\r
107\r
108 } else {\r
109\r
110 InputBuffer[CharsToCopy] = '\0';\r
111\r
112 }\r
113\r
114 //\r
115 // Increment the current file pointer (include the 0x0A)\r
116 //\r
117 InputFile->CurrentFilePointer += CharsToCopy + 1;\r
118\r
119 //\r
120 // Strip any comments\r
121 //\r
122 CharPtr = strstr (InputBuffer, "//");\r
123 if (CharPtr != 0) {\r
124 CharPtr[0] = 0;\r
125 }\r
126 //\r
127 // Return the string\r
128 //\r
129 return InputBuffer;\r
130}\r
131\r
132BOOLEAN\r
133FindSection (\r
134 IN MEMORY_FILE *InputFile,\r
135 IN CHAR8 *Section\r
136 )\r
137/*++\r
138\r
139Routine Description:\r
140\r
141 This function parses a file from the beginning to find a section.\r
142 The section string may be anywhere within a line.\r
143\r
144Arguments:\r
145\r
146 InputFile Memory file image.\r
147 Section Section to search for\r
148\r
149Returns:\r
150\r
151 FALSE if error or EOF\r
152 TRUE if section found\r
153\r
154--*/\r
155{\r
1be2ed90 156 CHAR8 InputBuffer[MAX_LONG_FILE_PATH];\r
30fdf114
LG
157 CHAR8 *CurrentToken;\r
158\r
159 //\r
160 // Verify input is not NULL\r
161 //\r
162 assert (InputFile->FileImage);\r
163 assert (InputFile->Eof);\r
164 assert (InputFile->CurrentFilePointer);\r
165 assert (Section);\r
166\r
167 //\r
168 // Rewind to beginning of file\r
169 //\r
170 InputFile->CurrentFilePointer = InputFile->FileImage;\r
171\r
172 //\r
173 // Read lines until the section is found\r
174 //\r
175 while (InputFile->CurrentFilePointer < InputFile->Eof) {\r
176 //\r
177 // Read a line\r
178 //\r
1be2ed90 179 ReadLine (InputFile, InputBuffer, MAX_LONG_FILE_PATH);\r
30fdf114
LG
180\r
181 //\r
182 // Check if the section is found\r
183 //\r
184 CurrentToken = strstr (InputBuffer, Section);\r
185 if (CurrentToken != NULL) {\r
186 return TRUE;\r
187 }\r
188 }\r
189\r
190 return FALSE;\r
191}\r
192\r
193EFI_STATUS\r
194FindToken (\r
195 IN MEMORY_FILE *InputFile,\r
196 IN CHAR8 *Section,\r
197 IN CHAR8 *Token,\r
198 IN UINTN Instance,\r
199 OUT CHAR8 *Value\r
200 )\r
201/*++\r
202\r
203Routine Description:\r
204\r
205 Finds a token value given the section and token to search for.\r
206\r
207Arguments:\r
208\r
209 InputFile Memory file image.\r
210 Section The section to search for, a string within [].\r
211 Token The token to search for, e.g. EFI_PEIM_RECOVERY, followed by an = in the INF file.\r
212 Instance The instance of the token to search for. Zero is the first instance.\r
1be2ed90 213 Value The string that holds the value following the =. Must be MAX_LONG_FILE_PATH in size.\r
30fdf114
LG
214\r
215Returns:\r
216\r
217 EFI_SUCCESS Value found.\r
218 EFI_ABORTED Format error detected in INF file.\r
219 EFI_INVALID_PARAMETER Input argument was null.\r
220 EFI_LOAD_ERROR Error reading from the file.\r
221 EFI_NOT_FOUND Section/Token/Value not found.\r
222\r
223--*/\r
224{\r
1be2ed90 225 CHAR8 InputBuffer[MAX_LONG_FILE_PATH];\r
30fdf114 226 CHAR8 *CurrentToken;\r
8b7ebdb0 227 CHAR8 *Delimiter;\r
30fdf114
LG
228 BOOLEAN ParseError;\r
229 BOOLEAN ReadError;\r
fb0b35e0 230 UINTN Occurrence;\r
30fdf114
LG
231\r
232 //\r
233 // Check input parameters\r
234 //\r
235 if (InputFile->FileImage == NULL ||\r
236 InputFile->Eof == NULL ||\r
237 InputFile->CurrentFilePointer == NULL ||\r
238 Section == NULL ||\r
239 strlen (Section) == 0 ||\r
240 Token == NULL ||\r
241 strlen (Token) == 0 ||\r
242 Value == NULL\r
243 ) {\r
244 return EFI_INVALID_PARAMETER;\r
245 }\r
246 //\r
247 // Initialize error codes\r
248 //\r
249 ParseError = FALSE;\r
250 ReadError = FALSE;\r
251\r
252 //\r
253 // Initialize our instance counter for the search token\r
254 //\r
fb0b35e0 255 Occurrence = 0;\r
30fdf114
LG
256\r
257 if (FindSection (InputFile, Section)) {\r
258 //\r
259 // Found the desired section, find and read the desired token\r
260 //\r
261 do {\r
262 //\r
263 // Read a line from the file\r
264 //\r
1be2ed90 265 if (ReadLine (InputFile, InputBuffer, MAX_LONG_FILE_PATH) == NULL) {\r
30fdf114
LG
266 //\r
267 // Error reading from input file\r
268 //\r
269 ReadError = TRUE;\r
270 break;\r
271 }\r
272 //\r
273 // Get the first non-whitespace string\r
274 //\r
8b7ebdb0
LG
275 Delimiter = strchr (InputBuffer, '=');\r
276 if (Delimiter != NULL) {\r
277 *Delimiter = 0;\r
278 }\r
279\r
30fdf114 280 CurrentToken = strtok (InputBuffer, " \t\n");\r
8b7ebdb0 281 if (CurrentToken == NULL || Delimiter == NULL) {\r
30fdf114
LG
282 //\r
283 // Whitespace line found (or comment) so continue\r
284 //\r
285 CurrentToken = InputBuffer;\r
286 continue;\r
287 }\r
288 //\r
289 // Make sure we have not reached the end of the current section\r
290 //\r
291 if (CurrentToken[0] == '[') {\r
292 break;\r
293 }\r
294 //\r
295 // Compare the current token with the desired token\r
296 //\r
297 if (strcmp (CurrentToken, Token) == 0) {\r
298 //\r
299 // Found it\r
300 //\r
301 //\r
302 // Check if it is the correct instance\r
303 //\r
fb0b35e0 304 if (Instance == Occurrence) {\r
30fdf114
LG
305 //\r
306 // Copy the contents following the =\r
307 //\r
8b7ebdb0
LG
308 CurrentToken = Delimiter + 1;\r
309 if (*CurrentToken == 0) {\r
30fdf114
LG
310 //\r
311 // Nothing found, parsing error\r
312 //\r
313 ParseError = TRUE;\r
314 } else {\r
8b7ebdb0
LG
315 //\r
316 // Strip leading white space\r
317 //\r
318 while (*CurrentToken == ' ' || *CurrentToken == '\t') {\r
319 CurrentToken++;\r
320 }\r
30fdf114
LG
321 //\r
322 // Copy the current token to the output value\r
323 //\r
324 strcpy (Value, CurrentToken);\r
8b7ebdb0
LG
325 //\r
326 // Strip trailing white space\r
327 //\r
328 while (strlen(Value) > 0 && (*(Value + strlen(Value) - 1) == ' ' || *(Value + strlen(Value) - 1) == '\t')) {\r
329 *(Value + strlen(Value) - 1) = 0;\r
330 }\r
30fdf114
LG
331 return EFI_SUCCESS;\r
332 }\r
333 } else {\r
334 //\r
fb0b35e0 335 // Increment the occurrence found\r
30fdf114 336 //\r
fb0b35e0 337 Occurrence++;\r
30fdf114
LG
338 }\r
339 }\r
340 } while (\r
341 !ParseError &&\r
342 !ReadError &&\r
343 InputFile->CurrentFilePointer < InputFile->Eof &&\r
344 CurrentToken[0] != '[' &&\r
fb0b35e0 345 Occurrence <= Instance\r
30fdf114
LG
346 );\r
347 }\r
348 //\r
349 // Distinguish between read errors and INF file format errors.\r
350 //\r
351 if (ReadError) {\r
352 return EFI_LOAD_ERROR;\r
353 }\r
354\r
355 if (ParseError) {\r
356 return EFI_ABORTED;\r
357 }\r
358\r
359 return EFI_NOT_FOUND;\r
360}\r
361\r
362EFI_STATUS\r
363StringToGuid (\r
364 IN CHAR8 *AsciiGuidBuffer,\r
365 OUT EFI_GUID *GuidBuffer\r
366 )\r
367/*++\r
368\r
f7496d71 369Routine Description:\r
30fdf114 370\r
f7496d71 371 Converts a string to an EFI_GUID. The string must be in the\r
30fdf114
LG
372 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format.\r
373\r
f7496d71 374Arguments:\r
30fdf114
LG
375\r
376 AsciiGuidBuffer - pointer to ascii string\r
377 GuidBuffer - pointer to destination Guid\r
378\r
f7496d71 379Returns:\r
30fdf114
LG
380\r
381 EFI_ABORTED Could not convert the string\r
382 EFI_SUCCESS The string was successfully converted\r
383 EFI_INVALID_PARAMETER Input parameter is invalid.\r
384\r
385--*/\r
386{\r
387 INT32 Index;\r
851c97ce
HW
388 int Data1;\r
389 int Data2;\r
390 int Data3;\r
391 int Data4[8];\r
30fdf114
LG
392\r
393 if (AsciiGuidBuffer == NULL || GuidBuffer == NULL) {\r
394 return EFI_INVALID_PARAMETER;\r
395 }\r
396 //\r
397 // Check Guid Format strictly xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r
398 //\r
399 for (Index = 0; AsciiGuidBuffer[Index] != '\0' && Index < 37; Index ++) {\r
400 if (Index == 8 || Index == 13 || Index == 18 || Index == 23) {\r
401 if (AsciiGuidBuffer[Index] != '-') {\r
402 break;\r
403 }\r
404 } else {\r
f7496d71 405 if (((AsciiGuidBuffer[Index] >= '0') && (AsciiGuidBuffer[Index] <= '9')) ||\r
30fdf114
LG
406 ((AsciiGuidBuffer[Index] >= 'a') && (AsciiGuidBuffer[Index] <= 'f')) ||\r
407 ((AsciiGuidBuffer[Index] >= 'A') && (AsciiGuidBuffer[Index] <= 'F'))) {\r
408 continue;\r
409 } else {\r
410 break;\r
411 }\r
412 }\r
413 }\r
f7496d71 414\r
30fdf114
LG
415 if (Index < 36 || AsciiGuidBuffer[36] != '\0') {\r
416 Error (NULL, 0, 1003, "Invalid option value", "Incorrect GUID \"%s\"\n Correct Format \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", AsciiGuidBuffer);\r
417 return EFI_ABORTED;\r
418 }\r
f7496d71 419\r
30fdf114
LG
420 //\r
421 // Scan the guid string into the buffer\r
422 //\r
423 Index = sscanf (\r
424 AsciiGuidBuffer,\r
fd171542 425 "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",\r
30fdf114
LG
426 &Data1,\r
427 &Data2,\r
428 &Data3,\r
429 &Data4[0],\r
430 &Data4[1],\r
431 &Data4[2],\r
432 &Data4[3],\r
433 &Data4[4],\r
434 &Data4[5],\r
435 &Data4[6],\r
436 &Data4[7]\r
437 );\r
438\r
439 //\r
440 // Verify the correct number of items were scanned.\r
441 //\r
442 if (Index != 11) {\r
443 Error (NULL, 0, 1003, "Invalid option value", "Incorrect GUID \"%s\"\n Correct Format \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", AsciiGuidBuffer);\r
444 return EFI_ABORTED;\r
445 }\r
446 //\r
447 // Copy the data into our GUID.\r
448 //\r
449 GuidBuffer->Data1 = (UINT32) Data1;\r
450 GuidBuffer->Data2 = (UINT16) Data2;\r
451 GuidBuffer->Data3 = (UINT16) Data3;\r
452 GuidBuffer->Data4[0] = (UINT8) Data4[0];\r
453 GuidBuffer->Data4[1] = (UINT8) Data4[1];\r
454 GuidBuffer->Data4[2] = (UINT8) Data4[2];\r
455 GuidBuffer->Data4[3] = (UINT8) Data4[3];\r
456 GuidBuffer->Data4[4] = (UINT8) Data4[4];\r
457 GuidBuffer->Data4[5] = (UINT8) Data4[5];\r
458 GuidBuffer->Data4[6] = (UINT8) Data4[6];\r
459 GuidBuffer->Data4[7] = (UINT8) Data4[7];\r
460\r
461 return EFI_SUCCESS;\r
462}\r
463\r
464EFI_STATUS\r
465AsciiStringToUint64 (\r
466 IN CONST CHAR8 *AsciiString,\r
467 IN BOOLEAN IsHex,\r
468 OUT UINT64 *ReturnValue\r
469 )\r
470/*++\r
471\r
472Routine Description:\r
473\r
f7496d71 474 Converts a null terminated ascii string that represents a number into a\r
fb0b35e0 475 UINT64 value. A hex number may be preceded by a 0x, but may not be\r
f7496d71 476 succeeded by an h. A number without 0x or 0X is considered to be base 10\r
30fdf114
LG
477 unless the IsHex input is true.\r
478\r
479Arguments:\r
480\r
481 AsciiString The string to convert.\r
482 IsHex Force the string to be treated as a hex number.\r
483 ReturnValue The return value.\r
484\r
485Returns:\r
486\r
487 EFI_SUCCESS Number successfully converted.\r
488 EFI_ABORTED Invalid character encountered.\r
489\r
490--*/\r
491{\r
492 UINT8 Index;\r
52302d4d 493 UINT64 Value;\r
30fdf114 494 CHAR8 CurrentChar;\r
f7496d71 495\r
30fdf114
LG
496 //\r
497 // Initialize the result\r
498 //\r
52302d4d
LG
499 Value = 0;\r
500 Index = 0;\r
f7496d71 501\r
30fdf114 502 //\r
99e55970 503 // Check input parameter\r
30fdf114 504 //\r
8391ffdc 505 if (AsciiString == NULL || ReturnValue == NULL || strlen(AsciiString) > 0xFF) {\r
30fdf114
LG
506 return EFI_INVALID_PARAMETER;\r
507 }\r
52302d4d
LG
508 while (AsciiString[Index] == ' ') {\r
509 Index ++;\r
510 }\r
f7496d71 511\r
30fdf114
LG
512 //\r
513 // Add each character to the result\r
514 //\r
f7496d71 515\r
64137027
LG
516 //\r
517 // Skip first two chars only if the string starts with '0x' or '0X'\r
518 //\r
519 if (AsciiString[Index] == '0' && (AsciiString[Index + 1] == 'x' || AsciiString[Index + 1] == 'X')) {\r
520 IsHex = TRUE;\r
521 Index += 2;\r
522 }\r
523 if (IsHex) {\r
30fdf114
LG
524 //\r
525 // Convert the hex string.\r
526 //\r
64137027 527 for (; AsciiString[Index] != '\0'; Index++) {\r
30fdf114 528 CurrentChar = AsciiString[Index];\r
52302d4d
LG
529 if (CurrentChar == ' ') {\r
530 break;\r
531 }\r
532 //\r
533 // Verify Hex string\r
534 //\r
535 if (isxdigit ((int)CurrentChar) == 0) {\r
536 return EFI_ABORTED;\r
537 }\r
538 //\r
539 // Add hex value\r
540 //\r
541 Value *= 16;\r
30fdf114 542 if (CurrentChar >= '0' && CurrentChar <= '9') {\r
52302d4d 543 Value += CurrentChar - '0';\r
30fdf114 544 } else if (CurrentChar >= 'a' && CurrentChar <= 'f') {\r
52302d4d 545 Value += CurrentChar - 'a' + 10;\r
30fdf114 546 } else if (CurrentChar >= 'A' && CurrentChar <= 'F') {\r
52302d4d 547 Value += CurrentChar - 'A' + 10;\r
30fdf114
LG
548 }\r
549 }\r
550\r
52302d4d 551 *ReturnValue = Value;\r
30fdf114
LG
552 } else {\r
553 //\r
52302d4d 554 // Convert dec string is a number\r
30fdf114 555 //\r
52302d4d
LG
556 for (; Index < strlen (AsciiString); Index++) {\r
557 CurrentChar = AsciiString[Index];\r
558 if (CurrentChar == ' ') {\r
559 break;\r
560 }\r
561 //\r
562 // Verify Dec string\r
563 //\r
564 if (isdigit ((int)CurrentChar) == 0) {\r
30fdf114
LG
565 return EFI_ABORTED;\r
566 }\r
52302d4d
LG
567 //\r
568 // Add dec value\r
569 //\r
570 Value = Value * 10;\r
571 Value += CurrentChar - '0';\r
30fdf114
LG
572 }\r
573\r
52302d4d 574 *ReturnValue = Value;\r
30fdf114
LG
575 }\r
576\r
577 return EFI_SUCCESS;\r
578}\r
579\r
580CHAR8 *\r
581ReadLineInStream (\r
582 IN FILE *InputFile,\r
583 IN OUT CHAR8 *InputBuffer\r
584 )\r
585/*++\r
586\r
587Routine Description:\r
588\r
589 This function reads a line, stripping any comments.\r
590 // BUGBUG: This is obsolete once genmake goes away...\r
591\r
592Arguments:\r
593\r
594 InputFile Stream pointer.\r
1be2ed90 595 InputBuffer Buffer to read into, must be MAX_LONG_FILE_PATH size.\r
30fdf114
LG
596\r
597Returns:\r
598\r
599 NULL if error or EOF\r
600 InputBuffer otherwise\r
601\r
602--*/\r
603{\r
604 CHAR8 *CharPtr;\r
605\r
606 //\r
607 // Verify input parameters are not null\r
608 //\r
609 assert (InputFile);\r
610 assert (InputBuffer);\r
611\r
612 //\r
613 // Read a line\r
614 //\r
1be2ed90 615 if (fgets (InputBuffer, MAX_LONG_FILE_PATH, InputFile) == NULL) {\r
30fdf114
LG
616 return NULL;\r
617 }\r
618 //\r
619 // Strip any comments\r
620 //\r
621 CharPtr = strstr (InputBuffer, "//");\r
622 if (CharPtr != 0) {\r
623 CharPtr[0] = 0;\r
624 }\r
625\r
626 CharPtr = strstr (InputBuffer, "#");\r
627 if (CharPtr != 0) {\r
628 CharPtr[0] = 0;\r
629 }\r
630 //\r
631 // Return the string\r
632 //\r
633 return InputBuffer;\r
634}\r
635\r
636BOOLEAN\r
637FindSectionInStream (\r
638 IN FILE *InputFile,\r
639 IN CHAR8 *Section\r
640 )\r
641/*++\r
642\r
643Routine Description:\r
644\r
645 This function parses a stream file from the beginning to find a section.\r
646 The section string may be anywhere within a line.\r
647 // BUGBUG: This is obsolete once genmake goes away...\r
648\r
649Arguments:\r
650\r
651 InputFile Stream pointer.\r
652 Section Section to search for\r
653\r
654Returns:\r
655\r
656 FALSE if error or EOF\r
657 TRUE if section found\r
658\r
659--*/\r
660{\r
1be2ed90 661 CHAR8 InputBuffer[MAX_LONG_FILE_PATH];\r
30fdf114
LG
662 CHAR8 *CurrentToken;\r
663\r
664 //\r
665 // Verify input is not NULL\r
666 //\r
667 assert (InputFile);\r
668 assert (Section);\r
669\r
670 //\r
671 // Rewind to beginning of file\r
672 //\r
673 if (fseek (InputFile, 0, SEEK_SET) != 0) {\r
674 return FALSE;\r
675 }\r
676 //\r
677 // Read lines until the section is found\r
678 //\r
679 while (feof (InputFile) == 0) {\r
680 //\r
681 // Read a line\r
682 //\r
683 ReadLineInStream (InputFile, InputBuffer);\r
684\r
685 //\r
686 // Check if the section is found\r
687 //\r
688 CurrentToken = strstr (InputBuffer, Section);\r
689 if (CurrentToken != NULL) {\r
690 return TRUE;\r
691 }\r
692 }\r
693\r
694 return FALSE;\r
695}\r