blob: 2d05b6654b4c798e166b8188d7d93da9e88aeb59 [file]
//
// 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;
}