Skip to content

Instantly share code, notes, and snippets.

@Kreijstal
Created April 10, 2025 07:30
Show Gist options
  • Save Kreijstal/2c9509455028cbeacd7b5d6243414f3a to your computer and use it in GitHub Desktop.
Save Kreijstal/2c9509455028cbeacd7b5d6243414f3a to your computer and use it in GitHub Desktop.
Attempting to get canonical path, for usage in ngspice..
#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;
}
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