diff --git a/src/fdlib.c b/src/fdlib.c index 4ba522e5a1cc5ace087381c7863ebe700173ca96..08d2d395cdae8c182241061f295c5049ad304af5 100644 --- a/src/fdlib.c +++ b/src/fdlib.c @@ -28,6 +28,9 @@ #if defined(HAVE_WINSOCK_H) +#include <shlobj.h> +#include <objbase.h> + #include <time.h> /* Old versions of the headerfiles don't have this constant... */ @@ -76,6 +79,9 @@ int fd_busy[FD_SETSIZE]; /* Next free fd. FD_NO_MORE_FREE (-1) if all fds allocated. */ int first_free_handle; +/* The root desktop folder. */ +static LPSHELLFOLDER isf = NULL; + /* #define FD_DEBUG */ /* #define FD_STAT_DEBUG */ @@ -629,6 +635,10 @@ void fd_init(void) } while(0) #include "ntlibfuncs.h" } + + if (SHGetDesktopFolder(&isf) != S_OK) { + Pike_fatal("fdlib: No desktop folder!\n"); + } } void fd_exit(void) @@ -1520,6 +1530,106 @@ PMOD_EXPORT char *debug_fd_get_current_dir_name(void) return utf8buffer; } +PMOD_EXPORT char *debug_fd_normalize_path(const char *path) +{ + p_wchar1 *pname = pike_dwim_utf8_to_utf16(path); + p_wchar1 *buffer; + char *res; + size_t len; + PIDLIST_ABSOLUTE idl; + HRESULT hres; + + if (!pname) { + errno = ENOMEM; + return NULL; + } + + /* First convert to an absolute path. */ + len = MAX_PATH; + do { + size_t ret; + + buffer = malloc(len * sizeof(p_wchar1)); + if (!buffer) { + errno = ENOMEM; + return NULL; + } + + ret = GetFullPathNameW(pname, len, buffer, NULL); + + if (!ret) { + set_errno_from_win32_error(GetLastError()); + return NULL; + } + if (ret*2 < len) break; + + /* Buffer too small. Reallocate. + * NB: We over allocate 100% to be able to reuse the + * buffer safely with SHGetPathFromIDListW() below. + */ + free(buffer); + len = (ret+1)*2; + } while(1); + free(pname); + + if ((hres = isf->lpVtbl->ParseDisplayName(isf, NULL, NULL, buffer, + NULL, &idl, NULL)) != S_OK) { + free(buffer); + set_errno_from_win32_error(hres); + return NULL; + } + + if (!SHGetPathFromIDListW(idl, buffer)) { + /* FIXME: Use SHGetPathFromIDListExW() (Vista/2008 and later), + * to ensure that we don't overrun the buffer. + * It however doesn't seem to be documented how + * to detect whether it fails due to a too small + * buffer or for some other reason, so we couldn't + * use it to grow the res buffer and retry. + */ + free(buffer); + CoTaskMemFree (idl); + errno = EINVAL; + return NULL; + } + CoTaskMemFree (idl); + + /* Remove trailing slashes, except after a drive letter. */ + len = wcslen(buffer); + while(len && buffer[len]=='\\') { + len--; + } + if (!len || (len == 1 && buffer[len] == ':')) len++; + buffer[len] = '\\'; /* Paranoia. */ + buffer[len + 1] = 0; + + /* Convert host and share in an UNC path to lowercase since Windows + * Shell doesn't do that consistently. + */ + if (buffer[0] == '\\' && buffer[1] == '\\') { + size_t i; + int segments; + p_wchar1 c; + + for (i = segments = 2; (c = buffer[i]) && segments; i++) { + if (c >= 256) continue; + buffer[i] = tolower(buffer[i]); + if (c == '\\') { + segments--; + } + } + } + + res = pike_utf16_to_utf8(buffer); + free(buffer); + if (!res) { + errno = ENOMEM; + return NULL; + } + + return res; +} + PMOD_EXPORT FD debug_fd_open(const char *file, int open_mode, int create_mode) { HANDLE x; diff --git a/src/fdlib.h b/src/fdlib.h index 65bf51fd3a44cb2ff746c24f974b6c25a61079da..a2988c85d75e20351afd0c57b43cc356c56808b4 100644 --- a/src/fdlib.h +++ b/src/fdlib.h @@ -89,6 +89,7 @@ typedef off_t PIKE_OFF_T; #define fd_rename(O,N) debug_fd_rename(O,N) #define fd_chdir(DIR) debug_fd_chdir(DIR) #define fd_get_current_dir_name() debug_fd_get_current_dir_name() +#define fd_normalize_path(PATH) debug_fd_normalize_path(PATH) #define fd_open(X,Y,Z) dmalloc_register_fd(debug_fd_open((X),(Y)|fd_BINARY,(Z))) #define fd_socket(X,Y,Z) dmalloc_register_fd(debug_fd_socket((X),(Y),(Z))) #define fd_pipe(X) debug_fd_pipe( (X) DMALLOC_POS ) @@ -139,6 +140,7 @@ PMOD_EXPORT int debug_fd_mkdir(const char *dir, int mode); PMOD_EXPORT int debug_fd_rename(const char *old, const char *new); PMOD_EXPORT int debug_fd_chdir(const char *dir); PMOD_EXPORT char *debug_fd_get_current_dir_name(void); +PMOD_EXPORT char *debug_fd_normalize_path(const char *path); PMOD_EXPORT FD debug_fd_open(const char *file, int open_mode, int create_mode); PMOD_EXPORT FD debug_fd_socket(int domain, int type, int proto); PMOD_EXPORT int debug_fd_pipe(int fds[2] DMALLOC_LINE_ARGS); diff --git a/src/modules/system/nt.c b/src/modules/system/nt.c index 6d719caeec4150b9acbb79e4000d2aa76e402528..5bc9646ea18b8fa585f837678792852a7c3e3f81 100644 --- a/src/modules/system/nt.c +++ b/src/modules/system/nt.c @@ -34,9 +34,6 @@ #include <security.h> #endif -#include <shlobj.h> -#include <objbase.h> - /* These are defined by winerror.h in recent SDKs. */ #ifndef SEC_E_INSUFFICIENT_MEMORY #include <issperr.h> @@ -2746,132 +2743,17 @@ static void f_NetWkstaUserEnum(INT32 args) */ static void f_normalize_path(INT32 args) { - struct pike_string *str; - struct string_builder res; - size_t file_len; - p_wchar1 *pname, *file; - char *utf8; - ONERROR res_uwp, file_uwp; - DWORD ret; - - get_all_args("normalize_path", args, "%S", &str); - - pname = pike_dwim_utf8_to_utf16(str->str); - if (!pname) SIMPLE_OUT_OF_MEMORY_ERROR("normalize_path", str->len * 2); - SET_ONERROR(file_uwp, free, pname); + char *path = NULL; - /* Haven't got Emulate_GetLongPathNameW(). We essentially do what it - * does. This appears to be the only reliable way to normalize a - * path. (GetLongPathNameW() doesn't always correct upper/lower case - * differences, and opening the file to use e.g. - * GetFinalPathNameByHandle() on it might not work if it's already - * opened for exclusive access.) - */ + get_all_args("normalize_path", args, "%s", &path); - /* First convert to an absolute path. */ - file_len = MAX_PATH; - do { - file = xalloc (file_len * sizeof(p_wchar1)); - ret = GetFullPathNameW (pname, file_len, file, NULL); - if (ret >= file_len) { - /* Buffer to small. Reallocate. */ - free(file); - file_len = ret+1; - continue; - } - break; - } while(1); - CALL_AND_UNSET_ONERROR(file_uwp); - SET_ONERROR (file_uwp, free, file); - - if (!ret) { - unsigned int err = GetLastError(); - set_errno_from_win32_error (err); - throw_nt_error ("normalize_path", err); + path = fd_normalize_path(path); + if (!path) { + throw_nt_error("normalize_path", errno); } - { - LPSHELLFOLDER isf; - PIDLIST_ABSOLUTE idl; - HRESULT hres; - - if (SHGetDesktopFolder (&isf) != S_OK) - /* Use a nondescript error code. */ - throw_nt_error ("normalize_path", errno = ERROR_INVALID_DATA); - - hres = isf->lpVtbl->ParseDisplayName (isf, NULL, NULL, file, - NULL, &idl, NULL); - if (hres != S_OK) { - errno = (HRESULT_FACILITY (hres) == FACILITY_WIN32 ? - HRESULT_CODE (hres) : - /* Use a nondescript code if the error isn't a Win32 one. */ - ERROR_INVALID_DATA); - throw_nt_error ("normalize_path", errno); - } - - /* NB: Ensure that the buffer isn't likely to be overrun - * by the call to SHGetPathFromIDListW() below. - */ - file_len = (wcslen(file) + 1) * 2; - if (file_len < MAX_PATH) file_len = MAX_PATH; - init_string_builder_alloc(&res, file_len, 1); - SET_ONERROR (res_uwp, free_string_builder, &res); - - if (!SHGetPathFromIDListW(idl, STR1(res.s))) { - /* FIXME: Use SHGetPathFromIDListExW() (Vista/2008 and later), - * to ensure that we don't overrun the buffer. - * It however doesn't seem to be documented how - * to detect whether it fails due to a too small - * buffer or for some other reason, so we couldn't - * use it to grow the res buffer and retry. - */ - CoTaskMemFree (idl); - throw_nt_error ("normalize_path", errno = ERROR_INVALID_DATA); - } - res.s->len = wcslen(STR1(res.s)); - - CoTaskMemFree (idl); - } - - file = STR1(res.s); - - /* Remove trailing slashes, except after a drive letter. */ - { - ptrdiff_t l = (ptrdiff_t) res.s->len-1; - - if(l >= 0 && file[l]=='\\') - { - do l--; - while(l && file[l]=='\\'); - if (l == 1 && file[l] == ':') l++; - file[l + 1]=0; - } - res.s->len = l + 1; - } - - /* Convert host and share in an UNC path to lowercase since Windows - * Shell doesn't do that consistently. - */ - if (file[0] == '\\' && file[1] == '\\') { - size_t i; - for (i = 2; file[i] && file[i] != '\\'; i++) - if (file[i] < 256) - file[i] = tolower (file[i]); - if (file[i] == '\\') - for (i++; file[i] && file[i] != '\\'; i++) - if (file[i] < 256) - file[i] = tolower (file[i]); - } - - pop_n_elems(args); - - utf8 = pike_utf16_to_utf8(file); - if (!utf8) SIMPLE_OUT_OF_MEMORY_ERROR("normalize_path", res.s->len * 2); - CALL_AND_UNSET_ONERROR (res_uwp); - CALL_AND_UNSET_ONERROR (file_uwp); - - push_text(utf8); - free(utf8); + push_text(path); + free(path); } /*! @decl int GetFileAttributes(string filename)