| // |
| // Copyright (c) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. |
| // |
| |
| /*++ |
| |
| |
| |
| Module Name: |
| |
| path.c |
| |
| Abstract: |
| |
| Implementation of path functions part of Windows runtime library. |
| |
| Revision History: |
| |
| |
| |
| --*/ |
| |
| #include "pal/palinternal.h" |
| #include "pal/dbgmsg.h" |
| #include "pal/file.h" |
| #include "pal/printfcpp.hpp" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <sys/param.h> |
| #include <errno.h> |
| #include <limits.h> |
| |
| SET_DEFAULT_DEBUG_CHANNEL(CRT); |
| |
| |
| /* ON_ERROR. A Helper macro for _?splitpath functions. */ |
| #define ON_ERROR if ( drive ) \ |
| {\ |
| drive[0] = 0;\ |
| }\ |
| if(dir)\ |
| {\ |
| dir[0] = 0;\ |
| }\ |
| if(fname)\ |
| {\ |
| fname[0] = 0;\ |
| }\ |
| if(ext)\ |
| {\ |
| ext[0] = 0;\ |
| }\ |
| goto done;\ |
| |
| /*++ |
| Function: |
| _wsplitpath |
| |
| See MSDN doc. |
| |
| Notes : |
| This implementation ignores drive letters as they should not be |
| present. If the drive argument is non-NULL, it always returns an empty |
| string. |
| File names in which the only period is at the beginning (like .bashrc, but |
| not .bashrc.bak), the file is treated as having no extension |
| (fname is ".bashrc", ext is "") |
| |
| --*/ |
| void |
| __cdecl |
| _wsplitpath( |
| const char16_t *dospath, |
| char16_t *drive, |
| char16_t *dir, |
| char16_t *fname, |
| char16_t *ext) |
| { |
| WCHAR path[_MAX_PATH+1]; |
| LPCWSTR slash_ptr = NULL; |
| LPCWSTR period_ptr = NULL; |
| INT size = 0; |
| |
| PERF_ENTRY(_wsplitpath); |
| ENTRY("_wsplitpath (path=%p (%S), drive=%p, dir=%p, fname=%p, ext=%p)\n", |
| dospath?dospath:W16_NULLSTRING, |
| dospath?dospath:W16_NULLSTRING, drive, dir, fname, ext); |
| |
| /* Do performance intensive error checking only in debug builds. |
| |
| NOTE: This function must fail predictably across all platforms. |
| Under Windows this function throw an access violation if NULL |
| was passed in as the value for path. |
| |
| */ |
| #if _DEBUG |
| if ( !dospath ) |
| { |
| ERROR( "path cannot be NULL!\n" ); |
| } |
| #endif |
| |
| if( lstrlenW( dospath ) >= _MAX_PATH ) |
| { |
| ERROR("Path length is > _MAX_PATH (%d)!\n", _MAX_PATH); |
| ON_ERROR; |
| } |
| |
| |
| PAL_wcscpy(path, dospath); |
| FILEDosToUnixPathW(path); |
| |
| /* no drive letters in the PAL */ |
| if( drive != NULL ) |
| { |
| drive[0] = 0; |
| } |
| |
| /* find last path separator char */ |
| slash_ptr = PAL_wcsrchr(path, '/'); |
| |
| if( slash_ptr == NULL ) |
| { |
| TRACE("No path separator in path\n"); |
| slash_ptr = path - 1; |
| } |
| /* find extension separator, if any */ |
| period_ptr = PAL_wcsrchr(path, '.'); |
| |
| /* make sure we only consider periods after the last path separator */ |
| if( period_ptr < slash_ptr ) |
| { |
| period_ptr = NULL; |
| } |
| |
| /* if the only period in the file is a leading period (denoting a hidden |
| file), don't treat what follows as an extension */ |
| if( period_ptr == slash_ptr+1 ) |
| { |
| period_ptr = NULL; |
| } |
| |
| if( period_ptr == NULL ) |
| { |
| TRACE("No extension in path\n"); |
| period_ptr = path + lstrlenW(path); |
| } |
| |
| size = slash_ptr - path + 1; |
| if( dir != NULL ) |
| { |
| INT i; |
| |
| if( (size + 1 ) > _MAX_DIR ) |
| { |
| ERROR("Directory component needs %d characters, _MAX_DIR is %d\n", |
| size+1, _MAX_DIR); |
| ON_ERROR; |
| } |
| |
| memcpy(dir, path, size*sizeof(WCHAR)); |
| dir[size] = 0; |
| |
| /* only allow / separators in returned path */ |
| i = 0; |
| while( dir[ i ] ) |
| { |
| if( dir[ i ] == '\\' ) |
| { |
| dir[i]='/'; |
| } |
| i++; |
| } |
| } |
| |
| size = period_ptr-slash_ptr-1; |
| if( fname != NULL ) |
| { |
| if( (size+1) > _MAX_FNAME ) |
| { |
| ERROR("Filename component needs %d characters, _MAX_FNAME is %d\n", |
| size+1, _MAX_FNAME); |
| ON_ERROR; |
| } |
| memcpy(fname, slash_ptr+1, size*sizeof(WCHAR)); |
| fname[size] = 0; |
| } |
| |
| size = 1 + lstrlenW( period_ptr ); |
| if( ext != NULL ) |
| { |
| if( size > _MAX_EXT ) |
| { |
| ERROR("Extension component needs %d characters, _MAX_EXT is %d\n", |
| size, _MAX_EXT); |
| ON_ERROR; |
| } |
| memcpy(ext, period_ptr, size*sizeof(WCHAR)); |
| ext[size-1] = 0; |
| } |
| |
| TRACE("Path components are '%S' '%S' '%S'\n", dir, fname, ext); |
| |
| done: |
| |
| LOGEXIT("_wsplitpath returns.\n"); |
| PERF_EXIT(_wsplitpath); |
| } |
| |
| |
| /*++ |
| Function: |
| _splitpath |
| |
| See description above for _wsplitpath. |
| |
| --*/ |
| void |
| __cdecl |
| _splitpath( |
| const char *path, |
| char *drive, |
| char *dir, |
| char *fname, |
| char *ext) |
| { |
| WCHAR w_path[_MAX_PATH]; |
| WCHAR w_dir[_MAX_DIR]; |
| WCHAR w_fname[_MAX_FNAME]; |
| WCHAR w_ext[_MAX_EXT]; |
| |
| PERF_ENTRY(_splitpath); |
| ENTRY("_splitpath (path=%p (%s), drive=%p, dir=%p, fname=%p, ext=%p)\n", |
| path?path:"NULL", |
| path?path:"NULL", drive, dir, fname, ext); |
| |
| /* Do performance intensive error checking only in debug builds. |
| |
| NOTE: This function must fail predictably across all platforms. |
| Under Windows this function throw an access violation if NULL |
| was passed in as the value for path. |
| |
| */ |
| #if _DEBUG |
| if ( !path ) |
| { |
| ERROR( "path cannot be NULL!\n" ); |
| } |
| |
| if( strlen( path ) >= _MAX_PATH ) |
| { |
| ERROR( "Path length is > _MAX_PATH (%d)!\n", _MAX_PATH); |
| } |
| #endif |
| |
| /* no drive letters in the PAL */ |
| if(drive) |
| { |
| drive[0] = '\0'; |
| } |
| |
| if(0 == MultiByteToWideChar(CP_ACP, 0, path, -1, w_path, _MAX_PATH)) |
| { |
| ASSERT("MultiByteToWideChar failed!\n"); |
| ON_ERROR; |
| } |
| |
| /* Call up to Unicode version; pass NULL for parameters the caller doesn't |
| care about */ |
| _wsplitpath(w_path, NULL, dir?w_dir:NULL, |
| fname?w_fname:NULL, ext?w_ext:NULL); |
| |
| /* Convert result back to MultiByte; report conversion errors but don't |
| stop because of them */ |
| |
| if(dir) |
| { |
| if(0 == WideCharToMultiByte(CP_ACP, 0, w_dir, -1, dir, _MAX_DIR, |
| NULL, NULL)) |
| { |
| ASSERT("WideCharToMultiByte failed!\n"); |
| ON_ERROR; |
| } |
| } |
| if(fname) |
| { |
| if(0 == WideCharToMultiByte(CP_ACP, 0, w_fname, -1, fname, _MAX_FNAME, |
| NULL, NULL)) |
| { |
| ASSERT("WideCharToMultiByte failed!\n"); |
| ON_ERROR; |
| } |
| } |
| if(ext) |
| { |
| if(0 == WideCharToMultiByte(CP_ACP, 0, w_ext, -1, ext, _MAX_EXT, |
| NULL, NULL)) |
| { |
| ASSERT("WideCharToMultiByte failed!\n"); |
| ON_ERROR; |
| } |
| } |
| |
| done: |
| LOGEXIT("_splitpath returns.\n"); |
| PERF_EXIT(_splitpath); |
| } |
| |
| |
| |
| /*++ |
| Function: |
| _makepath |
| |
| See MSDN doc. |
| |
| --*/ |
| void |
| __cdecl |
| _makepath( |
| char *path, |
| const char *drive, |
| const char *dir, |
| const char *fname, |
| const char *ext) |
| { |
| UINT Length = 0; |
| |
| PERF_ENTRY(_makepath); |
| ENTRY( "_makepath (path=%p, drive=%p (%s), dir=%p (%s), fname=%p (%s), ext=%p (%s))\n", |
| path, drive ? drive:"NULL", drive ? drive:"NULL", dir ? dir:"NULL", dir ? dir:"NULL", fname ? fname:"NULL", fname ? fname:"NULL", |
| ext ? ext:"NULL", |
| ext ? ext:"NULL"); |
| |
| path[ 0 ] = '\0'; |
| |
| /* According to the pal documentation, host operating systems that |
| don't support drive letters, the "drive" parameter must always be null. */ |
| if ( drive != NULL && drive[0] != '\0' ) |
| { |
| ASSERT( "The drive parameter must always be NULL on systems that don't" |
| "support drive letters. drive is being ignored!.\n" ); |
| } |
| |
| if ( dir != NULL && dir[ 0 ] != '\0' ) |
| { |
| UINT DirLength = strlen( dir ); |
| Length += DirLength ; |
| |
| if ( Length < _MAX_PATH ) |
| { |
| strncat( path, dir, DirLength ); |
| if ( dir[ DirLength - 1 ] != '/' && dir[ DirLength - 1 ] != '\\' ) |
| { |
| if ( Length + 1 < _MAX_PATH ) |
| { |
| path[ Length ] = '/'; |
| Length++; |
| path[ Length ] = '\0'; |
| } |
| else |
| { |
| goto Max_Path_Error; |
| } |
| } |
| } |
| else |
| { |
| goto Max_Path_Error; |
| } |
| } |
| |
| if ( fname != NULL && fname[ 0 ] != '\0' ) |
| { |
| UINT fNameLength = strlen( fname ); |
| Length += fNameLength; |
| |
| if ( Length < _MAX_PATH ) |
| { |
| strncat( path, fname, fNameLength ); |
| } |
| else |
| { |
| goto Max_Path_Error; |
| } |
| } |
| |
| if ( ext != NULL && ext[ 0 ] != '\0' ) |
| { |
| UINT ExtLength = strlen( ext ); |
| Length += ExtLength; |
| |
| if ( ext[ 0 ] != '.' ) |
| { |
| /* Add a '.' */ |
| if ( Length + 1 < _MAX_PATH ) |
| { |
| path[ Length - ExtLength ] = '.'; |
| Length++; |
| path[ Length - ExtLength ] = '\0'; |
| strncat( path, ext, ExtLength ); |
| } |
| else |
| { |
| goto Max_Path_Error; |
| } |
| } |
| else |
| { |
| /* Already has a '.' */ |
| if ( Length < _MAX_PATH ) |
| { |
| strncat( path, ext, ExtLength ); |
| } |
| else |
| { |
| goto Max_Path_Error; |
| } |
| } |
| } |
| |
| FILEDosToUnixPathA( path ); |
| LOGEXIT( "_makepath returning void.\n" ); |
| PERF_EXIT(_makepath); |
| return; |
| |
| Max_Path_Error: |
| |
| ERROR( "path cannot be greater then _MAX_PATH\n" ); |
| path[ 0 ] = '\0'; |
| LOGEXIT( "_makepath returning void \n" ); |
| PERF_EXIT(_makepath); |
| return; |
| } |
| |
| /*++ |
| Function: |
| _wmakepath |
| |
| See MSDN doc. |
| |
| --*/ |
| void |
| __cdecl |
| _wmakepath( |
| char16_t *path, |
| const char16_t *drive, |
| const char16_t *dir, |
| const char16_t *fname, |
| const char16_t *ext) |
| { |
| CHAR Dir[ _MAX_DIR ]={0}; |
| CHAR FileName[ _MAX_FNAME ]={0}; |
| CHAR Ext[ _MAX_EXT ]={0}; |
| CHAR Path[ _MAX_PATH ]={0}; |
| |
| PERF_ENTRY(_wmakepath); |
| ENTRY("_wmakepath (path=%p, drive=%p (%S), dir=%p (%S), fname=%p (%S), ext=%p (%S))\n", |
| path, drive ? drive:W16_NULLSTRING, drive ? drive:W16_NULLSTRING, dir ? dir:W16_NULLSTRING, dir ? dir:W16_NULLSTRING, |
| fname ? fname:W16_NULLSTRING, |
| fname ? fname:W16_NULLSTRING, ext ? ext:W16_NULLSTRING, ext ? ext:W16_NULLSTRING); |
| |
| /* According to the pal documentation, host operating systems that |
| don't support drive letters, the "drive" parameter must always be null. */ |
| if ( drive != NULL && drive[0] != '\0' ) |
| { |
| ASSERT( "The drive parameter must always be NULL on systems that don't" |
| "support drive letters. drive is being ignored!.\n" ); |
| } |
| |
| if ((dir != NULL) && WideCharToMultiByte( CP_ACP, 0, dir, -1, Dir, |
| _MAX_DIR, NULL, NULL ) == 0 ) |
| { |
| ASSERT( "An error occurred while converting dir to multibyte." |
| "Possible error: Length of dir is greater than _MAX_DIR.\n" ); |
| goto error; |
| } |
| |
| if ((fname != NULL) && WideCharToMultiByte( CP_ACP, 0, fname, -1, FileName, |
| _MAX_FNAME, NULL, NULL ) == 0 ) |
| { |
| ASSERT( "An error occurred while converting fname to multibyte." |
| "Possible error: Length of fname is greater than _MAX_FNAME.\n" ); |
| goto error; |
| } |
| |
| if ((ext != NULL) && WideCharToMultiByte( CP_ACP, 0, ext, -1, Ext, |
| _MAX_EXT, NULL, NULL ) == 0 ) |
| { |
| ASSERT( "An error occurred while converting ext to multibyte." |
| "Possible error: Length of ext is greater than _MAX_EXT.\n" ); |
| goto error; |
| } |
| |
| /* Call up to the ANSI _makepath. */ |
| _makepath_s( Path, sizeof(Path), NULL, Dir, FileName, Ext ); |
| |
| if ( MultiByteToWideChar( CP_ACP, 0, Path, -1, path, _MAX_PATH ) == 0 ) |
| { |
| ASSERT( "An error occurred while converting the back wide char." |
| "Possible error: The length of combined path is greater " |
| "than _MAX_PATH.\n" ); |
| goto error; |
| } |
| |
| LOGEXIT("_wmakepath returns void\n"); |
| PERF_EXIT(_wmakepath); |
| return; |
| |
| error: |
| *path = '\0'; |
| LOGEXIT("_wmakepath returns void\n"); |
| PERF_EXIT(_wmakepath); |
| } |
| |
| |
| /*++ |
| Function: |
| _fullpath |
| |
| See MSDN doc. |
| |
| --*/ |
| char * |
| __cdecl |
| _fullpath( |
| char *absPath, |
| const char *relPath, |
| size_t maxLength) |
| { |
| char realpath_buf[PATH_MAX+1]; |
| char path_copy[PATH_MAX+1]; |
| char *retval = NULL; |
| DWORD cPathCopy = sizeof(path_copy)/sizeof(path_copy[0]); |
| size_t min_length; |
| BOOL fBufAllocated = FALSE; |
| |
| PERF_ENTRY(_fullpath); |
| ENTRY("_fullpath (absPath=%p, relPath=%p (%s), maxLength = %lu)\n", |
| absPath, relPath ? relPath:"NULL", relPath ? relPath:"NULL", maxLength); |
| |
| if (strncpy_s(path_copy, sizeof(path_copy), relPath ? relPath : ".", cPathCopy) != SAFECRT_SUCCESS) |
| { |
| TRACE("_fullpath: strncpy_s failed!\n"); |
| goto fullpathExit; |
| } |
| |
| FILEDosToUnixPathA(path_copy); |
| |
| if(NULL == realpath(path_copy, realpath_buf)) |
| { |
| // do nothing. filename may not be there yet.. |
| } |
| |
| TRACE("real path is %s\n", realpath_buf); |
| min_length = strlen(realpath_buf)+1; // +1 for the NULL terminator |
| |
| if(NULL == absPath) |
| { |
| absPath = static_cast<char *>( |
| PAL_malloc(_MAX_PATH * sizeof(char))); |
| if (!absPath) |
| { |
| ERROR("PAL_malloc failed with error %d\n", errno); |
| goto fullpathExit; |
| } |
| maxLength = _MAX_PATH; |
| fBufAllocated = TRUE; |
| } |
| |
| if(min_length > maxLength) |
| { |
| ERROR("maxLength is %lu, we need at least %lu\n", |
| maxLength, min_length); |
| if (fBufAllocated) |
| { |
| PAL_free(absPath); |
| fBufAllocated = FALSE; |
| } |
| goto fullpathExit; |
| } |
| |
| strcpy_s(absPath, maxLength, realpath_buf); |
| retval = absPath; |
| |
| fullpathExit: |
| LOGEXIT("_fullpath returns char * %p\n", retval); |
| PERF_EXIT(_fullpath); |
| return retval; |
| } |