// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. /*** *tsplitpath_s.inl - general implementation of _tsplitpath_s * * *Purpose: * This file contains the general algorithm for _splitpath_s and its variants. * *******************************************************************************/ _FUNC_PROLOGUE errno_t __cdecl _FUNC_NAME( __in_z const _CHAR *_Path, __out_ecount_z_opt(_DriveSize) _CHAR *_Drive, __in size_t _DriveSize, __out_ecount_z_opt(_DirSize) _CHAR *_Dir, __in size_t _DirSize, __out_ecount_z_opt(_FilenameSize) _CHAR *_Filename, __in size_t _FilenameSize, __out_ecount_z_opt(_ExtSize) _CHAR *_Ext, __in size_t _ExtSize ) { const _CHAR *tmp; const _CHAR *last_slash; const _CHAR *dot; int drive_set = 0; size_t length = 0; int bEinval = 0; /* validation section */ if (_Path == NULL) { goto error_einval; } if ((_Drive == NULL && _DriveSize != 0) || (_Drive != NULL && _DriveSize == 0)) { goto error_einval; } if ((_Dir == NULL && _DirSize != 0) || (_Dir != NULL && _DirSize == 0)) { goto error_einval; } if ((_Filename == NULL && _FilenameSize != 0) || (_Filename != NULL && _FilenameSize == 0)) { goto error_einval; } if ((_Ext == NULL && _ExtSize != 0) || (_Ext != NULL && _ExtSize == 0)) { goto error_einval; } /* check if _Path begins with the longpath prefix */ if (_Path[0] == _T('\\') && _Path[1] == _T('\\') && _Path[2] == _T('?') && _Path[3] == _T('\\')) { _Path += 4; } /* extract drive letter and ':', if any */ if (!drive_set) { // The CorUnix PAL is never built on Windows and thus, the code below // for the drive check is not required. #if 0 size_t skip = _MAX_DRIVE - 2; tmp = _Path; while (skip > 0 && *tmp != 0) { skip--; tmp++; } if (*tmp == _T(':')) { if (_Drive != NULL) { if (_DriveSize < _MAX_DRIVE) { goto error_erange; } _TCSNCPY_S(_Drive, _DriveSize, _Path, _MAX_DRIVE - 1); } _Path = tmp + 1; } else #endif { if (_Drive != NULL) { _RESET_STRING(_Drive, _DriveSize); } } } /* extract path string, if any. _Path now points to the first character * of the path, if any, or the filename or extension, if no path was * specified. Scan ahead for the last occurence, if any, of a '/' or * '\' path separator character. If none is found, there is no path. * We will also note the last '.' character found, if any, to aid in * handling the extension. */ last_slash = NULL; dot = NULL; tmp = _Path; for (; *tmp != 0; ++tmp) { #if _MBS_SUPPORT #pragma warning(push) #pragma warning(disable:4127) if (_ISMBBLEAD(*tmp)) #pragma warning(pop) { tmp++; } else #endif /* _MBS_SUPPORT */ { if (*tmp == _T('/') || *tmp == _T('\\')) { /* point to one beyond for later copy */ last_slash = tmp + 1; } else if (*tmp == _T('.')) { dot = tmp; } } } if (last_slash != NULL) { /* found a path - copy up through last_slash or max characters * allowed, whichever is smaller */ if (_Dir != NULL) { length = (size_t)(last_slash - _Path); if (_DirSize <= length) { goto error_erange; } _TCSNCPY_S(_Dir, _DirSize, _Path, length); // Normalize the path seperator int iIndex; for(iIndex = 0; iIndex < length; iIndex++) { if (_Dir[iIndex] == _T('\\')) { _Dir[iIndex] = _T('/'); } } } _Path = last_slash; } else { /* there is no path */ if (_Dir != NULL) { _RESET_STRING(_Dir, _DirSize); } } /* extract file name and extension, if any. Path now points to the * first character of the file name, if any, or the extension if no * file name was given. Dot points to the '.' beginning the extension, * if any. */ if (dot != NULL && (dot >= _Path)) { /* found the marker for an extension - copy the file name up to the '.' */ if (_Filename) { length = (size_t)(dot - _Path); if (length == 0) { // At this time, dot will be equal to _Path if string is something like "/." // since _path was set to last_slash, which in turn, was set to "tmp +1" // where "tmp" is the location where "/" was found. See code above for // clarification. // // For such cases, return the "." in filename buffer. // // Thus, if the length is zero, we know its a string like "/." and thus, we // set length to 1 to get the "." in filename buffer. length = 1; } if (_FilenameSize <= length) { goto error_erange; } _TCSNCPY_S(_Filename, _FilenameSize, _Path, length); } /* now we can get the extension - remember that tmp still points * to the terminating NULL character of path. */ if (_Ext) { // At this time, _Path is pointing to the character after the last slash found. // (See comments and code above for clarification). // // Returns extension as empty string for strings like "/.". if (dot > _Path) { length = (size_t)(tmp - dot); if (_ExtSize <= length) { goto error_erange; } /* Since dot pointed to the ".", make sure we actually have an extension like ".cmd" and not just ".", OR Confirm that its a string like "/.." - for this, return the second "." in the extension part. However, for strings like "/myfile.", return empty string in extension buffer. */ int fIsDir = (*(dot-1) == _T('.'))?1:0; if (length > 1 || (length == 1 && fIsDir == 1)) _TCSNCPY_S(_Ext, _ExtSize, dot, length); else _RESET_STRING(_Ext, _ExtSize); } else _RESET_STRING(_Ext, _ExtSize); } } else { /* found no extension, give empty extension and copy rest of * string into fname. */ if (_Filename) { length = (size_t)(tmp - _Path); if (_FilenameSize <= length) { goto error_erange; } _TCSNCPY_S(_Filename, _FilenameSize, _Path, length); } if (_Ext) { _RESET_STRING(_Ext, _ExtSize); } } _RETURN_NO_ERROR; error_einval: bEinval = 1; error_erange: if (_Drive != NULL && _DriveSize > 0) { _RESET_STRING(_Drive, _DriveSize); } if (_Dir != NULL && _DirSize > 0) { _RESET_STRING(_Dir, _DirSize); } if (_Filename != NULL && _FilenameSize > 0) { _RESET_STRING(_Filename, _FilenameSize); } if (_Ext != NULL && _ExtSize > 0) { _RESET_STRING(_Ext, _ExtSize); } _VALIDATE_POINTER(_Path); if (bEinval) { _RETURN_EINVAL; } return (errno = ERANGE); }