1 /** @file
2     Device Abstraction: Path manipulation utilities.
3 
4     Copyright (c) 2011, Intel Corporation. All rights reserved.<BR>
5     This program and the accompanying materials are licensed and made available under
6     the terms and conditions of the BSD License that accompanies this distribution.
7     The full text of the license may be found at
8     http://opensource.org/licenses/bsd-license.php.
9 
10     THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11     WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 **/
13 #include  <Library/BaseLib.h>
14 
15 #include  <LibConfig.h>
16 
17 #include  <errno.h>
18 #include  <stdlib.h>
19 #include  <wchar.h>
20 #include  <wctype.h>
21 #include  <kfile.h>
22 #include  <Device/Device.h>
23 #include  <MainData.h>
24 
25 /** Identify the type of path pointed to by Path.
26 
27     Paths are classified based upon their initial character sequences.
28       ^\\       Absolute Path
29       ^\.       Relative Path
30       ^[^:\\]:  Mapping Path
31       .*        Relative Path
32 
33     Mapping paths are broken into two parts at the ':'.  The part to the left of the ':'
34     is the Map Name, pointed to by Path, and the part to the right of the ':' is pointed
35     to by NewPath.
36 
37     If Path was not a Mapping Path, then NewPath is set to Path.
38 
39     @param[in]    Path      Pointer to the path to be classified.
40     @param[out]   NewPath   Pointer to the path portion of a mapping path.
41     @param[out]   Length    Length of the Map Name portion of the path.
42 
43     @retval PathAbsolute  Path is an absolute path. NewPath points to the first '\'.
44     @retval PathRelative  Path is a relative path. NewPath = Path.
45     @retval PathMapping   Path is a mapping path.  NewPath points to the character following ':'.
46     @retval PathError     Path is NULL.
47 **/
48 PATH_CLASS
49 EFIAPI
ClassifyPath(IN wchar_t * Path,OUT wchar_t ** NewPath,OUT int * const Length)50 ClassifyPath(
51   IN  wchar_t    *        Path,
52   OUT wchar_t   **        NewPath,
53   OUT int        * const  Length
54   )
55 {
56   size_t    MapLen;
57 
58   if(Path == NULL) {
59     return PathError;   // Bad parameter
60   }
61   if(NewPath != NULL) {
62     *NewPath = Path;    // Setup default condition
63   }
64   if((*Path == L'\\') || (*Path == L'\0')) {
65     return PathAbsolute;
66   }
67   if(*Path == L'.') {
68     return PathRelative;
69   }
70   /* The easy stuff has been done, now see if this is a mapping path.
71       See if there is a ':' in Path that isn't the first character and is before
72       any '\\' characters.
73   */
74   MapLen = wcscspn(Path, L"\\:");
75   if(Length != NULL) {
76     *Length = (int)MapLen;
77   }
78   /*  MapLen == 0       means that the first character is a ':'
79              == PathLen means that there are no '\\' or ':'
80       Otherwise, Path[MapLen] == ':'  for a mapping path
81                               or '\\' for a relative path.
82   */
83   if(MapLen == 0) {
84     return PathError;
85   }
86   if(Path[MapLen] == L':') {
87     if(NewPath != NULL) {
88       *NewPath = &Path[MapLen + 1];   // Point to character after then ':'.  Might be '\0'.
89     }
90     return PathMapping;
91   }
92   return PathRelative;
93 }
94 
95 /*  Normalize a narrow-character path and produce a wide-character path
96     that has forward slashes replaced with backslashes.
97     Backslashes are directory separators in UEFI File Paths.
98 
99     It is the caller's responsibility to eventually free() the returned buffer.
100 
101     @param[in]    path    A pointer to the narrow-character path to be normalized.
102 
103     @return     A pointer to a buffer containing the normalized, wide-character, path.
104 */
105 wchar_t *
NormalizePath(const char * path)106 NormalizePath( const char *path)
107 {
108   wchar_t  *temp;
109   wchar_t  *OldPath;
110   wchar_t  *NewPath;
111   size_t    Length;
112 
113   OldPath = AsciiStrToUnicodeStr(path, gMD->UString);
114   Length  = wcslen(OldPath) + 1;
115 
116   NewPath = calloc(Length, sizeof(wchar_t));
117   if(NewPath != NULL) {
118     temp = NewPath;
119     for( ; *OldPath; ++OldPath) {
120       if(*OldPath == L'/') {
121         *temp = L'\\';
122       }
123       else {
124         *temp = *OldPath;
125       }
126       ++temp;
127     }
128   }
129   else {
130     errno     = ENOMEM;
131     EFIerrno  = RETURN_OUT_OF_RESOURCES;
132   }
133   return NewPath;
134 }
135 
136 /** Process a wide character string representing a Mapping Path and extract the instance number.
137 
138     The instance number is the sequence of decimal digits immediately to the left
139     of the ":" in the Map Name portion of a Mapping Path.
140 
141     This function is called with a pointer to beginning of the Map Name.
142     Thus Path[Length] must be a ':' and Path[Length - 1] must be a decimal digit.
143     If either of these are not true, an instance value of 0 is returned.
144 
145     If Path is NULL, an instance value of 0 is returned.
146 
147     @param[in]  Path    Points to the beginning of a Mapping Path
148     @param[in]  Length  Number of valid characters to the left of the ':'
149 
150     @return   Returns either 0 or the value of the contiguous digits to the left of the ':'.
151 **/
152 int
153 EFIAPI
PathInstance(const wchar_t * Path,int Length)154 PathInstance(
155   const wchar_t  *Path,
156         int       Length
157   )
158 {
159   wchar_t    *temp;
160   int         instance    = 0;
161 
162   if((Path != NULL) && (Path[Length] == L':') && (Length > 0)) {
163     for(temp = __UNCONST(&Path[Length - 1]); Length > 0; --Length) {
164       if(!iswdigit(*temp)) {
165         break;
166       }
167       --temp;
168     }
169     instance = (int)wcstol(temp+1, NULL, 10);
170   }
171   return instance;
172 }
173 
174 /** Transform a relative path into an absolute path.
175 
176     If Path is NULL, return NULL.
177     Otherwise, pre-pend the CWD to Path then process the resulting path to:
178       - Replace "/./" with "/"
179       - Replace "/<dirname>/../" with "/"
180       - Do not allow one to back up past the root, "/"
181 
182     Also sets the Current Working Device to the Root Device.
183 
184     Path must be a previously allocated buffer.  PathAdjust will
185     allocate a new buffer to hold the results of the transformation
186     and free Path.  A pointer to the newly allocated buffer is returned.
187 
188     @param[in]  Path    A pointer to the path to be transformed.  This buffer
189                         will always be freed.
190 
191     @return   A pointer to a buffer containing the transformed path.
192 **/
193 wchar_t *
194 EFIAPI
PathAdjust(wchar_t * Path)195 PathAdjust(
196   wchar_t *Path
197   )
198 {
199   wchar_t    *NewPath;
200 
201   NewPath = calloc(PATH_MAX, sizeof(wchar_t));
202   if(NewPath != NULL) {
203     wmemcpy(NewPath, Path, PATH_MAX);
204   }
205   else {
206     errno = ENOMEM;
207   }
208   free(Path);
209   return NewPath;
210 }
211 
212 /** Replace the leading portion of Path with any aliases.
213 
214     Aliases are read from /etc/fstab.  If there is an associated device, the
215     Current Working Device is set to that device.
216 
217     Path must be a previously allocated buffer.  PathAlias will
218     allocate a new buffer to hold the results of the transformation
219     then free Path.  A pointer to the newly allocated buffer is returned.
220 
221     @param[in]    Path    A pointer to the original, unaliased, path.  This
222                           buffer is always freed.
223     @param[out]   Node    Filled in with a pointer to the Device Node describing
224                           the device abstraction associated with this path.
225 
226     @return     A pointer to a buffer containing the aliased path.
227 **/
228 wchar_t *
229 EFIAPI
PathAlias(wchar_t * Path,DeviceNode ** Node)230 PathAlias(
231   wchar_t      *Path,
232   DeviceNode  **Node
233   )
234 {
235   wchar_t    *NewPath;
236 
237   NewPath = calloc(PATH_MAX, sizeof(wchar_t));
238   if(NewPath != NULL) {
239     wmemcpy(NewPath, Path, PATH_MAX);
240   }
241   else {
242     errno = ENOMEM;
243   }
244   free(Path);
245   *Node = NULL;
246   return NewPath;
247 }
248 
249 /** Parse a path producing the target device, device instance, and file path.
250 
251     It is the caller's responsibility to free() FullPath and MapPath when they
252     are no longer needed.
253 
254     @param[in]    path
255     @param[out]   FullPath
256     @param[out]   DevNode
257     @param[out]   Which
258     @param[out]   MapPath       OPTIONAL.  If not NULL, it points to the place to save a pointer
259                                 to the extracted map name.  If the path didn't have a map name,
260                                 then *MapPath is set to NULL.
261 
262     @retval   RETURN_SUCCESS              The path was parsed successfully.
263     @retval   RETURN_NOT_FOUND            The path does not map to a valid device.
264     @retval   RETURN_OUT_OF_RESOURCES     Insufficient memory to calloc a MapName buffer.
265                                           The errno variable is set to ENOMEM.
266     @retval   RETURN_INVALID_PARAMETER    The path parameter is not valid.
267                                           The errno variable is set to EINVAL.
268 **/
269 RETURN_STATUS
270 EFIAPI
ParsePath(IN const char * path,OUT wchar_t ** FullPath,OUT DeviceNode ** DevNode,OUT int * Which,OUT wchar_t ** MapPath)271 ParsePath(
272   IN    const char   *path,
273   OUT   wchar_t     **FullPath,
274   OUT   DeviceNode  **DevNode,
275   OUT   int          *Which,
276   OUT   wchar_t     **MapPath
277   )
278 {
279   int                 MapLen;
280   PATH_CLASS          PathClass;
281   wchar_t            *NewPath;
282   wchar_t            *WPath     = NULL;
283   wchar_t            *MPath     = NULL;
284   DeviceNode         *Node      = NULL;
285   RETURN_STATUS       Status    = RETURN_NOT_FOUND;
286   int                 Instance  = 0;
287   BOOLEAN             ReMapped;
288 
289   ReMapped  = FALSE;
290 
291   // Convert name from MBCS to WCS and change '/' to '\\'
292   WPath = NormalizePath( path);
293   PathClass = ClassifyPath(WPath, &NewPath, &MapLen);
294 
295 reclassify:
296   switch(PathClass) {
297     case PathMapping:
298       if(!ReMapped) {
299         if((NewPath == NULL) || (*NewPath == L'\0')) { /* Nothing after the ':' */
300           PathClass = PathAbsolute;
301         }
302         else {
303           Instance = PathInstance(WPath, MapLen);
304           PathClass = ClassifyPath(NewPath, NULL, NULL);
305         }
306         ReMapped = TRUE;
307         if(WPath[MapLen] == L':') {
308           // Get the Map Name, including the trailing ':'. */
309           MPath = calloc(MapLen+2, sizeof(wchar_t));
310           if(MPath != NULL) {
311             wmemcpy(MPath, WPath, MapLen+1);
312           }
313           else {
314             errno = ENOMEM;
315             Status = RETURN_OUT_OF_RESOURCES;
316             break;    // Exit the switch(PathClass) statement.
317           }
318         }
319         if(WPath != NewPath) {
320           /* Shift the RHS of the path down to the start of the buffer. */
321           wmemmove(WPath, NewPath, wcslen(NewPath)+1);
322           NewPath = WPath;
323         }
324         goto reclassify;
325       }
326       /*  Fall through to PathError if Remapped.
327           This means that the path looked like "foo:bar:something".
328       */
329 
330     case PathError:
331       errno = EINVAL;
332       Status = RETURN_INVALID_PARAMETER;
333       break;
334 
335     case PathRelative:
336       /*  Transform a relative path into an Absolute path.
337           Prepends CWD and handles ./ and ../ entries.
338           It is the caller's responsibility to free the space
339           allocated to WPath.
340       */
341       WPath = PathAdjust(NewPath);    // WPath was malloc()ed by PathAdjust
342 
343     case PathAbsolute:
344       /*  Perform any path aliasing.  For example: /dev/foo -> { node.foo, "" }
345           The current volume and directory are updated in the path as needed.
346           It is the caller's responsibility to free the space
347           allocated to WPath.
348       */
349     Status = RETURN_SUCCESS;
350       WPath = PathAlias(WPath, &Node);       // PathAlias frees its argument and malloc()s a new one.
351       break;
352   }
353   if(!RETURN_ERROR(Status)) {
354     *FullPath = WPath;
355     *Which    = Instance;
356     if(MapPath != NULL) {
357       *MapPath  = MPath;
358     }
359     else if(MPath != NULL) {
360       free(MPath);    /* Caller doesn't want it so let MPath go free */
361     }
362 
363     /*  At this point, WPath is an absolute path,
364         MPath is either NULL or points to the Map Name,
365         and Instance is the instance number.
366     */
367     if(MPath == NULL) {
368       /* This is NOT a mapped path. */
369       if(Node == NULL) {
370         Node = daDefaultDevice;
371       }
372       if(Node != NULL) {
373         Status = RETURN_SUCCESS;
374       }
375       else {
376         Status = RETURN_NOT_FOUND;
377       }
378     }
379     else {
380       /* This is a mapped path. */
381       Status = __DevSearch( MPath, NULL, &Node);
382       if(Status == RETURN_NOT_FOUND) {
383         Node = daDefaultDevice;
384 
385         if(Node != NULL) {
386           Status = RETURN_SUCCESS;
387         }
388       }
389     }
390     if(DevNode != NULL) {
391       *DevNode = Node;
392     }
393   }
394   return Status;
395 }
396 
397 /**
398   Parses a normalized wide character path and returns a pointer to the entry
399   following the last \.  If a \ is not found in the path the return value will
400   be the same as the input value.  All error conditions return NULL.
401 
402   The behavior when passing in a path that has not been normalized is undefined.
403 
404   @param  Path - A pointer to a wide character string containing a path to a
405                  directory or a file.
406 
407   @return Pointer to the file name or terminal directory.  NULL if an error is
408           detected.
409 **/
410 wchar_t *
411 EFIAPI
GetFileNameFromPath(const wchar_t * Path)412 GetFileNameFromPath (
413   const wchar_t   *Path
414   )
415 {
416   wchar_t   *Tail;
417 
418   if (Path == NULL) {
419     return NULL;
420   }
421 
422   Tail = wcsrchr(Path, L'\\');
423   if(Tail == NULL) {
424     Tail = (wchar_t *) Path;
425   } else {
426     // Move to the next character after the '\\' to get the file name.
427     Tail++;
428   }
429 
430   return Tail;
431 }
432