MS Windows: Get a canonical paths name: Important if

path length exceeds MAX_PATH, might happen when using  PDKs

Patch provided by  kreijstal
This commit is contained in:
Holger Vogt 2025-04-12 18:12:54 +02:00
parent c264b71e22
commit f4963b1ada
1 changed files with 154 additions and 9 deletions

View File

@ -198,6 +198,10 @@ static void utf8_syntax_check(struct card *deck);
int add_to_sourcepath(const char* filepath, const char* path);
#if defined(_WIN32)
static char* get_windows_canonical_path(const char* input_path);
#endif
struct inp_read_t {
struct card *cc;
int line_number;
@ -1969,19 +1973,20 @@ FILE *inp_pathopen(const char *name, const char *mode)
if the file isn't in . and it isn't an abs path name.
*-------------------------------------------------------------------------*/
char *inp_pathresolve(const char *name)
char *inp_pathresolve(const char *cname)
{
struct variable *v;
struct stat st;
char* name;
#if defined(_WIN32)
/* If variable 'mingwpath' is set: convert mingw /d/... to d:/... */
if (cp_getvar("mingwpath", CP_BOOL, NULL, 0) &&
name[0] == DIR_TERM_LINUX && isalpha_c(name[1]) &&
name[2] == DIR_TERM_LINUX) {
cname[0] == DIR_TERM_LINUX && isalpha_c(cname[1]) &&
cname[2] == DIR_TERM_LINUX) {
DS_CREATE(ds, 100);
if (ds_cat_str(&ds, name) != 0) {
if (ds_cat_str(&ds, cname) != 0) {
fprintf(stderr, "Error: Unable to copy string while resolving path");
controlled_exit(EXIT_FAILURE);
}
@ -1993,27 +1998,33 @@ char *inp_pathresolve(const char *name)
return resolved_path;
}
/* Try to overcome MAX_PATH path length limit by removing '/..' */
name = get_windows_canonical_path(cname);
#else
name = copy(cname);
#endif
/* just try it */
if (stat(name, &st) == 0)
return copy(name);
return name;
#if !defined(EXT_ASC) && (defined(__MINGW32__) || defined(_MSC_VER))
wchar_t wname[BSIZE_SP];
if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, 2 * (int)strlen(name) + 1) == 0) {
fprintf(stderr, "UTF-8 to UTF-16 conversion failed with 0x%x\n", GetLastError());
fprintf(stderr, "%s could not be converted\n", name);
tfree(name);
return NULL;
}
if (_waccess(wname, 0) == 0)
return copy(name);
#endif
return name;
#endif
/* fail if this was an absolute filename or if there is no sourcepath var
*/
if (is_absolute_pathname(name) ||
!cp_getvar("sourcepath", CP_LIST, &v, 0)) {
tfree(name);
return (char *) NULL;
}
@ -2040,12 +2051,14 @@ char *inp_pathresolve(const char *name)
fprintf(stderr,
"ERROR: enumeration value `CP_BOOL' or `CP_LIST' "
"not handled in inp_pathresolve\nAborting...\n");
tfree(name);
controlled_exit(EXIT_FAILURE);
}
if (rc_ds != 0) { /* unable to build string */
(void) fprintf(cp_err,
"Error: Unable to build path name in inp_pathresolve");
tfree(name);
controlled_exit(EXIT_FAILURE);
}
@ -2056,6 +2069,7 @@ char *inp_pathresolve(const char *name)
char * const buf_cpy = dup_string(
buf, ds_get_length(&ds));
ds_free(&ds);
tfree(name);
return buf_cpy;
}
/* Else contiue with next attempt */
@ -2063,7 +2077,7 @@ char *inp_pathresolve(const char *name)
} /* end of loop over linked variables */
ds_free(&ds);
} /* end of block trying to find a valid name */
tfree(name);
return (char *) NULL;
} /* end of function inp_pathresolve */
@ -9749,3 +9763,134 @@ int add_to_sourcepath(const char* filepath, const char* path)
tfree(fpath);
return 0;
}
#if defined(_WIN32)
/**
* @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 = TMALLOC(wchar_t, inputLenW * sizeof(wchar_t));
if (!wPathInput) {
errno = ENOMEM;
return NULL;
}
if (MultiByteToWideChar(CP_UTF8, 0, input_path, inputLenMB + 1, wPathInput, inputLenW) == 0) {
tfree(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;
tfree(wPathInput);
return NULL;
}
wPathOutput = (wchar_t*)malloc(outputLenW * sizeof(wchar_t));
if (!wPathOutput) {
tfree(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;
tfree(wPathOutput);
return NULL;
}
outputLenMB = WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, NULL, 0, NULL, NULL);
if (outputLenMB == 0) {
tfree(wPathOutput);
errno = EINVAL;
return NULL;
}
utf8PathOutput = (char*)malloc(outputLenMB);
if (!utf8PathOutput) {
tfree(wPathOutput);
errno = ENOMEM;
return NULL;
}
if (WideCharToMultiByte(CP_UTF8, 0, wPathOutput, -1, utf8PathOutput, outputLenMB, NULL, NULL) == 0) {
tfree(wPathOutput);
tfree(utf8PathOutput);
errno = EINVAL;
return NULL;
}
tfree(wPathOutput);
errno = original_errno;
return utf8PathOutput;
}
#endif