[PATCH 5/5] kernel32: Reimplement GetVolumePathName() using NtQueryInformationFile(FileAttributeTagInformation).

Zhiyi Zhang zzhang at codeweavers.com
Sat Mar 14 23:36:07 CDT 2020



On 3/14/20 11:41 PM, Zebediah Figura wrote:
> Signed-off-by: Zebediah Figura <zfigura at codeweavers.com>
> ---
>  dlls/kernel32/volume.c | 229 +++++++++++++++++++++--------------------
>  1 file changed, 119 insertions(+), 110 deletions(-)
>
> diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c
> index 5bfebf60570..6a2e221d579 100644
> --- a/dlls/kernel32/volume.c
> +++ b/dlls/kernel32/volume.c
> @@ -1591,149 +1591,158 @@ BOOL WINAPI GetVolumePathNameA(LPCSTR filename, LPSTR volumepathname, DWORD bufl
>      return ret;
>  }
>  
> +static BOOL is_dos_path( const UNICODE_STRING *path )
> +{
> +    static const WCHAR global_prefix[4] = {'\\','?','?','\\'};
> +    return path->Length >= 7 * sizeof(WCHAR)
> +            && !memcmp(path->Buffer, global_prefix, sizeof(global_prefix))
> +            && path->Buffer[5] == ':' && path->Buffer[6] == '\\';
> +}
> +
> +/* resolve all symlinks in a path in-place; return FALSE if allocation failed */
> +static BOOL resolve_symlink( UNICODE_STRING *path )
> +{
> +    OBJECT_NAME_INFORMATION *info;
> +    OBJECT_ATTRIBUTES attr;
> +    IO_STATUS_BLOCK io;
> +    NTSTATUS status;
> +    HANDLE file;
> +    ULONG size;
> +
> +    InitializeObjectAttributes( &attr, path, OBJ_CASE_INSENSITIVE, 0, NULL );
> +    if (NtOpenFile( &file, SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT ))
> +        return TRUE;
> +
> +    if (NtQueryObject( file, ObjectNameInformation, NULL, 0, &size ) != STATUS_INFO_LENGTH_MISMATCH)
> +    {
> +        NtClose( file );
> +        return TRUE;
> +    }
> +
> +    if (!(info = HeapAlloc( GetProcessHeap(), 0, size )))
> +    {
> +        NtClose( file );
> +        return FALSE;
> +    }
> +
> +    status = NtQueryObject( file, ObjectNameInformation, info, size, NULL );
> +    NtClose( file );
> +    if (status)
> +        return TRUE;
> +
> +    NtClose( file );
NtClose() is called twice.
> +    RtlFreeUnicodeString( path );
> +    status = RtlDuplicateUnicodeString( 0, &info->Name, path );
> +    HeapFree( GetProcessHeap(), 0, info );
> +    return !status;
> +}
> +
>  /***********************************************************************
>   *           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)
> +BOOL WINAPI GetVolumePathNameW(const WCHAR *path, WCHAR *volume_path, DWORD length)
>  {
> -    static const WCHAR deviceprefixW[] = { '\\','?','?','\\',0 };
> -    static const WCHAR ntprefixW[] = { '\\','\\','?','\\',0 };
> -    WCHAR fallbackpathW[] = { 'C',':','\\',0 };
> -    NTSTATUS status = STATUS_SUCCESS;
> -    WCHAR *volumenameW = NULL, *c;
> -    int pos, last_pos, stop_pos;
> +    static const WCHAR device_prefix[4] = {'\\','\\','.','\\'};
> +    static const WCHAR device_prefix2[4] = {'\\','\\','?','\\'};
> +    static const WCHAR global_prefix[4] = {'\\','?','?','\\'};
> +    static const WCHAR dotW[] = {'.',0};
> +    FILE_ATTRIBUTE_TAG_INFORMATION attr_info;
> +    FILE_BASIC_INFORMATION basic_info;
> +    OBJECT_ATTRIBUTES attr;
>      UNICODE_STRING nt_name;
> -    ANSI_STRING unix_name;
> -    BOOL first_run = TRUE;
> -    dev_t search_dev = 0;
> -    struct stat st;
>  
> -    TRACE("(%s, %p, %d)\n", debugstr_w(filename), volumepathname, buflen);
> +    if (path && !memcmp(path, global_prefix, sizeof(global_prefix)))
> +        path = dotW;
>  
> -    if (!filename || !volumepathname || !buflen)
> +    if (!volume_path || !length || !RtlDosPathNameToNtPathName_U( path, &nt_name, NULL, NULL ))
>      {
> -        SetLastError(ERROR_INVALID_PARAMETER);
> +        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) )))
> +    if (!is_dos_path( &nt_name ))
>      {
> -        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
> +        RtlFreeUnicodeString( &nt_name );
> +        WARN("invalid path %s\n", debugstr_w(path));
> +        SetLastError( ERROR_INVALID_NAME );
>          return FALSE;
>      }
> -    strcpyW( volumenameW, filename );
> -
> -    /* Normalize path */
> -    for (c = volumenameW; *c; c++) if (*c == '/') *c = '\\';
>  
> -    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;
> +    InitializeObjectAttributes( &attr, &nt_name, OBJ_CASE_INSENSITIVE, 0, NULL );
>  
> -    do
> +    while (nt_name.Length > 7 * sizeof(WCHAR))
>      {
> -        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)
> +        IO_STATUS_BLOCK io;
> +        HANDLE file;
> +
> +        if (!NtQueryAttributesFile( &attr, &basic_info )
> +                && (basic_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
> +                && (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
> +                && !NtOpenFile( &file, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attr, &io,
> +                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                        FILE_OPEN_REPARSE_POINT | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT ))
>          {
> -            if (stat( unix_name.Buffer, &st ) != 0)
> -            {
> -                RtlFreeAnsiString( &unix_name );
> -                status = STATUS_OBJECT_NAME_INVALID;
> -                goto cleanup;
> -            }
> -            if (first_run)
> -            {
> -                first_run = FALSE;
> -                search_dev = st.st_dev;
> -            }
> -            else if (st.st_dev != search_dev)
> +            if (!NtQueryInformationFile( file, &io, &attr_info, sizeof(attr_info), FileAttributeTagInformation ))
>              {
> -                /* folder is on a new filesystem, return the last folder */
> -                RtlFreeAnsiString( &unix_name );
> -                break;
> +                NtClose( file );
> +
> +                if (attr_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
> +                    break;
> +
> +                if (!resolve_symlink( &nt_name ))
> +                {
> +                    SetLastError( ERROR_OUTOFMEMORY );
> +                    return FALSE;
> +                }
>              }
> +            NtClose( file );
>          }
> -        RtlFreeAnsiString( &unix_name );
> -        last_pos = pos;
> -        c = strrchrW( volumenameW, '\\' );
> -        if (c != NULL)
> -            pos = c-volumenameW;
> -    } while (c != NULL && pos > stop_pos);
>  
> -    if (status != STATUS_SUCCESS)
> +        if (nt_name.Buffer[(nt_name.Length / sizeof(WCHAR)) - 1] == '\\')
> +            nt_name.Length -= sizeof(WCHAR);
> +        while (nt_name.Length && nt_name.Buffer[(nt_name.Length / sizeof(WCHAR)) - 1] != '\\')
> +            nt_name.Length -= sizeof(WCHAR);
> +    }
> +
> +    nt_name.Buffer[nt_name.Length / sizeof(WCHAR)] = 0;
> +
> +    if (NtQueryAttributesFile( &attr, &basic_info ))
>      {
> -        WCHAR cwdW[MAX_PATH];
> +        RtlFreeUnicodeString( &nt_name );
> +        WARN("nonexistent path %s -> %s\n", debugstr_w(path), debugstr_w( nt_name.Buffer ));
> +        SetLastError( ERROR_FILE_NOT_FOUND );
> +        return FALSE;
> +    }
>  
> -        /* the path was completely invalid */
> -        if (filename[0] == '\\' && strncmpW(deviceprefixW, filename, strlenW(deviceprefixW)) != 0)
> +    if (!memcmp(path, device_prefix, sizeof(device_prefix))
> +            || !memcmp(path, device_prefix2, sizeof(device_prefix2)))
> +    {
> +        if (length >= nt_name.Length / sizeof(WCHAR))
>          {
> -            /* NT-style paths (that are not device paths) fail */
> -            status = STATUS_OBJECT_NAME_INVALID;
> -            goto cleanup;
> -        }
> +            memcpy(volume_path, path, 4 * sizeof(WCHAR));
> +            lstrcpynW( volume_path + 4, nt_name.Buffer + 4, length - 4 );
>  
> -        /* DOS-style paths (anything not beginning with a slash) have fallback replies */
> -        if (filename[1] == ':')
> -        {
> -            /* if the path is semi-sane (X:) then use the given drive letter (if it is mounted) */
> -            fallbackpathW[0] = filename[0];
> -            if (!isalphaW(filename[0]) || GetDriveTypeW( fallbackpathW ) == DRIVE_NO_ROOT_DIR)
> -            {
> -                status = STATUS_OBJECT_NAME_NOT_FOUND;
> -                goto cleanup;
> -            }
> -        }
> -        else if (GetCurrentDirectoryW(ARRAY_SIZE(cwdW), cwdW ))
> -        {
> -            /* if the path is completely bogus then revert to the drive of the working directory */
> -            fallbackpathW[0] = cwdW[0];
> -        }
> -        else
> -        {
> -            status = STATUS_OBJECT_NAME_INVALID;
> -            goto cleanup;
> +            TRACE("%s -> %s\n", debugstr_w(path), debugstr_w(volume_path));
> +
> +            RtlFreeUnicodeString( &nt_name );
> +            return TRUE;
>          }
> -        last_pos = strlenW(fallbackpathW) - 1; /* points to \\ */
> -        filename = fallbackpathW;
> -        status = STATUS_SUCCESS;
>      }
> -
> -    if (last_pos + 1 <= buflen)
> +    else if (length >= (nt_name.Length / sizeof(WCHAR)) - 4)
>      {
> -        memcpy(volumepathname, filename, last_pos * sizeof(WCHAR));
> -        if (last_pos + 2 <= buflen) volumepathname[last_pos++] = '\\';
> -        volumepathname[last_pos] = '\0';
> +        lstrcpynW( volume_path, nt_name.Buffer + 4, length );
> +        volume_path[0] = toupperW(volume_path[0]);
>  
> -        /* DOS-style paths always return upper-case drive letters */
> -        if (volumepathname[1] == ':')
> -            volumepathname[0] = toupperW(volumepathname[0]);
> +        TRACE("%s -> %s\n", debugstr_w(path), debugstr_w(volume_path));
>  
> -        TRACE("Successfully translated path %s to mount-point %s\n",
> -              debugstr_w(filename), debugstr_w(volumepathname));
> +        RtlFreeUnicodeString( &nt_name );
> +        return TRUE;
>      }
> -    else
> -        status = STATUS_NAME_TOO_LONG;
>  
> -cleanup:
> -    HeapFree( GetProcessHeap(), 0, volumenameW );
> -    return set_ntstatus( status );
> +    RtlFreeUnicodeString( &nt_name );
> +    SetLastError( ERROR_FILENAME_EXCED_RANGE );
> +    return FALSE;
>  }
>  
>  




More information about the wine-devel mailing list