Home | History | Annotate | Download | only in Utility
      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
     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 *
    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
    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
    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
    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
    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
    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