| /** @file | |
| Device Abstraction: Path manipulation utilities. | |
| Copyright (c) 2011, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials are licensed and made available under | |
| the terms and conditions of the BSD License that accompanies this distribution. | |
| The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php. | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include <Library/BaseLib.h> | |
| #include <LibConfig.h> | |
| #include <errno.h> | |
| #include <stdlib.h> | |
| #include <wchar.h> | |
| #include <wctype.h> | |
| #include <kfile.h> | |
| #include <Device/Device.h> | |
| #include <MainData.h> | |
| /** Identify the type of path pointed to by Path. | |
| Paths are classified based upon their initial character sequences. | |
| ^\\ Absolute Path | |
| ^\. Relative Path | |
| ^[^:\\]: Mapping Path | |
| .* Relative Path | |
| Mapping paths are broken into two parts at the ':'. The part to the left of the ':' | |
| is the Map Name, pointed to by Path, and the part to the right of the ':' is pointed | |
| to by NewPath. | |
| If Path was not a Mapping Path, then NewPath is set to Path. | |
| @param[in] Path Pointer to the path to be classified. | |
| @param[out] NewPath Pointer to the path portion of a mapping path. | |
| @param[out] Length Length of the Map Name portion of the path. | |
| @retval PathAbsolute Path is an absolute path. NewPath points to the first '\'. | |
| @retval PathRelative Path is a relative path. NewPath = Path. | |
| @retval PathMapping Path is a mapping path. NewPath points to the character following ':'. | |
| @retval PathError Path is NULL. | |
| **/ | |
| PATH_CLASS | |
| EFIAPI | |
| ClassifyPath( | |
| IN wchar_t * Path, | |
| OUT wchar_t ** NewPath, | |
| OUT int * const Length | |
| ) | |
| { | |
| size_t MapLen; | |
| if(Path == NULL) { | |
| return PathError; // Bad parameter | |
| } | |
| if(NewPath != NULL) { | |
| *NewPath = Path; // Setup default condition | |
| } | |
| if((*Path == L'\\') || (*Path == L'\0')) { | |
| return PathAbsolute; | |
| } | |
| if(*Path == L'.') { | |
| return PathRelative; | |
| } | |
| /* The easy stuff has been done, now see if this is a mapping path. | |
| See if there is a ':' in Path that isn't the first character and is before | |
| any '\\' characters. | |
| */ | |
| MapLen = wcscspn(Path, L"\\:"); | |
| if(Length != NULL) { | |
| *Length = (int)MapLen; | |
| } | |
| /* MapLen == 0 means that the first character is a ':' | |
| == PathLen means that there are no '\\' or ':' | |
| Otherwise, Path[MapLen] == ':' for a mapping path | |
| or '\\' for a relative path. | |
| */ | |
| if(MapLen == 0) { | |
| return PathError; | |
| } | |
| if(Path[MapLen] == L':') { | |
| if(NewPath != NULL) { | |
| *NewPath = &Path[MapLen + 1]; // Point to character after then ':'. Might be '\0'. | |
| } | |
| return PathMapping; | |
| } | |
| return PathRelative; | |
| } | |
| /* Normalize a narrow-character path and produce a wide-character path | |
| that has forward slashes replaced with backslashes. | |
| Backslashes are directory separators in UEFI File Paths. | |
| It is the caller's responsibility to eventually free() the returned buffer. | |
| @param[in] path A pointer to the narrow-character path to be normalized. | |
| @return A pointer to a buffer containing the normalized, wide-character, path. | |
| */ | |
| wchar_t * | |
| NormalizePath( const char *path) | |
| { | |
| wchar_t *temp; | |
| wchar_t *OldPath; | |
| wchar_t *NewPath; | |
| size_t Length; | |
| OldPath = AsciiStrToUnicodeStr(path, gMD->UString); | |
| Length = wcslen(OldPath) + 1; | |
| NewPath = calloc(Length, sizeof(wchar_t)); | |
| if(NewPath != NULL) { | |
| temp = NewPath; | |
| for( ; *OldPath; ++OldPath) { | |
| if(*OldPath == L'/') { | |
| *temp = L'\\'; | |
| } | |
| else { | |
| *temp = *OldPath; | |
| } | |
| ++temp; | |
| } | |
| } | |
| else { | |
| errno = ENOMEM; | |
| EFIerrno = RETURN_OUT_OF_RESOURCES; | |
| } | |
| return NewPath; | |
| } | |
| /** Process a wide character string representing a Mapping Path and extract the instance number. | |
| The instance number is the sequence of decimal digits immediately to the left | |
| of the ":" in the Map Name portion of a Mapping Path. | |
| This function is called with a pointer to beginning of the Map Name. | |
| Thus Path[Length] must be a ':' and Path[Length - 1] must be a decimal digit. | |
| If either of these are not true, an instance value of 0 is returned. | |
| If Path is NULL, an instance value of 0 is returned. | |
| @param[in] Path Points to the beginning of a Mapping Path | |
| @param[in] Length Number of valid characters to the left of the ':' | |
| @return Returns either 0 or the value of the contiguous digits to the left of the ':'. | |
| **/ | |
| int | |
| EFIAPI | |
| PathInstance( | |
| const wchar_t *Path, | |
| int Length | |
| ) | |
| { | |
| wchar_t *temp; | |
| int instance = 0; | |
| if((Path != NULL) && (Path[Length] == L':') && (Length > 0)) { | |
| for(temp = __UNCONST(&Path[Length - 1]); Length > 0; --Length) { | |
| if(!iswdigit(*temp)) { | |
| break; | |
| } | |
| --temp; | |
| } | |
| instance = (int)wcstol(temp+1, NULL, 10); | |
| } | |
| return instance; | |
| } | |
| /** Transform a relative path into an absolute path. | |
| If Path is NULL, return NULL. | |
| Otherwise, pre-pend the CWD to Path then process the resulting path to: | |
| - Replace "/./" with "/" | |
| - Replace "/<dirname>/../" with "/" | |
| - Do not allow one to back up past the root, "/" | |
| Also sets the Current Working Device to the Root Device. | |
| Path must be a previously allocated buffer. PathAdjust will | |
| allocate a new buffer to hold the results of the transformation | |
| and free Path. A pointer to the newly allocated buffer is returned. | |
| @param[in] Path A pointer to the path to be transformed. This buffer | |
| will always be freed. | |
| @return A pointer to a buffer containing the transformed path. | |
| **/ | |
| wchar_t * | |
| EFIAPI | |
| PathAdjust( | |
| wchar_t *Path | |
| ) | |
| { | |
| wchar_t *NewPath; | |
| NewPath = calloc(PATH_MAX, sizeof(wchar_t)); | |
| if(NewPath != NULL) { | |
| wmemcpy(NewPath, Path, PATH_MAX); | |
| } | |
| else { | |
| errno = ENOMEM; | |
| } | |
| free(Path); | |
| return NewPath; | |
| } | |
| /** Replace the leading portion of Path with any aliases. | |
| Aliases are read from /etc/fstab. If there is an associated device, the | |
| Current Working Device is set to that device. | |
| Path must be a previously allocated buffer. PathAlias will | |
| allocate a new buffer to hold the results of the transformation | |
| then free Path. A pointer to the newly allocated buffer is returned. | |
| @param[in] Path A pointer to the original, unaliased, path. This | |
| buffer is always freed. | |
| @param[out] Node Filled in with a pointer to the Device Node describing | |
| the device abstraction associated with this path. | |
| @return A pointer to a buffer containing the aliased path. | |
| **/ | |
| wchar_t * | |
| EFIAPI | |
| PathAlias( | |
| wchar_t *Path, | |
| DeviceNode **Node | |
| ) | |
| { | |
| wchar_t *NewPath; | |
| NewPath = calloc(PATH_MAX, sizeof(wchar_t)); | |
| if(NewPath != NULL) { | |
| wmemcpy(NewPath, Path, PATH_MAX); | |
| } | |
| else { | |
| errno = ENOMEM; | |
| } | |
| free(Path); | |
| *Node = NULL; | |
| return NewPath; | |
| } | |
| /** Parse a path producing the target device, device instance, and file path. | |
| It is the caller's responsibility to free() FullPath and MapPath when they | |
| are no longer needed. | |
| @param[in] path | |
| @param[out] FullPath | |
| @param[out] DevNode | |
| @param[out] Which | |
| @param[out] MapPath OPTIONAL. If not NULL, it points to the place to save a pointer | |
| to the extracted map name. If the path didn't have a map name, | |
| then *MapPath is set to NULL. | |
| @retval RETURN_SUCCESS The path was parsed successfully. | |
| @retval RETURN_NOT_FOUND The path does not map to a valid device. | |
| @retval RETURN_OUT_OF_RESOURCES Insufficient memory to calloc a MapName buffer. | |
| The errno variable is set to ENOMEM. | |
| @retval RETURN_INVALID_PARAMETER The path parameter is not valid. | |
| The errno variable is set to EINVAL. | |
| **/ | |
| RETURN_STATUS | |
| EFIAPI | |
| ParsePath( | |
| IN const char *path, | |
| OUT wchar_t **FullPath, | |
| OUT DeviceNode **DevNode, | |
| OUT int *Which, | |
| OUT wchar_t **MapPath | |
| ) | |
| { | |
| int MapLen; | |
| PATH_CLASS PathClass; | |
| wchar_t *NewPath; | |
| wchar_t *WPath = NULL; | |
| wchar_t *MPath = NULL; | |
| DeviceNode *Node = NULL; | |
| RETURN_STATUS Status = RETURN_NOT_FOUND; | |
| int Instance = 0; | |
| BOOLEAN ReMapped; | |
| ReMapped = FALSE; | |
| // Convert name from MBCS to WCS and change '/' to '\\' | |
| WPath = NormalizePath( path); | |
| PathClass = ClassifyPath(WPath, &NewPath, &MapLen); | |
| reclassify: | |
| switch(PathClass) { | |
| case PathMapping: | |
| if(!ReMapped) { | |
| if((NewPath == NULL) || (*NewPath == L'\0')) { /* Nothing after the ':' */ | |
| PathClass = PathAbsolute; | |
| } | |
| else { | |
| Instance = PathInstance(WPath, MapLen); | |
| PathClass = ClassifyPath(NewPath, NULL, NULL); | |
| } | |
| ReMapped = TRUE; | |
| if(WPath[MapLen] == L':') { | |
| // Get the Map Name, including the trailing ':'. */ | |
| MPath = calloc(MapLen+2, sizeof(wchar_t)); | |
| if(MPath != NULL) { | |
| wmemcpy(MPath, WPath, MapLen+1); | |
| } | |
| else { | |
| errno = ENOMEM; | |
| Status = RETURN_OUT_OF_RESOURCES; | |
| break; // Exit the switch(PathClass) statement. | |
| } | |
| } | |
| if(WPath != NewPath) { | |
| /* Shift the RHS of the path down to the start of the buffer. */ | |
| wmemmove(WPath, NewPath, wcslen(NewPath)+1); | |
| NewPath = WPath; | |
| } | |
| goto reclassify; | |
| } | |
| /* Fall through to PathError if Remapped. | |
| This means that the path looked like "foo:bar:something". | |
| */ | |
| case PathError: | |
| errno = EINVAL; | |
| Status = RETURN_INVALID_PARAMETER; | |
| break; | |
| case PathRelative: | |
| /* Transform a relative path into an Absolute path. | |
| Prepends CWD and handles ./ and ../ entries. | |
| It is the caller's responsibility to free the space | |
| allocated to WPath. | |
| */ | |
| WPath = PathAdjust(NewPath); // WPath was malloc()ed by PathAdjust | |
| case PathAbsolute: | |
| /* Perform any path aliasing. For example: /dev/foo -> { node.foo, "" } | |
| The current volume and directory are updated in the path as needed. | |
| It is the caller's responsibility to free the space | |
| allocated to WPath. | |
| */ | |
| Status = RETURN_SUCCESS; | |
| WPath = PathAlias(WPath, &Node); // PathAlias frees its argument and malloc()s a new one. | |
| break; | |
| } | |
| if(!RETURN_ERROR(Status)) { | |
| *FullPath = WPath; | |
| *Which = Instance; | |
| if(MapPath != NULL) { | |
| *MapPath = MPath; | |
| } | |
| else if(MPath != NULL) { | |
| free(MPath); /* Caller doesn't want it so let MPath go free */ | |
| } | |
| /* At this point, WPath is an absolute path, | |
| MPath is either NULL or points to the Map Name, | |
| and Instance is the instance number. | |
| */ | |
| if(MPath == NULL) { | |
| /* This is NOT a mapped path. */ | |
| if(Node == NULL) { | |
| Node = daDefaultDevice; | |
| } | |
| if(Node != NULL) { | |
| Status = RETURN_SUCCESS; | |
| } | |
| else { | |
| Status = RETURN_NOT_FOUND; | |
| } | |
| } | |
| else { | |
| /* This is a mapped path. */ | |
| Status = __DevSearch( MPath, NULL, &Node); | |
| if(Status == RETURN_NOT_FOUND) { | |
| Node = daDefaultDevice; | |
| if(Node != NULL) { | |
| Status = RETURN_SUCCESS; | |
| } | |
| } | |
| } | |
| if(DevNode != NULL) { | |
| *DevNode = Node; | |
| } | |
| } | |
| return Status; | |
| } | |
| /** | |
| Parses a normalized wide character path and returns a pointer to the entry | |
| following the last \. If a \ is not found in the path the return value will | |
| be the same as the input value. All error conditions return NULL. | |
| The behavior when passing in a path that has not been normalized is undefined. | |
| @param Path - A pointer to a wide character string containing a path to a | |
| directory or a file. | |
| @return Pointer to the file name or terminal directory. NULL if an error is | |
| detected. | |
| **/ | |
| wchar_t * | |
| EFIAPI | |
| GetFileNameFromPath ( | |
| const wchar_t *Path | |
| ) | |
| { | |
| wchar_t *Tail; | |
| if (Path == NULL) { | |
| return NULL; | |
| } | |
| Tail = wcsrchr(Path, L'\\'); | |
| if(Tail == NULL) { | |
| Tail = (wchar_t *) Path; | |
| } else { | |
| // Move to the next character after the '\\' to get the file name. | |
| Tail++; | |
| } | |
| return Tail; | |
| } |