From bcf395630fde511be0606ad8292385f37d127969 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Sat, 25 Jan 2014 09:47:12 -0700 Subject: kernel32: Implement GetVolumePathName. --- dlls/kernel32/tests/volume.c | 3 - dlls/kernel32/volume.c | 132 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 116 insertions(+), 19 deletions(-) diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c index 61da509..4b3bdf5 100644 --- a/dlls/kernel32/tests/volume.c +++ b/dlls/kernel32/tests/volume.c @@ -649,15 +649,12 @@ static void test_GetVolumePathNameA(void) volume[0] = '\0'; ret = pGetVolumePathNameA(pathC1, volume, sizeof(volume)); ok(ret, "expected success\n"); -todo_wine ok(!strcmp(expected, volume) || broken(!strcasecmp(expected, volume)) /* <=XP */, "expected name '%s', returned '%s'\n", expected, volume); volume[0] = '\0'; ret = pGetVolumePathNameA(pathC2, volume, sizeof(volume)); -todo_wine ok(ret, "expected success\n"); -todo_wine ok(!strcmp(expected, volume), "expected name '%s', returned '%s'\n", expected, volume); /* test an invalid path */ diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c index 1509d73..a4b158a 100644 --- a/dlls/kernel32/volume.c +++ b/dlls/kernel32/volume.c @@ -1784,7 +1784,7 @@ BOOL WINAPI GetVolumePathNameA(LPCSTR filename, LPSTR volumepathname, DWORD bufl BOOL ret; WCHAR *filenameW = NULL, *volumeW = NULL; - FIXME("(%s, %p, %d), stub!\n", debugstr_a(filename), volumepathname, buflen); + TRACE("(%s, %p, %d)\n", debugstr_a(filename), volumepathname, buflen); if (filename && !(filenameW = FILE_name_AtoW( filename, FALSE ))) return FALSE; @@ -1800,37 +1800,137 @@ BOOL WINAPI GetVolumePathNameA(LPCSTR filename, LPSTR volumepathname, DWORD bufl /*********************************************************************** * GetVolumePathNameW (KERNEL32.@) + * + * This routine is intended to find the most basic path on the same filesystem + * for any particular path name. Since we can have very complicated drive/path + * relationships on Unix systems, due to symbolic links, the safest way to + * handle this is to start with the full path and work our way back folder by + * folder unil we find a folder on a different drive (or run out of folders). */ BOOL WINAPI GetVolumePathNameW(LPCWSTR filename, LPWSTR volumepathname, DWORD buflen) { - const WCHAR *p = filename; + const WCHAR ntprefixW[] = { '\\','\\','?','\\',0 }; + const WCHAR fallbackpathW[] = { 'C',':','\\',0 }; + NTSTATUS status = STATUS_SUCCESS; + WCHAR *volumenameW = NULL, *c; + int pos, last_pos, stop_pos; + UNICODE_STRING nt_name; + ANSI_STRING unix_name; + BOOL first_run = TRUE; + dev_t search_dev = 0; + struct stat st; - FIXME("(%s, %p, %d), stub!\n", debugstr_w(filename), volumepathname, buflen); + TRACE("(%s, %p, %d)\n", debugstr_w(filename), volumepathname, buflen); - if (!filename || !volumepathname || !buflen) + if (!filename || !volumepathname || buflen == 0) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + last_pos = pos = strlenW( filename ); + /* allocate enough memory for searching the path (need room for a slash and a NULL terminator) */ + if (!(volumenameW = HeapAlloc( GetProcessHeap(), 0, (pos + 2) * sizeof(WCHAR) ))) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return FALSE; } + strcpyW( volumenameW, filename ); + stop_pos = 0; + /* stop searching slashes early for NT-type and nearly NT-type paths */ + if (strncmpW(ntprefixW, filename, strlenW(ntprefixW)) == 0) + stop_pos = strlenW(ntprefixW)-1; + else if (strncmpW(ntprefixW, filename, 2) == 0) + stop_pos = 2; + + do + { + volumenameW[pos+0] = '\\'; + volumenameW[pos+1] = '\0'; + if (!RtlDosPathNameToNtPathName_U( volumenameW, &nt_name, NULL, NULL )) + goto cleanup; + volumenameW[pos] = '\0'; + status = wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ); + RtlFreeUnicodeString( &nt_name ); + if (status == STATUS_SUCCESS) + { + if (stat( unix_name.Buffer, &st ) != 0) + { + RtlFreeAnsiString( &unix_name ); + status = STATUS_OBJECT_NAME_INVALID; + goto cleanup; + } + RtlFreeAnsiString( &unix_name ); + if (first_run) + { + first_run = FALSE; + search_dev = st.st_dev; + } + else if (st.st_dev != search_dev) + { + /* folder is on a new filesystem, return the last folder */ + break; + } + } + last_pos = pos; + c = strrchrW( volumenameW, '\\' ); + if (c != NULL) + pos = c-volumenameW; + } while (c != NULL && pos > stop_pos); - if (p && tolowerW(p[0]) >= 'a' && tolowerW(p[0]) <= 'z' && p[1] ==':' && p[2] == '\\') + if (status != STATUS_SUCCESS) { - if (buflen < 4) + /* the path was completely invalid */ + if (filename[0] != '\\') { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return FALSE; + /* DOS-style paths revert to C:\ (anything not beginning with a slash) */ + last_pos = strlenW(fallbackpathW); + filename = fallbackpathW; + status = STATUS_SUCCESS; + } + else + { + /* NT-style paths fail */ + status = STATUS_OBJECT_NAME_INVALID; + goto cleanup; } - volumepathname[0] = p[0]; - volumepathname[1] = ':'; - volumepathname[2] = '\\'; - volumepathname[3] = 0; - return TRUE; } - SetLastError(ERROR_INVALID_NAME); - return FALSE; + /* include the terminating backslash unless returning the full path */ + if (filename[last_pos] != '\0') + last_pos++; + /* require room to NULL terminate the string */ + if ((filename[last_pos] == '\\' && last_pos * sizeof(WCHAR) <= buflen) + || (last_pos+1) * sizeof(WCHAR) <= buflen) + { + memcpy(volumepathname, filename, last_pos*sizeof(WCHAR)); + /* remove the terminating backslash if the buffer is one byte short */ + if (filename[last_pos] == '\\' && (last_pos+1) * sizeof(WCHAR) > buflen) + last_pos--; + volumepathname[last_pos] = '\0'; + } + else + status = STATUS_NAME_TOO_LONG; + + if (status == STATUS_SUCCESS) + { + if (volumepathname[1] == ':') + { + /* DOS-style paths always return upper-case drive letters */ + volumepathname[0] = toupper(volumepathname[0]); + } + TRACE("Successfully translated path %s to mount-point %s\n", + debugstr_w(filename), debugstr_w(volumepathname)); + } + +cleanup: + HeapFree( GetProcessHeap(), 0, volumenameW ); + + if (status != STATUS_SUCCESS) + SetLastError( RtlNtStatusToDosError(status) ); + return (status == STATUS_SUCCESS); } + /*********************************************************************** * GetVolumePathNamesForVolumeNameA (KERNEL32.@) */ -- 1.7.9.5