1/** @file
2  Functions for manipulating file names.
3
4Copyright (c) 2005 - 2015, Intel Corporation. All rights reserved.<BR>
5This program and the accompanying materials are licensed and made available
6under the terms and conditions of the BSD License which accompanies this
7distribution. The full text of the license may be found at
8http://opensource.org/licenses/bsd-license.php
9
10THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12
13**/
14
15#include "Fat.h"
16
17/**
18
19  This function checks whether the input FileName is a valid 8.3 short name.
20  If the input FileName is a valid 8.3, the output is the 8.3 short name;
21  otherwise, the output is the base tag of 8.3 short name.
22
23  @param  FileName              - The input unicode filename.
24  @param  File8Dot3Name         - The output ascii 8.3 short name or base tag of 8.3 short name.
25
26  @retval TRUE                  - The input unicode filename is a valid 8.3 short name.
27  @retval FALSE                 - The input unicode filename is not a valid 8.3 short name.
28
29**/
30BOOLEAN
31FatCheckIs8Dot3Name (
32  IN  CHAR16    *FileName,
33  OUT CHAR8     *File8Dot3Name
34  )
35{
36  BOOLEAN PossibleShortName;
37  CHAR16  *TempName;
38  CHAR16  *ExtendName;
39  CHAR16  *SeparateDot;
40  UINTN   MainNameLen;
41  UINTN   ExtendNameLen;
42
43  PossibleShortName = TRUE;
44  SeparateDot       = NULL;
45  SetMem (File8Dot3Name, FAT_NAME_LEN, ' ');
46  for (TempName = FileName; *TempName != '\0'; TempName++) {
47    if (*TempName == L'.') {
48      SeparateDot = TempName;
49    }
50  }
51
52  if (SeparateDot == NULL) {
53    //
54    // Extended filename is not detected
55    //
56    MainNameLen   = TempName - FileName;
57    ExtendName    = TempName;
58    ExtendNameLen = 0;
59  } else {
60    //
61    // Extended filename is detected
62    //
63    MainNameLen   = SeparateDot - FileName;
64    ExtendName    = SeparateDot + 1;
65    ExtendNameLen = TempName - ExtendName;
66  }
67  //
68  // We scan the filename for the second time
69  // to check if there exists any extra blanks and dots
70  //
71  while (--TempName >= FileName) {
72    if ((*TempName == L'.' || *TempName == L' ') && (TempName != SeparateDot)) {
73      //
74      // There exist extra blanks and dots
75      //
76      PossibleShortName = FALSE;
77    }
78  }
79
80  if (MainNameLen == 0) {
81    PossibleShortName = FALSE;
82  }
83
84  if (MainNameLen > FAT_MAIN_NAME_LEN) {
85    PossibleShortName = FALSE;
86    MainNameLen       = FAT_MAIN_NAME_LEN;
87  }
88
89  if (ExtendNameLen > FAT_EXTEND_NAME_LEN) {
90    PossibleShortName = FALSE;
91    ExtendNameLen     = FAT_EXTEND_NAME_LEN;
92  }
93
94  if (FatStrToFat (FileName, MainNameLen, File8Dot3Name)) {
95    PossibleShortName = FALSE;
96  }
97
98  if (FatStrToFat (ExtendName, ExtendNameLen, File8Dot3Name + FAT_MAIN_NAME_LEN)) {
99    PossibleShortName = FALSE;
100  }
101
102  return PossibleShortName;
103}
104
105/**
106
107  Trim the trailing blanks of fat name.
108
109  @param  Name                  - The Char8 string needs to be trimed.
110  @param  Len                   - The length of the fat name.
111
112  The real length of the fat name after the trailing blanks are trimmed.
113
114**/
115STATIC
116UINTN
117FatTrimAsciiTrailingBlanks (
118  IN CHAR8        *Name,
119  IN UINTN        Len
120  )
121{
122  while (Len > 0 && Name[Len - 1] == ' ') {
123    Len--;
124  }
125
126  return Len;
127}
128
129/**
130
131  Convert the ascii fat name to the unicode string and strip trailing spaces,
132  and if necessary, convert the unicode string to lower case.
133
134  @param  FatName               - The Char8 string needs to be converted.
135  @param  Len                   - The length of the fat name.
136  @param  LowerCase             - Indicate whether to convert the string to lower case.
137  @param  Str                   - The result of the convertion.
138
139**/
140VOID
141FatNameToStr (
142  IN  CHAR8            *FatName,
143  IN  UINTN            Len,
144  IN  UINTN            LowerCase,
145  OUT CHAR16           *Str
146  )
147{
148  //
149  // First, trim the trailing blanks
150  //
151  Len = FatTrimAsciiTrailingBlanks (FatName, Len);
152  //
153  // Convert fat string to unicode string
154  //
155  FatFatToStr (Len, FatName, Str);
156
157  //
158  // If the name is to be lower cased, do it now
159  //
160  if (LowerCase != 0) {
161    FatStrLwr (Str);
162  }
163}
164
165/**
166
167  This function generates 8Dot3 name from user specified name for a newly created file.
168
169  @param  Parent                - The parent directory.
170  @param  DirEnt                - The directory entry whose 8Dot3Name needs to be generated.
171
172**/
173VOID
174FatCreate8Dot3Name (
175  IN FAT_OFILE    *Parent,
176  IN FAT_DIRENT   *DirEnt
177  )
178{
179  CHAR8 *ShortName;
180  CHAR8 *ShortNameChar;
181  UINTN BaseTagLen;
182  UINTN Index;
183  UINTN Retry;
184  UINT8 Segment;
185  union {
186    UINT32  Crc;
187    struct HEX_DATA {
188      UINT8 Segment : HASH_VALUE_TAG_LEN;
189    } Hex[HASH_VALUE_TAG_LEN];
190  } HashValue;
191  //
192  // Make sure the whole directory has been loaded
193  //
194  ASSERT (Parent->ODir->EndOfDir);
195  ShortName = DirEnt->Entry.FileName;
196
197  //
198  // Trim trailing blanks of 8.3 name
199  //
200  BaseTagLen = FatTrimAsciiTrailingBlanks (ShortName, FAT_MAIN_NAME_LEN);
201  if (BaseTagLen > SPEC_BASE_TAG_LEN) {
202    BaseTagLen = SPEC_BASE_TAG_LEN;
203  }
204  //
205  // We first use the algorithm described by spec.
206  //
207  ShortNameChar     = ShortName + BaseTagLen;
208  *ShortNameChar++  = '~';
209  *ShortNameChar    = '1';
210  Retry = 0;
211  while (*FatShortNameHashSearch (Parent->ODir, ShortName) != NULL) {
212    *ShortNameChar = (CHAR8)(*ShortNameChar + 1);
213    if (++Retry == MAX_SPEC_RETRY) {
214      //
215      // We use new algorithm to generate 8.3 name
216      //
217      ASSERT (DirEnt->FileString != NULL);
218      gBS->CalculateCrc32 (DirEnt->FileString, StrSize (DirEnt->FileString), &HashValue.Crc);
219
220      if (BaseTagLen > HASH_BASE_TAG_LEN) {
221        BaseTagLen = HASH_BASE_TAG_LEN;
222      }
223
224      ShortNameChar = ShortName + BaseTagLen;
225      for (Index = 0; Index < HASH_VALUE_TAG_LEN; Index++) {
226        Segment = HashValue.Hex[Index].Segment;
227        if (Segment > 9) {
228          *ShortNameChar++ = (CHAR8)(Segment - 10 + 'A');
229        } else {
230          *ShortNameChar++ = (CHAR8)(Segment + '0');
231        }
232      }
233
234      *ShortNameChar++  = '~';
235      *ShortNameChar    = '1';
236    }
237  }
238}
239
240/**
241
242  Check the string is lower case or upper case
243  and it is used by fatname to dir entry count
244
245  @param Str                   - The string which needs to be checked.
246  @param InCaseFlag            - The input case flag which is returned when the string is lower case.
247
248  @retval OutCaseFlag           - The output case flag.
249
250**/
251STATIC
252UINT8
253FatCheckNameCase (
254  IN CHAR16           *Str,
255  IN UINT8            InCaseFlag
256  )
257{
258  CHAR16  Buffer[FAT_MAIN_NAME_LEN + 1 + FAT_EXTEND_NAME_LEN + 1];
259  UINT8   OutCaseFlag;
260
261  //
262  // Assume the case of input string is mixed
263  //
264  OutCaseFlag = FAT_CASE_MIXED;
265  //
266  // Lower case a copy of the string, if it matches the
267  // original then the string is lower case
268  //
269  StrCpyS (Buffer, ARRAY_SIZE (Buffer), Str);
270  FatStrLwr (Buffer);
271  if (StrCmp (Str, Buffer) == 0) {
272    OutCaseFlag = InCaseFlag;
273  }
274  //
275  // Upper case a copy of the string, if it matches the
276  // original then the string is upper case
277  //
278  StrCpyS (Buffer, ARRAY_SIZE (Buffer), Str);
279  FatStrUpr (Buffer);
280  if (StrCmp (Str, Buffer) == 0) {
281    OutCaseFlag = 0;
282  }
283
284  return OutCaseFlag;
285}
286
287/**
288
289  Set the caseflag value for the directory entry.
290
291  @param DirEnt                - The logical directory entry whose caseflag value is to be set.
292
293**/
294VOID
295FatSetCaseFlag (
296  IN FAT_DIRENT   *DirEnt
297  )
298{
299  CHAR16  LfnBuffer[FAT_MAIN_NAME_LEN + 1 + FAT_EXTEND_NAME_LEN + 1];
300  CHAR16  *TempCharPtr;
301  CHAR16  *ExtendName;
302  CHAR16  *FileNameCharPtr;
303  UINT8   CaseFlag;
304
305  ExtendName      = NULL;
306  TempCharPtr     = LfnBuffer;
307  FileNameCharPtr = DirEnt->FileString;
308  ASSERT (StrSize (DirEnt->FileString) <= sizeof (LfnBuffer));
309  while ((*TempCharPtr = *FileNameCharPtr) != 0) {
310    if (*TempCharPtr == L'.') {
311      ExtendName = TempCharPtr;
312    }
313
314    TempCharPtr++;
315    FileNameCharPtr++;
316  }
317
318  CaseFlag = 0;
319  if (ExtendName != NULL) {
320    *ExtendName = 0;
321    ExtendName++;
322    CaseFlag = (UINT8)(CaseFlag | FatCheckNameCase (ExtendName, FAT_CASE_EXT_LOWER));
323  }
324
325  CaseFlag = (UINT8)(CaseFlag | FatCheckNameCase (LfnBuffer, FAT_CASE_NAME_LOWER));
326  if ((CaseFlag & FAT_CASE_MIXED) == 0) {
327    //
328    // We just need one directory entry to store this file name entry
329    //
330    DirEnt->Entry.CaseFlag = CaseFlag;
331  } else {
332    //
333    // We need one extra directory entry to store the mixed case entry
334    //
335    DirEnt->Entry.CaseFlag = 0;
336    DirEnt->EntryCount++;
337  }
338}
339
340/**
341
342  Convert the 8.3 ASCII fat name to cased Unicode string according to case flag.
343
344  @param  DirEnt                - The corresponding directory entry.
345  @param  FileString            - The output Unicode file name.
346  @param  FileStringMax           The max length of FileString.
347
348**/
349VOID
350FatGetFileNameViaCaseFlag (
351  IN     FAT_DIRENT   *DirEnt,
352  IN OUT CHAR16       *FileString,
353  IN     UINTN        FileStringMax
354  )
355{
356  UINT8   CaseFlag;
357  CHAR8   *File8Dot3Name;
358  CHAR16  TempExt[1 + FAT_EXTEND_NAME_LEN + 1];
359  //
360  // Store file extension like ".txt"
361  //
362  CaseFlag      = DirEnt->Entry.CaseFlag;
363  File8Dot3Name = DirEnt->Entry.FileName;
364
365  FatNameToStr (File8Dot3Name, FAT_MAIN_NAME_LEN, CaseFlag & FAT_CASE_NAME_LOWER, FileString);
366  FatNameToStr (File8Dot3Name + FAT_MAIN_NAME_LEN, FAT_EXTEND_NAME_LEN, CaseFlag & FAT_CASE_EXT_LOWER, &TempExt[1]);
367  if (TempExt[1] != 0) {
368    TempExt[0] = L'.';
369    StrCatS (FileString, FileStringMax, TempExt);
370  }
371}
372
373/**
374
375  Get the Check sum for a short name.
376
377  @param  ShortNameString       - The short name for a file.
378
379  @retval Sum                   - UINT8 checksum.
380
381**/
382UINT8
383FatCheckSum (
384  IN CHAR8  *ShortNameString
385  )
386{
387  UINTN ShortNameLen;
388  UINT8 Sum;
389  Sum = 0;
390  for (ShortNameLen = FAT_NAME_LEN; ShortNameLen != 0; ShortNameLen--) {
391    Sum = (UINT8)((((Sum & 1) != 0) ? 0x80 : 0) + (Sum >> 1) + *ShortNameString++);
392  }
393
394  return Sum;
395}
396
397/**
398
399  Takes Path as input, returns the next name component
400  in Name, and returns the position after Name (e.g., the
401  start of the next name component)
402
403  @param  Path                  - The path of one file.
404  @param  Name                  - The next name component in Path.
405
406  The position after Name in the Path
407
408**/
409CHAR16 *
410FatGetNextNameComponent (
411  IN  CHAR16      *Path,
412  OUT CHAR16      *Name
413  )
414{
415  while (*Path != 0 && *Path != PATH_NAME_SEPARATOR) {
416    *Name++ = *Path++;
417  }
418  *Name = 0;
419  //
420  // Get off of trailing path name separator
421  //
422  while (*Path == PATH_NAME_SEPARATOR) {
423    Path++;
424  }
425
426  return Path;
427}
428
429/**
430
431  Check whether the IFileName is valid long file name. If the IFileName is a valid
432  long file name, then we trim the possible leading blanks and leading/trailing dots.
433  the trimmed filename is stored in OutputFileName
434
435  @param  InputFileName         - The input file name.
436  @param  OutputFileName        - The output file name.
437
438  @retval TRUE                  - The InputFileName is a valid long file name.
439  @retval FALSE                 - The InputFileName is not a valid long file name.
440
441**/
442BOOLEAN
443FatFileNameIsValid (
444  IN  CHAR16  *InputFileName,
445  OUT CHAR16  *OutputFileName
446  )
447{
448  CHAR16  *TempNamePointer;
449  CHAR16  TempChar;
450  //
451  // Trim Leading blanks
452  //
453  while (*InputFileName == L' ') {
454    InputFileName++;
455  }
456
457  TempNamePointer = OutputFileName;
458  while (*InputFileName != 0) {
459    *TempNamePointer++ = *InputFileName++;
460  }
461  //
462  // Trim Trailing blanks and dots
463  //
464  while (TempNamePointer > OutputFileName) {
465    TempChar = *(TempNamePointer - 1);
466    if (TempChar != L' ' && TempChar != L'.') {
467      break;
468    }
469
470    TempNamePointer--;
471  }
472
473  *TempNamePointer = 0;
474
475  //
476  // Per FAT Spec the file name should meet the following criteria:
477  //   C1. Length (FileLongName) <= 255
478  //   C2. Length (X:FileFullPath<NUL>) <= 260
479  // Here we check C1.
480  //
481  if (TempNamePointer - OutputFileName > EFI_FILE_STRING_LENGTH) {
482    return FALSE;
483  }
484  //
485  // See if there is any illegal characters within the name
486  //
487  do {
488    if (*OutputFileName < 0x20 ||
489        *OutputFileName == '\"' ||
490        *OutputFileName == '*' ||
491        *OutputFileName == '/' ||
492        *OutputFileName == ':' ||
493        *OutputFileName == '<' ||
494        *OutputFileName == '>' ||
495        *OutputFileName == '?' ||
496        *OutputFileName == '\\' ||
497        *OutputFileName == '|'
498        ) {
499      return FALSE;
500    }
501
502    OutputFileName++;
503  } while (*OutputFileName != 0);
504  return TRUE;
505}
506