593 lines
16 KiB
C++
593 lines
16 KiB
C++
/**
|
|
* @file kPath.cpp
|
|
*
|
|
* @internal
|
|
* Copyright (C) 2008-2021 by LMI Technologies Inc.
|
|
* Licensed under the MIT License.
|
|
* Redistributed files must retain the above copyright notice.
|
|
*/
|
|
#define K_PLATFORM
|
|
#include <kApi/Io/kPath.h>
|
|
#include <kApi/Io/kDirectory.h>
|
|
#include <stdio.h>
|
|
|
|
kBeginStaticClassEx(k, kPath)
|
|
kEndStaticClassEx()
|
|
|
|
kFx(kStatus) xkPath_SetDefaultHandlers()
|
|
{
|
|
//set the default path separator if a user-supplied value has not been provided
|
|
if (kApiLib_NativeSeparator() == 0)
|
|
{
|
|
kCheck(kApiLib_SetNativeSeparator(xkPATH_DEFAULT_NATIVE_SEPARATOR));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkPath_InitStatic()
|
|
{
|
|
return xkPath_SetDefaultHandlers();
|
|
}
|
|
|
|
kFx(kStatus) xkPath_ReleaseStatic()
|
|
{
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kChar) kPath_Separator()
|
|
{
|
|
return xkPATH_SEPARATOR;
|
|
}
|
|
|
|
kFx(kChar) xkPath_AltSeparator()
|
|
{
|
|
return xkPATH_ALT_SEPARATOR;
|
|
}
|
|
|
|
kFx(kChar) xkPath_NativeSeparator()
|
|
{
|
|
return kApiLib_NativeSeparator();
|
|
}
|
|
|
|
kFx(kBool) kPath_IsSeparator(kChar ch)
|
|
{
|
|
return (ch == kPath_Separator());
|
|
}
|
|
|
|
kFx(kChar) xkPath_VolumeSeparator()
|
|
{
|
|
return xkPATH_VOLUME_SEPARATOR;
|
|
}
|
|
|
|
kFx(kStatus) kPath_Combine(const kChar* segment1, const kChar* segment2, kChar* path, kSize capacity)
|
|
{
|
|
kChar segment1Norm[kPATH_MAX];
|
|
kChar segment2Norm[kPATH_MAX];
|
|
kSize length1 = kStrLength(segment1);
|
|
|
|
//normalize inputs
|
|
kCheck(kPath_ToNormal(segment1, segment1Norm, kCountOf(segment1Norm)));
|
|
kCheck(kPath_ToNormal(segment2, segment2Norm, kCountOf(segment2Norm)));
|
|
|
|
if (kStrEquals(segment2Norm, ""))
|
|
{
|
|
kCheck(kStrCopy(path, capacity, segment1Norm));
|
|
}
|
|
else if (kStrEquals(segment1Norm, ""))
|
|
{
|
|
kCheck(kStrCopy(path, capacity, segment2Norm));
|
|
}
|
|
else if (kPath_IsSeparator(segment1Norm[length1-1]) || kPath_IsSeparator(segment2Norm[0]))
|
|
{
|
|
kCheck(kStrPrintf(path, capacity, "%s%s", segment1Norm, segment2Norm));
|
|
}
|
|
else
|
|
{
|
|
kCheck(kStrPrintf(path, capacity, "%s%c%s", segment1Norm, kPath_Separator(), segment2Norm));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_Directory(const kChar* path, kChar* directory, kSize capacity)
|
|
{
|
|
kChar pathNorm[kPATH_MAX];
|
|
kSize pathLength = kStrLength(path);
|
|
kChar* division = kNULL;
|
|
|
|
//normalize inputs
|
|
kCheck(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm)));
|
|
|
|
//find the last separator
|
|
division = strrchr(pathNorm, kPath_Separator());
|
|
|
|
if (kIsNull(division) || ((kSize)(division - pathNorm + 1) == pathLength))
|
|
{
|
|
//if there is no separator, or if the separator is at the end of the path (since the path is
|
|
//in normal form, this can only mean root/windows-drive/unc-root), indicate that a parent
|
|
//directory was not found
|
|
return kERROR_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
kSize outputCapacity = (kSize)(division - pathNorm) + 2; //+1 for separator, +1 for null
|
|
|
|
if (outputCapacity > capacity)
|
|
{
|
|
return kERROR_INCOMPLETE;
|
|
}
|
|
|
|
//take the directory portion of the path
|
|
kStrCopy(directory, outputCapacity, pathNorm);
|
|
|
|
//remove trailing separator, if appropriate
|
|
kCheck(xkPath_RemoveTrailingSeparator(directory));
|
|
|
|
return kOK;
|
|
}
|
|
}
|
|
|
|
kFx(kStatus) kPath_BaseName(const kChar* path, kChar* baseName, kSize capacity)
|
|
{
|
|
kChar pathNorm[kPATH_MAX];
|
|
kChar* division = kNULL;
|
|
|
|
//normalize inputs
|
|
kCheck(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm)));
|
|
|
|
//find the last separator
|
|
division = strrchr(pathNorm, kPath_Separator());
|
|
|
|
if (kIsNull(division))
|
|
{
|
|
if (kStrEquals(pathNorm, ""))
|
|
{
|
|
//if the string is empty, report that the file portion of the path was not found.
|
|
return kERROR_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
//if no separator was found, but the path contains characters, assume that it represents a file name
|
|
kCheck(kStrCopy(baseName, capacity, pathNorm));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//take the file portion of the path
|
|
kCheck(kStrCopy(baseName, capacity, division+1));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
//bad name, because the returned portion may as well be a directory: deprecate?
|
|
kFx(kStatus) kPath_FileName(const kChar* path, kChar* fileName, kSize capacity)
|
|
{
|
|
return kPath_BaseName(path, fileName, capacity);
|
|
}
|
|
|
|
kFx(kStatus) kPath_Extension(const kChar* path, kChar* extension, kSize capacity)
|
|
{
|
|
kChar name[kPATH_MAX];
|
|
const kChar* division = kNULL;
|
|
|
|
kCheck(kPath_FileName(path, name, kCountOf(name)));
|
|
|
|
if (kIsNull(division = strrchr(name, '.')))
|
|
{
|
|
return kERROR_NOT_FOUND;
|
|
}
|
|
|
|
kCheck(kStrCopy(extension, capacity, division+1));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_StripExtension(const kChar* path, kChar* strippedPath, kSize capacity)
|
|
{
|
|
kChar normPath[kPATH_MAX];
|
|
const kChar* division = kNULL;
|
|
kSize charCount;
|
|
|
|
//normalize inputs
|
|
kCheck(kPath_ToNormal(path, normPath, kCountOf(normPath)));
|
|
|
|
if (kStrEquals(normPath, ""))
|
|
{
|
|
//if the string is empty, report that the file portion of the path was not found.
|
|
return kERROR_NOT_FOUND;
|
|
}
|
|
|
|
//use portion behind the last separator
|
|
division = strrchr(normPath, kPath_Separator());
|
|
division = kIsNull(division) ? normPath : division+1;
|
|
|
|
if (kIsNull(division = strrchr(division, '.')))
|
|
{
|
|
//no extension
|
|
return kStrCopy(strippedPath, capacity, normPath);
|
|
}
|
|
|
|
if ((charCount = (kSize)(division-normPath)) >= capacity)
|
|
{
|
|
return kERROR_MEMORY;
|
|
}
|
|
|
|
//copy portion of path without extension
|
|
kItemCopy(strippedPath, normPath, charCount);
|
|
strippedPath[charCount] = 0;
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kBool) kPath_IsAbsolute(const kChar* path)
|
|
{
|
|
kChar pathNorm[kPATH_MAX];
|
|
kChar separator = kPath_Separator();
|
|
|
|
//normalize inputs
|
|
if (kSuccess(kPath_ToNormal(path, pathNorm, kCountOf(pathNorm))))
|
|
{
|
|
kSize length = kStrLength(path);
|
|
kBool isRoot = (length > 0) && (pathNorm[0] == separator); // "/", "//"
|
|
kBool isVolumeRoot = (length > 2) && (pathNorm[1] == xkPath_VolumeSeparator()); // "c:/"
|
|
|
|
return isRoot || isVolumeRoot;
|
|
}
|
|
|
|
return kFALSE;
|
|
}
|
|
|
|
kFx(kSize) xkPath_CommonLength(const kChar* pathA, const kChar* pathB)
|
|
{
|
|
kChar separator = kPath_Separator();
|
|
kSize segment = 0;
|
|
kSize i = 0;
|
|
|
|
while ((pathA[i] != 0) && (pathB[i] != 0) && (xkPath_CompareChar(pathA[i], pathB[i])))
|
|
{
|
|
if (pathA[i] == separator)
|
|
{
|
|
segment = i;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (((pathA[i] == 0) && ((pathB[i] == 0) || (pathB[i] == separator))) ||
|
|
((pathB[i] == 0) && ((pathA[i] == 0) || (pathA[i] == separator))))
|
|
{
|
|
segment = i;
|
|
}
|
|
|
|
return segment;
|
|
}
|
|
|
|
kFx(kStatus) kPath_ToRelative(const kChar* pathA, const kChar* pathB, kChar* bRelativeToA, kSize capacity)
|
|
{
|
|
kChar pathANorm[kPATH_MAX];
|
|
kChar pathBNorm[kPATH_MAX];
|
|
kSize commonLength = 0;
|
|
kSize i;
|
|
|
|
if (!kIsNull(pathA))
|
|
{
|
|
kCheckArgs(kPath_IsAbsolute(pathA));
|
|
kCheck(kPath_ToCanonical(pathA, pathANorm, kCountOf(pathANorm)));
|
|
}
|
|
else
|
|
{
|
|
kCheck(kDirectory_Current(pathANorm, sizeof(pathANorm)));
|
|
}
|
|
|
|
kCheckArgs(kPath_IsAbsolute(pathB));
|
|
kCheck(kPath_ToCanonical(pathB, pathBNorm, kCountOf(pathBNorm)));
|
|
|
|
if (kStrCompareLower(pathANorm, pathBNorm) == 0)
|
|
{
|
|
//paths are identical
|
|
return kStrCopy(bRelativeToA, capacity, ".");
|
|
}
|
|
|
|
//absolute paths are guaranteed to have at least 1 character
|
|
if (kChar_ToLower(pathANorm[0]) != kChar_ToLower(pathBNorm[0]))
|
|
{
|
|
//paths have no common component (possible with Windows volumes)
|
|
return kStrCopy(bRelativeToA, capacity, pathBNorm);
|
|
}
|
|
|
|
kCheck(kStrCopy(bRelativeToA, capacity, ""));
|
|
|
|
commonLength = xkPath_CommonLength(pathANorm, pathBNorm);
|
|
|
|
//for each topmost directory in the reference path that is *not* in the target path,
|
|
//add an instance of ".." to the relative path
|
|
i = commonLength;
|
|
while ((pathANorm[i] != 0))
|
|
{
|
|
if (pathANorm[i++] == kPath_Separator())
|
|
{
|
|
kCheck(kPath_Combine(bRelativeToA, "..", bRelativeToA, capacity));
|
|
}
|
|
}
|
|
|
|
if (pathBNorm[commonLength] != 0)
|
|
{
|
|
kCheck(kPath_Combine(bRelativeToA, &pathBNorm[commonLength+1], bRelativeToA, capacity));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_ToAbsolute(const kChar* pathA, const kChar* bRelativeToA, kChar* pathB, kSize capacity)
|
|
{
|
|
kChar pathANorm[kPATH_MAX];
|
|
kChar bRelativeToANorm[kPATH_MAX];
|
|
kSize i = 0;
|
|
|
|
if (!kIsNull(pathA))
|
|
{
|
|
kCheckArgs(kPath_IsAbsolute(pathA));
|
|
|
|
kCheck(kPath_ToNormal(pathA, pathANorm, kCountOf(pathANorm)));
|
|
}
|
|
else
|
|
{
|
|
kCheck(kDirectory_Current(pathANorm, sizeof(pathANorm)));
|
|
}
|
|
|
|
kCheck(kPath_ToNormal(bRelativeToA, bRelativeToANorm, kCountOf(bRelativeToANorm)));
|
|
|
|
if (kStrEquals(bRelativeToA, "."))
|
|
{
|
|
//relative path is same as absolute path
|
|
return kStrCopy(pathB, capacity, pathA);
|
|
}
|
|
|
|
if (kPath_IsAbsolute(bRelativeToA))
|
|
{
|
|
//input path was already in absolute form
|
|
return kStrCopy(pathB, capacity, bRelativeToANorm);
|
|
}
|
|
|
|
kCheck(kStrCopy(pathB, capacity, pathANorm));
|
|
|
|
//pop the topmost directory from the absolute reference path for each instance of ".." in the relative path
|
|
while ((bRelativeToANorm[i] == '.') && (bRelativeToANorm[i+1] == '.'))
|
|
{
|
|
kCheck(kPath_Directory(pathB, pathB, capacity));
|
|
|
|
i += (bRelativeToANorm[i+2] == kPath_Separator()) ? 3 : 2;
|
|
}
|
|
|
|
if (bRelativeToANorm[i] != 0)
|
|
{
|
|
kCheck(kPath_Combine(pathB, &bRelativeToANorm[i], pathB, capacity));
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_ToNormal(const kChar* path, kChar* normalized, kSize capacity)
|
|
{
|
|
kChar separator = kPath_Separator();
|
|
kChar altSeparator = xkPath_AltSeparator();
|
|
kSize length = kStrLength(path);
|
|
kSize i = 0;
|
|
|
|
kCheckArgs(capacity >= (length+1));
|
|
|
|
//convert separators
|
|
for (i = 0; i < length+1; ++i)
|
|
{
|
|
normalized[i] = (path[i] == altSeparator) ? separator : path[i];
|
|
}
|
|
|
|
kCheck(xkPath_RemoveTrailingSeparator(normalized));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_ToNative(const kChar* path, kChar* native, kSize capacity)
|
|
{
|
|
kChar separator = kPath_Separator();
|
|
kChar altSeparator = xkPath_AltSeparator();
|
|
kChar nativeSeparator = xkPath_NativeSeparator();
|
|
kSize length = kStrLength(path);
|
|
kSize i = 0;
|
|
|
|
kCheckArgs(capacity >= (length+1));
|
|
|
|
//convert separators
|
|
for (i = 0; i < length+1; ++i)
|
|
{
|
|
native[i] = ((path[i] == separator) || (path[i] == altSeparator)) ? nativeSeparator : path[i];
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
//original from newlib realpath.c by Werner Almesberger
|
|
//modified to support UNC paths (\\NAS\share) as well as Windows long paths (\\?\c:\long-path)
|
|
kFx(kStatus) kPath_ToCanonical(const kChar* path, kChar* normalized, kSize capacity)
|
|
{
|
|
kChar buffer[kPATH_MAX];
|
|
kChar* temp = buffer;
|
|
kChar* sepPos = kNULL;
|
|
kChar* rootSep = normalized;
|
|
kChar* pos = normalized;
|
|
const kChar UNC_LONG_PATH_PREFIX[] = "//";
|
|
const kChar UNC_LONG_PATH_PREFIX_LENGTH = 2;
|
|
|
|
kCheckArgs(capacity <= kCountOf(buffer));
|
|
|
|
kCheck(kPath_ToNormal(path, buffer, kCountOf(buffer)));
|
|
|
|
if (strncmp(temp, UNC_LONG_PATH_PREFIX, UNC_LONG_PATH_PREFIX_LENGTH) == 0)
|
|
{
|
|
while (pos != normalized + UNC_LONG_PATH_PREFIX_LENGTH)
|
|
{
|
|
*pos++ = *temp++;
|
|
}
|
|
}
|
|
|
|
if (!kIsNull(sepPos = strchr(temp, '/')) && (sepPos == temp || *(sepPos-1) == ':'))
|
|
{
|
|
rootSep = pos + (sepPos-temp);
|
|
|
|
while (pos != rootSep+1)
|
|
{
|
|
*pos++ = *temp++;
|
|
}
|
|
}
|
|
|
|
*pos = 0;
|
|
|
|
if (!*temp)
|
|
{
|
|
return kOK;
|
|
}
|
|
|
|
do
|
|
{
|
|
sepPos = *temp ? strchr(temp, '/') : kNULL;
|
|
if (sepPos) *sepPos = 0;
|
|
|
|
if (!temp[0] || (temp[0] == '.' &&
|
|
(!temp[1] || (temp[1] == '.' && !temp[2]))))
|
|
{
|
|
pos--;
|
|
|
|
if (temp[0] && temp[1])
|
|
{
|
|
while (pos != rootSep && *--pos != '/');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strcpy(pos, temp);
|
|
|
|
pos = strchr(normalized, 0);
|
|
}
|
|
|
|
if (!kIsNull(sepPos) || pos == rootSep)
|
|
{
|
|
*pos++ = '/';
|
|
temp = sepPos+1;
|
|
}
|
|
|
|
*pos = 0;
|
|
}
|
|
while (!kIsNull(sepPos));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkPath_RemoveTrailingSeparator(kChar* path)
|
|
{
|
|
kSize length = kStrLength(path);
|
|
|
|
//if trailing slash is present...
|
|
if ((length > 0) && (path[length-1] == kPath_Separator()))
|
|
{
|
|
kBool isStandardRoot = (length == 1); // "/"
|
|
kBool isUncRoot = (length == 2) && (path[0] == kPath_Separator()); // "//"
|
|
kBool isVolumeRoot = (length == 3) && (path[1] == xkPath_VolumeSeparator()); // "c:/"
|
|
|
|
//if trailing slash is not a root...
|
|
if (!isStandardRoot && !isUncRoot && !isVolumeRoot)
|
|
{
|
|
//remove trailing slash
|
|
path[length-1] = 0;
|
|
}
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_LibraryName(const kChar* libraryName, kChar* fileName, kSize capacity)
|
|
{
|
|
kChar tempName[kPATH_MAX];
|
|
|
|
kCheck(kStrCopy(tempName, kCountOf(tempName), libraryName));
|
|
|
|
return kStrPrintf(fileName, capacity, "%s.%s", tempName, xkPATH_LIBRARY_EXTENSION);
|
|
}
|
|
|
|
kFx(kStatus) kPath_Plugin(const kChar* pluginName, kChar* pluginPath, kSize capacity)
|
|
{
|
|
kChar fileName[kPATH_MAX];
|
|
|
|
kCheck(kPath_LibraryName(pluginName, fileName, capacity));
|
|
|
|
kCheck(kDirectory_Plugin(pluginPath, capacity));
|
|
kCheck(kPath_Combine(pluginPath, fileName, pluginPath, capacity));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kPath_ApplicationName(const kChar* appName, kChar* fileName, kSize capacity)
|
|
{
|
|
return kStrPrintf(fileName, capacity, "%s%s", appName, xkPATH_EXECUTABLE_EXTENSION);
|
|
}
|
|
|
|
kFx(kStatus) kPath_Application(const kChar* appName, kChar* appPath, kSize capacity)
|
|
{
|
|
kChar fileName[kPATH_MAX];
|
|
|
|
kCheck(kPath_ApplicationName(appName, fileName, kCountOf(fileName)));
|
|
kCheck(kDirectory_Application(appPath, capacity));
|
|
kCheck(kPath_Combine(appPath, fileName, appPath, capacity));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kBool) kPath_IsFileNameValid(const kChar* fileName)
|
|
{
|
|
return (strcspn(fileName, "\\/:*?\"<>|") == strlen(fileName));
|
|
}
|
|
|
|
kFx(kStatus) xkPath_FromVirtual(const kChar* vPath, kChar* path, kSize capacity)
|
|
{
|
|
return kApiLib_DirectoryHandlers()->fromVirtual(vPath, path, capacity);
|
|
}
|
|
|
|
kFx(kStatus) xkPath_ToVirtual(const kChar* path, kChar* vPath, kSize capacity)
|
|
{
|
|
return kApiLib_DirectoryHandlers()->toVirtual(path, vPath, capacity);
|
|
}
|
|
|
|
#if defined(K_WINDOWS)
|
|
|
|
kFx(kStatus) xkPath_NormalizedToNativeWideWin(const kChar* path, WCHAR* wpath, kSize capacity)
|
|
{
|
|
kChar nativePath[kPATH_MAX];
|
|
|
|
kCheck(xkPath_FromVirtual(path, nativePath, kCountOf(nativePath)));
|
|
kCheck(kPath_ToNative(nativePath, nativePath, kCountOf(nativePath)));
|
|
|
|
if (MultiByteToWideChar(CP_UTF8, 0, nativePath, -1, wpath, (int)capacity) == 0)
|
|
{
|
|
return kERROR_PARAMETER;
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkPath_NativeWideToNormalizedWin(const WCHAR* wpath, kChar* path, kSize capacity)
|
|
{
|
|
kChar nativePath[kPATH_MAX];
|
|
|
|
if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, nativePath, kCountOf(nativePath), kNULL, kNULL) == 0)
|
|
{
|
|
return kERROR_PARAMETER;
|
|
}
|
|
|
|
kCheck(kPath_ToNormal(nativePath, nativePath, capacity));
|
|
kCheck(xkPath_ToVirtual(nativePath, path, capacity));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
#endif
|