From 80d6e681f2bd154571105c54ec52de75396d4d98 Mon Sep 17 00:00:00 2001 From: Holger Vogt Date: Sat, 12 Apr 2025 18:12:54 +0200 Subject: [PATCH] MS Windows: Get a canonical paths name: Important if path length exceeds MAX_PATH, might happen when using PDKs Patch provided by kreijstal --- src/frontend/inpcom.c | 163 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 154 insertions(+), 9 deletions(-) diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index 83e3af6a4..c6d678386 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -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