Created
April 10, 2025 07:30
-
-
Save Kreijstal/2c9509455028cbeacd7b5d6243414f3a to your computer and use it in GitHub Desktop.
Attempting to get canonical path, for usage in ngspice..
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <errno.h> | |
/** | |
* @brief Resolves a Windows path to its canonical, absolute form using GetFullPathNameW. | |
* | |
* This function takes a path string (assumed to be UTF-8), converts it to | |
* UTF-16, calls the Windows API GetFullPathNameW to resolve '..' and '.' | |
* components and make the path absolute, and then converts the result back | |
* to a newly allocated UTF-8 string. | |
* | |
* It handles potential failures during conversion or path resolution. | |
* It does NOT automatically add the '\\?\' prefix for long paths, but | |
* GetFullPathNameW can produce paths longer than MAX_PATH. The caller | |
* might need to add the prefix separately if using the result in APIs | |
* that require it for long path support. | |
* | |
* @param input_path The input path string (UTF-8 encoded). Can be relative or | |
* absolute, may contain '.' or '..'. | |
* @return char* A newly allocated UTF-8 string containing the canonical absolute | |
* path, or NULL on failure. The caller is responsible for | |
* calling free() on the returned string. On failure, errno is | |
* set to indicate the error (e.g., ENOMEM, EINVAL, ENOENT). | |
*/ | |
char* get_windows_canonical_path(const char* input_path) { | |
wchar_t* wPathInput = NULL; | |
wchar_t* wPathOutput = NULL; | |
char* utf8PathOutput = NULL; | |
DWORD inputLenW = 0; | |
DWORD outputLenW = 0; | |
DWORD resultLenW = 0; | |
int inputLenMB = 0; | |
int outputLenMB = 0; | |
int original_errno = errno; | |
if (input_path == NULL) { | |
errno = EINVAL; | |
return NULL; | |
} | |
inputLenMB = (int)strlen(input_path); | |
if (inputLenMB == 0) { | |
inputLenW = 1; | |
} else { | |
inputLenW = MultiByteToWideChar(CP_UTF8, 0, input_path, inputLenMB, NULL, 0); | |
if (inputLenW == 0) { | |
errno = EINVAL; | |
return NULL; | |
} | |
inputLenW++; | |
} | |
wPathInput = (wchar_t*)malloc(inputLenW * sizeof(wchar_t)); | |
if (!wPathInput) { | |
errno = ENOMEM; | |
return NULL; | |
} | |
if (MultiByteToWideChar(CP_UTF8, 0, input_path, inputLenMB + 1, wPathInput, inputLenW) == 0) { | |
free(wPathInput); | |
errno = EINVAL; | |
return NULL; | |
} | |
errno = original_errno; | |
outputLenW = GetFullPathNameW(wPathInput, 0, NULL, NULL); | |
if (outputLenW == 0) { | |
DWORD dwError = GetLastError(); | |
if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_PATH_NOT_FOUND) | |
errno = ENOENT; | |
else if (dwError == ERROR_ACCESS_DENIED) | |
errno = EACCES; | |
else | |
errno = EINVAL; | |
free(wPathInput); | |
return NULL; | |
} | |
wPathOutput = (wchar_t*)malloc(outputLenW * sizeof(wchar_t)); | |
if (!wPathOutput) { | |
free(wPathInput); | |
errno = ENOMEM; | |
return NULL; | |
} | |
errno = original_errno; | |
resultLenW = GetFullPathNameW(wPathInput, outputLenW, wPathOutput, NULL); | |
free(wPathInput); | |
if (resultLenW == 0 || resultLenW >= outputLenW) { | |
DWORD dwError = GetLastError(); | |
if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_PATH_NOT_FOUND) | |
errno = ENOENT; | |
else if (dwError == ERROR_ACCESS_DENIED) | |
errno = EACCES; | |
else | |
errno = EINVAL; | |
free(wPathOutput); | |
return NULL; | |
} | |
outputLenMB = WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, NULL, 0, NULL, NULL); | |
if (outputLenMB == 0) { | |
free(wPathOutput); | |
errno = EINVAL; | |
return NULL; | |
} | |
utf8PathOutput = (char*)malloc(outputLenMB); | |
if (!utf8PathOutput) { | |
free(wPathOutput); | |
errno = ENOMEM; | |
return NULL; | |
} | |
if (WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, utf8PathOutput, outputLenMB, NULL, NULL) == 0) { | |
free(wPathOutput); | |
free(utf8PathOutput); | |
errno = EINVAL; | |
return NULL; | |
} | |
free(wPathOutput); | |
errno = original_errno; | |
return utf8PathOutput; | |
} | |
int main() { | |
const char* test_path = "C:/Users/test/some/dir/../../another/../real_dir/./file.txt"; | |
const char* long_base = "C:\\VeryLongDirectoryNameOftenExceedingLegacyLimits"; | |
char long_test_path[1024]; | |
snprintf(long_test_path, sizeof(long_test_path), "%s\\Subdir1\\..\\Subdir2\\.\\FinalFile.data", long_base); | |
const char* ngspice_path = "C:/Users/kreij/scoop/apps/msys2/2022-01-28/home/kreij/ok/protodec/examples/juan_temp/sky130A/libs.tech/ngspice/corners/../../../libs.ref/sky130_fd_pr/spice/../../../libs.tech/ngspice/parasitics/sky130_fd_pr__model__parasitic__diode_pw2dn__extended_drain.model.spice"; | |
char* canonical1 = get_windows_canonical_path(test_path); | |
char* canonical2 = get_windows_canonical_path(long_test_path); | |
char* canonical3 = get_windows_canonical_path(ngspice_path); | |
char* canonical4 = get_windows_canonical_path(".\\relative\\..\\path.txt"); | |
char* canonical_fail = get_windows_canonical_path("C:\\NonExistent\\..\\Path"); | |
printf("Input 1: %s\n", test_path); | |
if (canonical1) { | |
printf("Output 1: %s\n", canonical1); | |
free(canonical1); | |
} else { | |
perror("Error resolving path 1"); | |
} | |
printf("\n"); | |
printf("Input 2: %s\n", long_test_path); | |
if (canonical2) { | |
printf("Output 2: %s\n", canonical2); | |
free(canonical2); | |
} else { | |
perror("Error resolving path 2"); | |
} | |
printf("\n"); | |
printf("Input 3: %s\n", ngspice_path); | |
if (canonical3) { | |
printf("Output 3: %s\n", canonical3); | |
free(canonical3); | |
} else { | |
perror("Error resolving path 3"); | |
} | |
printf("\n"); | |
printf("Input 4: .\\relative\\..\\path.txt\n"); | |
if (canonical4) { | |
printf("Output 4: %s\n", canonical4); | |
free(canonical4); | |
} else { | |
perror("Error resolving path 4"); | |
} | |
printf("\n"); | |
printf("Input 5: C:\\NonExistent\\..\\Path\n"); | |
if (canonical_fail) { | |
printf("Output 5: %s\n", canonical_fail); | |
free(canonical_fail); | |
} else { | |
printf("Output 5: NULL\n"); | |
perror(" Error reported for path 5"); | |
} | |
printf("\n"); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Input 1: C:/Users/test/some/dir/../../another/../real_dir/./file.txt | |
Output 1: C:\Users\test\real_dir\file.txt | |
Input 2: C:\VeryLongDirectoryNameOftenExceedingLegacyLimits\Subdir1\..\Subdir2\.\FinalFile.data | |
Output 2: C:\VeryLongDirectoryNameOftenExceedingLegacyLimits\Subdir2\FinalFile.data | |
Input 3: C:/Users/kreij/scoop/apps/msys2/2022-01-28/home/kreij/ok/protodec/examples/juan_temp/sky130A/libs.tech/ngspice/corners/../../../libs.ref/sky130_fd_pr/spice/../../../libs.tech/ngspice/parasitics/sky130_fd_pr__model__parasitic__diode_pw2dn__extended_drain.model.spice | |
Output 3: C:\Users\kreij\scoop\apps\msys2\2022-01-28\home\kreij\ok\protodec\examples\juan_temp\sky130A\libs.tech\ngspice\parasitics\sky130_fd_pr__model__parasitic__diode_pw2dn__extended_drain.model.spice | |
Input 4: .\relative\..\path.txt | |
Output 4: C:\test\path.txt | |
Input 5: C:\NonExistent\..\Path | |
Output 5: C:\Path |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment