From: "Erich E. Hoover" Subject: [PATCH 1/1] kernel32: Implement GetVolumePathName (resend 2). Message-Id: Date: Thu, 22 Dec 2011 12:41:05 -0700 Real Name: Erich Hoover Description: This patch implements GetVolumePathName by using the full folder path and working backward until stat() returns a different device. I encountered issues with having this function unimplemented after installing Portal 2 and attempting to launch the game for the first time (the "completing installation" failed). After looking into the issue, I found that steam has been updated in such a way that the "first run installation" calls CreateFile using FILE_OPEN_BY_FILE_ID (lookup by inode). Since my steam installation is on a different filesystem than my root partition, this meant that steam passed the wrong root search path to CreateFile and resulted in steam being unable to locate its files. To clarify a couple of use cases: 1) If only one file system is encountered then the base path is returned ("C:\windows\system32\" becomes "C:\") 2) If a symbolic link or mount-point causes a folder to be on a different file system then the most basic path on the same file system is returned ("C:\windows\system32\" becomes "C:\windows\" when "windows" is a link or mount-point residing on a different drive) Changelog: kernel32: Implement GetVolumePathName. From 6f6af1f515dc86289ed15ff552f957f65b6240ab Mon Sep 17 00:00:00 2001 From: Erich Hoover Date: Thu, 22 Dec 2011 12:15:30 -0700 Subject: kernel32: Implement GetVolumePathName. --- dlls/kernel32/volume.c | 92 +++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 83 insertions(+), 9 deletions(-) diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c index 5d7a07d..9d52099 100644 --- a/dlls/kernel32/volume.c +++ b/dlls/kernel32/volume.c @@ -1726,22 +1726,96 @@ 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; + NTSTATUS status = STATUS_SUCCESS; + WCHAR *volumenameW = NULL, *c; + UNICODE_STRING nt_name; + ANSI_STRING unix_name; + int first_run = TRUE; + dev_t search_dev = 0; + int pos, last_pos; + struct stat st; - FIXME("(%s, %p, %d), stub!\n", debugstr_w(filename), volumepathname, buflen); + if (!filename || !volumepathname) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + last_pos = pos = strlenW( filename ) + 1; + if (!(volumenameW = HeapAlloc( GetProcessHeap(), 0, pos * sizeof(WCHAR) ))) + { + SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + return FALSE; + } + strcpyW( volumenameW, filename ); - if (p && tolowerW(p[0]) >= 'a' && tolowerW(p[0]) <= 'z' && p[1] ==':' && p[2] == '\\' && buflen >= 4) + do { - volumepathname[0] = p[0]; - volumepathname[1] = ':'; - volumepathname[2] = '\\'; - volumepathname[3] = 0; - return TRUE; + volumenameW[pos] = '\0'; + if (!RtlDosPathNameToNtPathName_U( volumenameW, &nt_name, NULL, NULL )) + { + status = STATUS_OBJECT_PATH_NOT_FOUND; + goto cleanup; + } + status = wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ); + RtlFreeUnicodeString( &nt_name ); + if (status != STATUS_SUCCESS) + goto cleanup; + if (stat( unix_name.Buffer, &st ) != 0) + { + RtlFreeAnsiString( &unix_name ); + status = STATUS_OBJECT_PATH_NOT_FOUND; + 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); + + /* 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'; + TRACE("Successfully translated path %s to mount-point %s\n", + debugstr_w(filename), debugstr_w(volumepathname)); } - return FALSE; + else + status = STATUS_BUFFER_OVERFLOW; + +cleanup: + HeapFree( GetProcessHeap(), 0, volumenameW ); + + if (status != STATUS_SUCCESS) + SetLastError( RtlNtStatusToDosError(status) ); + return (status == STATUS_SUCCESS); } /*********************************************************************** -- 1.7.1