diff options
Diffstat (limited to 'src/sysutils.c')
-rw-r--r-- | src/sysutils.c | 122 |
1 files changed, 90 insertions, 32 deletions
diff --git a/src/sysutils.c b/src/sysutils.c index f016c10..5517b3d 100644 --- a/src/sysutils.c +++ b/src/sysutils.c @@ -26,6 +26,7 @@ #include <errno.h> #ifdef HAVE_W32_SYSTEM # include <windows.h> +# include <wchar.h> #endif #ifdef HAVE_STAT # include <sys/stat.h> @@ -253,73 +254,99 @@ _gpgrt_setenv (const char *name, const char *value, int overwrite) #ifdef HAVE_W32_SYSTEM +/* Checks whether fname has a path separator, that is a slash or + * backslash somewhere. However, it skips leading slashes with drive + * letters (backslashes and slashes are considered the same): + * x:\foo -> false + * x:foo -> false + * x: -> false + * x:\ -> false + * foo -> false + * foo\bar -> true + * x:\foo\bar -> true + * x:foo\bar -> true + * \\server -> true + */ +static int +has_path_separator (const char *fname) +{ + if (fname[0] == '/' || fname[0] == '\\') + return 1; + else if (fname[0] && fname[1] == ':') + return (fname[2] && strpbrk (fname+3, "/\\")); + else + return !!strpbrk (fname, "/\\"); +} + /* Convert an UTF-8 encode file name to wchar. If the file name is * close to the limit of MAXPATH the API functions will fail. The * method to overcome this API limitation is to use a prefix which * bypasses the checking by CreateFile. This also requires to first * convert the name to an absolute file name. To avoid looking up the - * fill name in all cases we do the \\?\ prefixing only if FNAME is + * full name in all cases we do the \\?\ prefixing only if FNAME is * longer than 60 byes or if it has any path separator. */ wchar_t * _gpgrt_fname_to_wchar (const char *fname) { static int no_extlenpath; - wchar_t *wname; + wchar_t *wname, *w; wchar_t *wfullpath = NULL; int success = 0; if (!no_extlenpath) { + /* Note that a value of -2 enables debugging of extlenpath. */ const char *s = getenv ("GPGRT_DISABLE_EXTLENPATH"); - if (s && atoi (s)) + int i = atoi (s? s:"0"); + + if (i > 0) no_extlenpath = 1; else - no_extlenpath = -1; + no_extlenpath = (i == -2)? -2 : -1; } wname = _gpgrt_utf8_to_wchar (fname); if (!wname) return NULL; + /* We use only backslashes in the wchar version of fname. */ + for (w = wname; *w; w++) + if (*w == L'/') + *w = L'\\'; if (no_extlenpath > 0) success = 1; /* Extended length path support has been disabled. */ else if (!strncmp (fname, "\\\\?\\", 4) || !strncmp (fname, "//?/", 4)) success = 1; /* Already translated. */ - else if (strpbrk (fname, "/\\") || wcslen (wname) > 60) + else if (has_path_separator (fname) || wcslen (wname) > 60) { int wlen = 1024; - int extralen; DWORD res; - wchar_t *w; try_again: - wfullpath = xtrymalloc (wlen * sizeof *wfullpath); + /* We need some extra characters (backslashes are actually used): + * Either "//?/" = 4 or "//?/UNC" = 7 and to be safe one more + * for the end of string */ + wfullpath = xtrymalloc ((wlen+8) * sizeof *wfullpath); if (!wfullpath) goto leave; - if (((*fname == '\\' && fname[1] == '\\') - ||(*fname == '/' && fname[1] == '/')) && fname[2]) - { - /* Note that the GFPN call below will append the UNC name - * and thus two leading backslashes. However, for an - * extended length path only one backslash is expected after - * the prefix. We handle this by replacing the first - * backslash with the C from UNC after the GFPN call. */ - wcscpy (wfullpath, L"\\\\?\\UN"); - extralen = 6; - } - else + if (no_extlenpath == -2) { - wcscpy (wfullpath, L"\\\\?\\"); - extralen = 4; + char *tmpn = _gpgrt_wchar_to_utf8 (wname, -1); + _gpgrt_log_debug ("%s:%d: checking '%s'\n", + __func__, __LINE__, tmpn); + xfree (tmpn); } - res = GetFullPathNameW (wname, wlen-extralen, wfullpath+extralen, NULL); + /* Note that GFPN is basically a string function without a + * MAX_PATH limitation. cf https://googleprojectzero.blogspot.com + * /2016/02/the-definitive-guide-on-win32-to-nt.html */ + res = GetFullPathNameW (wname, wlen, wfullpath, NULL); if (!res) { _gpgrt_w32_set_errno (-1); goto leave; } - else if (res >= wlen - extralen) + else if (res >= wlen) { /* Truncated - increase to the desired length. */ if (wlen > 1024) @@ -331,21 +358,46 @@ _gpgrt_fname_to_wchar (const char *fname) /* GetFullPathNameW indicated the required buffer length. */ _gpgrt_free_wchar (wfullpath); wfullpath = NULL; - wlen = res + extralen; + wlen = res; goto try_again; } _gpgrt_free_wchar (wname); wname = wfullpath; wfullpath = NULL; + if (no_extlenpath == -2) + { + char *tmpn = _gpgrt_wchar_to_utf8 (wname, -1); + _gpgrt_log_debug ("%s:%d: absfname '%s' (len=%d)\n", + __func__, __LINE__, tmpn, (int)res); + xfree (tmpn); + } - if (extralen == 6) - wname[6] = L'C'; /* Replace first backslash - see above. */ + if (res < MAX_PATH - 5) + ; /* No need for extended length path (-5 is kind of arbitrary) */ + else if (*wname == L'\\' && wname[1] == L'\\' && wname[2]) + { + /* For an UNC extended length path only one backslash is + * expected after the prefix. This we overwrite the first + * slash with the 'C'. Thus a shift by 6 and not 7. */ + wmemmove (wname+6, wname, res+1); + wname[0] = L'\\'; + wname[1] = L'\\'; + wname[2] = L'?'; + wname[3] = L'\\'; + wname[4] = L'U'; + wname[5] = L'N'; + wname[6] = L'C'; /* (Overwrites the first slash.) */ - /* Need to make sure that all slashes are mapped. */ - for (w = wname; *w; w++) - if (*w == L'/') - *w = L'\\'; - success = 1; + } + else + { + wmemmove (wname+4, wname, res+1); + wname[0] = L'\\'; + wname[1] = L'\\'; + wname[2] = L'?'; + wname[3] = L'\\'; + } + success = 2; } else success = 1; @@ -357,6 +409,12 @@ _gpgrt_fname_to_wchar (const char *fname) _gpgrt_free_wchar (wname); wname = NULL; } + if (wname && success == 2 && no_extlenpath == -2) + { + char *tmpn = _gpgrt_wchar_to_utf8 (wname, -1); + _gpgrt_log_debug ("%s:%d: using '%s'\n", __func__, __LINE__, tmpn); + xfree (tmpn); + } return wname; } |