Skip to content
Snippets Groups Projects
path.c 28.3 KiB
Newer Older
/*
 * File path.c - managing path in debugging environments
 *
 * Copyright (C) 2004,2008, Eric Pouech
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "dbghelp_private.h"
#include "winnls.h"
#include "winternl.h"
#include "wine/debug.h"
#include "wine/heap.h"

WINE_DEFAULT_DEBUG_CHANNEL(dbghelp);

static const struct machine_dir
{
    DWORD machine;
    const WCHAR *pe_dir;
    const WCHAR *so_dir;
}
    all_machine_dir[] =
{
    {IMAGE_FILE_MACHINE_I386,  L"\\i386-windows\\",    L"\\i386-unix\\"},
    {IMAGE_FILE_MACHINE_AMD64, L"\\x86_64-windows\\",  L"\\x86_64-unix\\"},
    {IMAGE_FILE_MACHINE_ARMNT, L"\\arm-windows\\",     L"\\arm-unix\\"},
    {IMAGE_FILE_MACHINE_ARM64, L"\\aarch64-windows\\", L"\\aarch64-unix\\"},
};
static inline BOOL is_sepA(char ch) {return ch == '/' || ch == '\\';}
static inline BOOL is_sep(WCHAR ch) {return ch == '/' || ch == '\\';}
const char* file_nameA(const char* str)
    for (p = str + strlen(str) - 1; p >= str && !is_sepA(*p); p--);
const WCHAR* file_name(const WCHAR* str)
    for (p = str + lstrlenW(str) - 1; p >= str && !is_sep(*p); p--);
static inline void file_pathW(const WCHAR *src, WCHAR *dst)
{
    int len;

    for (len = lstrlenW(src) - 1; (len > 0) && (!is_sep(src[len])); len--);
    memcpy( dst, src, len * sizeof(WCHAR) );
    dst[len] = 0;
}

/******************************************************************
 *		FindDebugInfoFile (DBGHELP.@)
 *
 */
HANDLE WINAPI FindDebugInfoFile(PCSTR FileName, PCSTR SymbolPath, PSTR DebugFilePath)
    h = CreateFileA(FileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (h == INVALID_HANDLE_VALUE)
    {
        if (!SearchPathA(SymbolPath, file_nameA(FileName), NULL, MAX_PATH, DebugFilePath, NULL))
            return NULL;
        h = CreateFileA(DebugFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    }
    return (h == INVALID_HANDLE_VALUE) ? NULL : h;
}
 
/******************************************************************
 *		FindDebugInfoFileEx (DBGHELP.@)
 *
 */
HANDLE WINAPI FindDebugInfoFileEx(PCSTR FileName, PCSTR SymbolPath,
                                  PSTR DebugFilePath, 
                                  PFIND_DEBUG_FILE_CALLBACK Callback,
                                  PVOID CallerData)
{
    FIXME("(%s %s %s %p %p): stub\n", debugstr_a(FileName), debugstr_a(SymbolPath),
            debugstr_a(DebugFilePath), Callback, CallerData);
    return NULL;
}

/******************************************************************
 *		FindExecutableImageExW (DBGHELP.@)
HANDLE WINAPI FindExecutableImageExW(PCWSTR FileName, PCWSTR SymbolPath, PWSTR ImageFilePath,
                                     PFIND_EXE_FILE_CALLBACKW Callback, PVOID user)
{
    HANDLE h;

    if (Callback) FIXME("Unsupported callback yet\n");
    if (!SearchPathW(SymbolPath, FileName, NULL, MAX_PATH, ImageFilePath, NULL))
        return NULL;
    h = CreateFileW(ImageFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    return (h == INVALID_HANDLE_VALUE) ? NULL : h;
}

/******************************************************************
 *		FindExecutableImageEx (DBGHELP.@)
 *
 */
HANDLE WINAPI FindExecutableImageEx(PCSTR FileName, PCSTR SymbolPath, PSTR ImageFilePath,
                                    PFIND_EXE_FILE_CALLBACK Callback, PVOID user)

    if (Callback) FIXME("Unsupported callback yet\n");
    if (!SearchPathA(SymbolPath, FileName, NULL, MAX_PATH, ImageFilePath, NULL))
        return NULL;
    h = CreateFileA(ImageFilePath, GENERIC_READ, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    return (h == INVALID_HANDLE_VALUE) ? NULL : h;
}

/******************************************************************
 *		FindExecutableImage (DBGHELP.@)
 *
 */
HANDLE WINAPI FindExecutableImage(PCSTR FileName, PCSTR SymbolPath, PSTR ImageFilePath)
{
    return FindExecutableImageEx(FileName, SymbolPath, ImageFilePath, NULL, NULL);
}

/***********************************************************************
 *           MakeSureDirectoryPathExists (DBGHELP.@)
 */
BOOL WINAPI MakeSureDirectoryPathExists(PCSTR DirPath)
    char path[MAX_PATH];
    const char *p = DirPath;
    int  n;

    if (p[0] && p[1] == ':') p += 2;
    while (*p == '\\') p++; /* skip drive root */
    while ((p = strchr(p, '\\')) != NULL)
       n = p - DirPath + 1;
       memcpy(path, DirPath, n);
       path[n] = '\0';
       if( !CreateDirectoryA(path, NULL)            &&
           (GetLastError() != ERROR_ALREADY_EXISTS))
           return FALSE;
       p++;
    if (GetLastError() == ERROR_ALREADY_EXISTS)
       SetLastError(ERROR_SUCCESS);

    return TRUE;
/******************************************************************
 *		SymMatchFileNameW (DBGHELP.@)
 *
 */
BOOL WINAPI SymMatchFileNameW(PCWSTR file, PCWSTR match,
                              PWSTR* filestop, PWSTR* matchstop)

    TRACE("(%s %s %p %p)\n",
          debugstr_w(file), debugstr_w(match), filestop, matchstop);

    fptr = file + lstrlenW(file) - 1;
    mptr = match + lstrlenW(match) - 1;

    while (fptr >= file && mptr >= match)
    {
        if (towupper(*fptr) != towupper(*mptr) && !(is_sep(*fptr) && is_sep(*mptr)))
            break;
        fptr--; mptr--;
    }
    if (filestop) *filestop = (PWSTR)fptr;
    if (matchstop) *matchstop = (PWSTR)mptr;

    return mptr == match - 1;
}

/******************************************************************
 *		SymMatchFileName (DBGHELP.@)
 *
 */
BOOL WINAPI SymMatchFileName(PCSTR file, PCSTR match,
                             PSTR* filestop, PSTR* matchstop)
    TRACE("(%s %s %p %p)\n", debugstr_a(file), debugstr_a(match), filestop, matchstop);

    fptr = file + strlen(file) - 1;
    mptr = match + strlen(match) - 1;

    while (fptr >= file && mptr >= match)
    {
        if (toupper(*fptr) != toupper(*mptr) && !(is_sepA(*fptr) && is_sepA(*mptr)))
    if (filestop) *filestop = (PSTR)fptr;
    if (matchstop) *matchstop = (PSTR)mptr;
static BOOL do_searchW(PCWSTR file, PWSTR buffer, BOOL recurse,
                       PENUMDIRTREE_CALLBACKW cb, PVOID user)
{
    HANDLE              h;
    WIN32_FIND_DATAW    fd;
    unsigned            pos;
    BOOL                found = FALSE;

    pos = lstrlenW(buffer);
    if (buffer[pos - 1] != '\\') buffer[pos++] = '\\';
    lstrcpyW(buffer + pos, L"*.*");
    if ((h = FindFirstFileW(buffer, &fd)) == INVALID_HANDLE_VALUE)
        return FALSE;
    /* doc doesn't specify how the tree is enumerated...
     * doing a depth first based on, but may be wrong
     */
    do
    {
        if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..")) continue;
        lstrcpyW(buffer + pos, fd.cFileName);
        if (recurse && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            found = do_searchW(file, buffer, TRUE, cb, user);
        else if (SymMatchFileNameW(buffer, file, NULL, NULL))
        {
            if (!cb || cb(buffer, user)) found = TRUE;
        }
    } while (!found && FindNextFileW(h, &fd));
    if (!found) buffer[--pos] = '\0';
    FindClose(h);

    return found;
}

/***********************************************************************
 *           SearchTreeForFileW (DBGHELP.@)
 */
BOOL WINAPI SearchTreeForFileW(PCWSTR root, PCWSTR file, PWSTR buffer)
{
    TRACE("(%s, %s, %p)\n",
          debugstr_w(root), debugstr_w(file), buffer);
    lstrcpyW(buffer, root);
    return do_searchW(file, buffer, TRUE, NULL, NULL);
}

/***********************************************************************
 *           SearchTreeForFile (DBGHELP.@)
 */
BOOL WINAPI SearchTreeForFile(PCSTR root, PCSTR file, PSTR buffer)
    WCHAR       rootW[MAX_PATH];
    WCHAR       fileW[MAX_PATH];
    WCHAR       bufferW[MAX_PATH];
    BOOL        ret;

    MultiByteToWideChar(CP_ACP, 0, root, -1, rootW, MAX_PATH);
    MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, MAX_PATH);
    ret = SearchTreeForFileW(rootW, fileW, bufferW);
    if (ret)
        WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, NULL, NULL);
    return ret;
/******************************************************************
 *		EnumDirTreeW (DBGHELP.@)
 *
 *
 */
BOOL WINAPI EnumDirTreeW(HANDLE hProcess, PCWSTR root, PCWSTR file,
                        PWSTR buffer, PENUMDIRTREE_CALLBACKW cb, PVOID user)
{
    TRACE("(%p %s %s %p %p %p)\n",
          hProcess, debugstr_w(root), debugstr_w(file), buffer, cb, user);

    lstrcpyW(buffer, root);
    return do_searchW(file, buffer, TRUE, cb, user);
}

/******************************************************************
 *		EnumDirTree (DBGHELP.@)
 *
 *
 */
struct enum_dir_treeWA
{
    PENUMDIRTREE_CALLBACK       cb;
    void*                       user;
    char                        name[MAX_PATH];
};

static BOOL CALLBACK enum_dir_treeWA(PCWSTR name, PVOID user)
{
    struct enum_dir_treeWA*     edt = user;

    WideCharToMultiByte(CP_ACP, 0, name, -1, edt->name, MAX_PATH, NULL, NULL);
    return edt->cb(edt->name, edt->user);
}

BOOL WINAPI EnumDirTree(HANDLE hProcess, PCSTR root, PCSTR file,
                        PSTR buffer, PENUMDIRTREE_CALLBACK cb, PVOID user)
    WCHAR                       rootW[MAX_PATH];
    WCHAR                       fileW[MAX_PATH];
    WCHAR                       bufferW[MAX_PATH];
    struct enum_dir_treeWA      edt;
    BOOL                        ret;

    edt.cb = cb;
    edt.user = user;
    MultiByteToWideChar(CP_ACP, 0, root, -1, rootW, MAX_PATH);
    MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, MAX_PATH);
    if ((ret = EnumDirTreeW(hProcess, rootW, fileW, bufferW, enum_dir_treeWA, &edt)))
        WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, NULL, NULL);
    return ret;
    PFINDFILEINPATHCALLBACKW    cb;
/* checks that buffer (as found by matching the name) matches the info
 * (information is based on file type)
 * returns TRUE when file is found, FALSE to continue searching
 * (NB this is the opposite convention of SymFindFileInPathProc)
static BOOL CALLBACK sffip_cb(PCWSTR buffer, PVOID user)
    struct sffip*       s = user;
    /* yes, EnumDirTree/do_search and SymFindFileInPath callbacks use the opposite
     * convention to stop/continue enumeration. sigh.
     */
    return !(s->cb)(buffer, s->user);
}

/******************************************************************
 *		SymFindFileInPathW (DBGHELP.@)
BOOL WINAPI SymFindFileInPathW(HANDLE hProcess, PCWSTR searchPath, PCWSTR full_path,
                               PVOID id, DWORD two, DWORD three, DWORD flags,
                               PWSTR buffer, PFINDFILEINPATHCALLBACKW cb,
{
    struct sffip        s;
    struct process*     pcs = process_find_by_handle(hProcess);
    WCHAR               tmp[MAX_PATH];
    WCHAR*              ptr;
    const WCHAR*        filename;
    TRACE("(hProcess = %p, searchPath = %s, full_path = %s, id = %p, two = 0x%08lx, three = 0x%08lx, flags = 0x%08lx, buffer = %p, cb = %p, user = %p)\n",
          hProcess, debugstr_w(searchPath), debugstr_w(full_path),
          id, two, three, flags, buffer, cb, user);
    if (!searchPath) searchPath = pcs->search_path;
    filename = file_name(full_path);
        ptr = wcschr(searchPath, ';');
            memcpy(tmp, searchPath, (ptr - searchPath) * sizeof(WCHAR));
            tmp[ptr - searchPath] = 0;
            searchPath = ptr + 1;
        }
        else
        {
            lstrcpyW(tmp, searchPath);
        if (do_searchW(filename, tmp, FALSE, sffip_cb, &s))
            lstrcpyW(buffer, tmp);
    SetLastError(ERROR_FILE_NOT_FOUND);
    return FALSE;
}

/******************************************************************
 *		SymFindFileInPath (DBGHELP.@)
 *
 */
BOOL WINAPI SymFindFileInPath(HANDLE hProcess, PCSTR searchPath, PCSTR full_path,
                              PVOID id, DWORD two, DWORD three, DWORD flags,
                              PSTR buffer, PFINDFILEINPATHCALLBACK cb,
                              PVOID user)
{
    WCHAR                       searchPathW[MAX_PATH];
    WCHAR                       full_pathW[MAX_PATH];
    WCHAR                       bufferW[MAX_PATH];
    struct enum_dir_treeWA      edt;
    BOOL                        ret;

    /* a PFINDFILEINPATHCALLBACK and a PENUMDIRTREE_CALLBACK have actually the
     * same signature & semantics, hence we can reuse the EnumDirTree W->A
     * conversion helper
     */
    edt.cb = cb;
    edt.user = user;
    if (searchPath)
        MultiByteToWideChar(CP_ACP, 0, searchPath, -1, searchPathW, MAX_PATH);
    MultiByteToWideChar(CP_ACP, 0, full_path, -1, full_pathW, MAX_PATH);
    if ((ret =  SymFindFileInPathW(hProcess, searchPath ? searchPathW : NULL, full_pathW,
                                   id, two, three, flags,
                                   bufferW, enum_dir_treeWA, &edt)))
        WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, MAX_PATH, NULL, NULL);
    return ret;
}

struct module_find
{
    /* pdb: guid        PDB guid (if DS PDB file)
     *      or dw1      PDB timestamp (if JG PDB file)
     *      dw2         PDB age
     * dbg: dw1         DWORD:timestamp
     *      dw2         size of image (from PE header)
     */
    const GUID*                 guid;
    DWORD                       dw1;
    DWORD                       dw2;
    SYMSRV_INDEX_INFOW         *info;
    WCHAR                      *buffer; /* MAX_PATH + 1 */
    unsigned                    matched;
};

/* checks that buffer (as found by matching the name) matches the info
 * (information is based on file type)
 * returns TRUE when file is found, FALSE to continue searching
 * (NB this is the opposite convention of SymFindFileInPathProc)
 */
static BOOL CALLBACK module_find_cb(PCWSTR buffer, PVOID user)
{
    struct module_find* mf = user;
    unsigned            matched = 0;
    info.sizeofstruct = sizeof(info);
    if (!SymSrvGetFileIndexInfoW(buffer, &info, 0))
        return FALSE;
    if (!memcmp(&info.guid, mf->guid, sizeof(GUID))) matched++;
    if (info.timestamp == mf->dw1) matched++;
    if (info.age == mf->dw2) matched++;
    if (matched > mf->matched)
    {
        size_t len = min(wcslen(buffer), MAX_PATH);
        memcpy(mf->buffer, buffer, len * sizeof(WCHAR));
        mf->buffer[len] = L'\0';
        mf->matched = matched;
        mf->info->guid = info.guid;
        mf->info->timestamp = info.timestamp;
        mf->info->age = info.age;
    }
    /* yes, EnumDirTree/do_search and SymFindFileInPath callbacks use the opposite
     * convention to stop/continue enumeration. sigh.
     */
BOOL path_find_symbol_file(const struct process* pcs, const struct module* module,
                           PCSTR full_path, BOOL is_pdb, const GUID* guid, DWORD dw1, DWORD dw2,
                           SYMSRV_INDEX_INFOW *info, BOOL* is_unmatched)
{
    struct module_find  mf;
    const WCHAR*        filename;
    WCHAR              *searchPath = pcs->search_path;
    WCHAR               buffer[MAX_PATH];
    TRACE("(pcs = %p, full_path = %s, guid = %s, dw1 = 0x%08lx, dw2 = 0x%08lx)\n",
          pcs, debugstr_a(full_path), debugstr_guid(guid), dw1, dw2);
    mf.guid = guid;
    mf.dw1 = dw1;
    mf.dw2 = dw2;
    mf.matched = 0;
    mf.buffer = is_pdb ? info->pdbfile : info->dbgfile;
    MultiByteToWideChar(CP_ACP, 0, full_path, -1, info->file, MAX_PATH);
    filename = file_name(info->file);

    /* first check full path to file */
    if (is_pdb && module_find_cb(info->file, &mf))
        wcscpy( info->pdbfile, info->file );
    /* FIXME: Use Environment-Variables (see MS docs)
                 _NT_SYMBOL_PATH and _NT_ALT_SYMBOL_PATH
    ext = wcsrchr(module->module.LoadedImageName, L'.');
    while (searchPath)
    {
        ptr = wcschr(searchPath, ';');
        len = (ptr) ? ptr - searchPath : wcslen(searchPath);

        if (len + 1 < ARRAY_SIZE(buffer))
            memcpy(buffer, searchPath, len * sizeof(WCHAR));
            buffer[len] = L'\0';
            /* return first fully matched file */
            if (do_searchW(filename, buffer, FALSE, module_find_cb, &mf)) return TRUE;
            len = wcslen(buffer); /* do_searchW removes the trailing \ in buffer when present */
            /* check once max size for \symbols\<ext>\ */
            if (ext && len + 9 /* \symbols\ */ + wcslen(ext + 1) + 1 + 1 <= ARRAY_SIZE(buffer))
            {
                buffer[len++] = L'\\';
                wcscpy(buffer + len, ext + 1);
                wcscat(buffer + len, L"\\");
                if (do_searchW(filename, buffer, FALSE, module_find_cb, &mf)) return TRUE;
                wcscpy(buffer + len, L"symbols\\");
                wcscat(buffer + len, ext + 1);
                wcscat(buffer + len, L"\\");
                if (do_searchW(filename, buffer, FALSE, module_find_cb, &mf)) return TRUE;
            }
            ERR("Too long search element %ls\n", searchPath);
        searchPath = ptr ? ptr + 1 : NULL;

    /* check module-path */
    if (module->real_path)
    {
        file_pathW(module->real_path, buffer);
        if (do_searchW(filename, buffer, FALSE, module_find_cb, &mf)) return TRUE;
    }
    file_pathW(module->module.LoadedImageName, buffer);
    if (do_searchW(filename, buffer, FALSE, module_find_cb, &mf)) return TRUE;

    /* if no fully matching file is found, return the best matching file if any */
    if ((dbghelp_options & SYMOPT_LOAD_ANYTHING) && mf.matched)
    {
        return TRUE;
    }
    return FALSE;
}

WCHAR *get_dos_file_name(const WCHAR *filename)
{
    WCHAR *dos_path;
    size_t len;

    if (*filename == '/')
    {
        char *unix_path;
        len = WideCharToMultiByte(CP_UNIXCP, 0, filename, -1, NULL, 0, NULL, NULL);
        unix_path = heap_alloc(len * sizeof(WCHAR));
        WideCharToMultiByte(CP_UNIXCP, 0, filename, -1, unix_path, len, NULL, NULL);
        dos_path = wine_get_dos_file_name(unix_path);
        heap_free(unix_path);
    }
    else
    {
        len = lstrlenW(filename);
        dos_path = heap_alloc((len + 1) * sizeof(WCHAR));
        memcpy(dos_path, filename, (len + 1) * sizeof(WCHAR));
    }
    return dos_path;
}
static inline const WCHAR* get_machine_dir(const struct machine_dir *machine_dir, const WCHAR *name)
{
    WCHAR *ptr;
    if ((ptr = wcsrchr(name, L'.')) && !lstrcmpW(ptr, L".so"))
        return machine_dir->so_dir;
    return machine_dir->pe_dir;
}

static BOOL try_match_file(const WCHAR *name, BOOL (*match)(void*, HANDLE, const WCHAR*), void *param)
{
    HANDLE file = CreateFileW(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file != INVALID_HANDLE_VALUE)
    {
        BOOL ret = match(param, file, name);
        CloseHandle(file);
        return ret;
    }
    return FALSE;
}

BOOL search_dll_path(const struct process *process, const WCHAR *name, WORD machine, BOOL (*match)(void*, HANDLE, const WCHAR*), void *param)
    size_t len, i, machine_dir_len;
    const struct cpu* cpu;
    const struct machine_dir* machine_dir;
    cpu = machine == IMAGE_FILE_MACHINE_UNKNOWN ? process_get_cpu(process) : cpu_find(machine);

    for (machine_dir = all_machine_dir; machine_dir < all_machine_dir + ARRAY_SIZE(all_machine_dir); machine_dir++)
        if (machine_dir->machine == cpu->machine) break;
    if (machine_dir >= all_machine_dir + ARRAY_SIZE(all_machine_dir)) return FALSE;
    machine_dir_len = max(wcslen(machine_dir->pe_dir), wcslen(machine_dir->so_dir));

    if ((env = process_getenv(process, L"WINEBUILDDIR")))
        if (!(buf = heap_alloc((len + wcslen(L"\\programs\\") + machine_dir_len +
                                2 * lstrlenW(name) + 1) * sizeof(WCHAR)))) return FALSE;
        wcscpy(buf, env);
        end = buf + len;
        wcscpy(end, L"\\dlls\\");
        wcscat(end, name);
        if ((p = wcsrchr(end, '.')) && !lstrcmpW(p, L".so")) *p = 0;
        if ((p = wcsrchr(end, '.')) && !lstrcmpW(p, L".dll")) *p = 0;
        p = end + lstrlenW(end);
        /* try multi-arch first */
        wcscpy(p, get_machine_dir(machine_dir, name));
        wcscpy(p + wcslen(p), name);
        if (try_match_file(buf, match, param)) goto found;
        /* then old mono-arch */
        lstrcpyW(p, name);
        if (try_match_file(buf, match, param)) goto found;
        wcscpy(end, L"\\programs\\");
        end += wcslen(end);
        wcscpy(end, name);
        if ((p = wcsrchr(end, '.')) && !lstrcmpW(p, L".so")) *p = 0;
        if ((p = wcsrchr(end, '.')) && !lstrcmpW(p, L".exe")) *p = 0;
        p = end + lstrlenW(end);
        /* try multi-arch first */
        wcscpy(p, get_machine_dir(machine_dir, name));
        wcscpy(p + wcslen(p), name);
        if (try_match_file(buf, match, param)) goto found;
        /* then old mono-arch */
        lstrcpyW(p, name);
        if (try_match_file(buf, match, param)) goto found;

        heap_free(buf);
    }

    for (i = 0;; i++)
    {
        WCHAR env_name[64];
        swprintf(env_name, ARRAY_SIZE(env_name), L"WINEDLLDIR%u", i);
        if (!(env = process_getenv(process, env_name))) return FALSE;
        len = wcslen(env) + machine_dir_len + wcslen(name) + 1;
        if (!(buf = heap_alloc(len * sizeof(WCHAR)))) return FALSE;
        swprintf(buf, len, L"%s%s%s", env, get_machine_dir(machine_dir, name), name);
        if (try_match_file(buf, match, param)) goto found;
        if (try_match_file(buf, match, param)) goto found;
        heap_free(buf);
    }

    return FALSE;

found:
    TRACE("found %s\n", debugstr_w(buf));
    heap_free(buf);
    return TRUE;
}
BOOL search_unix_path(const WCHAR *name, const WCHAR *path, BOOL (*match)(void*, HANDLE, const WCHAR*), void *param)
    const WCHAR *iter, *next;
    size_t size, len;
    WCHAR *dos_path;
    char *buf;
    BOOL ret = FALSE;

    if (!path) return FALSE;
    name = file_name(name);

    size = WideCharToMultiByte(CP_UNIXCP, 0, name, -1, NULL, 0, NULL, NULL)
        + WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
    if (!(buf = heap_alloc(size))) return FALSE;

    for (iter = path;; iter = next + 1)
    {
        if (!(next = wcschr(iter, ':'))) next = iter + lstrlenW(iter);
            len = WideCharToMultiByte(CP_UNIXCP, 0, iter, next - iter, buf, size, NULL, NULL);
            if (buf[len - 1] != '/') buf[len++] = '/';
            WideCharToMultiByte(CP_UNIXCP, 0, name, -1, buf + len, size - len, NULL, NULL);
            if ((dos_path = wine_get_dos_file_name(buf)))
            {
                ret = try_match_file(dos_path, match, param);
                if (ret) TRACE("found %s\n", debugstr_w(dos_path));
                heap_free(dos_path);
                if (ret) break;
            }
        }
        if (*next != ':') break;
    }

    heap_free(buf);
    return ret;
}

/******************************************************************
 *      SymSrvGetFileIndexInfo (DBGHELP.@)
 *
 */
BOOL WINAPI SymSrvGetFileIndexInfo(const char *file, SYMSRV_INDEX_INFO* info, DWORD flags)
{
    SYMSRV_INDEX_INFOW infoW;
    WCHAR fileW[MAX_PATH];
    BOOL ret;

    TRACE("(%s, %p, 0x%08lx)\n", debugstr_a(file), info, flags);

    if (info->sizeofstruct < sizeof(*info))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW));
    infoW.sizeofstruct = sizeof(infoW);
    ret = SymSrvGetFileIndexInfoW(fileW, &infoW, flags);
    if (ret)
    {
        WideCharToMultiByte(CP_ACP, 0, infoW.file, -1, info->file, ARRAY_SIZE(info->file), NULL, NULL);
        info->stripped = infoW.stripped;
        info->timestamp = infoW.timestamp;
        info->size = infoW.size;
        WideCharToMultiByte(CP_ACP, 0, infoW.dbgfile, -1, info->dbgfile, ARRAY_SIZE(info->dbgfile), NULL, NULL);
        WideCharToMultiByte(CP_ACP, 0, infoW.pdbfile, -1, info->pdbfile, ARRAY_SIZE(info->pdbfile), NULL, NULL);
        info->guid = infoW.guid;
        info->sig = infoW.sig;
        info->age = infoW.age;
    }
    return ret;
}

/******************************************************************
 *      SymSrvGetFileIndexInfoW (DBGHELP.@)
 *
 */
BOOL WINAPI SymSrvGetFileIndexInfoW(const WCHAR *file, SYMSRV_INDEX_INFOW* info, DWORD flags)
{
    HANDLE      hFile, hMap = NULL;
    void*       image = NULL;
    DWORD       fsize, ret;

    TRACE("(%s, %p, 0x%08lx)\n", debugstr_w(file), info, flags);

    if (info->sizeofstruct < sizeof(*info))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if ((hFile = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ, NULL,
                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE &&
        ((hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL) &&
        ((image = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)) != NULL))
    {
        /* must handle PE, or .dbg or .pdb files. So each helper will return:
         * - ERROR_SUCCESS: if the file format is recognized and index info filled,
         * - ERROR_BAD_FORMAT: if the file doesn't match the expected format,
         * - any other error: if the file has expected format, but internal errors
         */
        fsize = GetFileSize(hFile, NULL);
        /* try PE module first */
        ret = pe_get_file_indexinfo(image, fsize, info);
        if (ret == ERROR_BAD_FORMAT)
            ret = pdb_get_file_indexinfo(image, fsize, info);
        if (ret == ERROR_BAD_FORMAT)
            ret = dbg_get_file_indexinfo(image, fsize, info);
    }
    else ret = ERROR_FILE_NOT_FOUND;

    if (image) UnmapViewOfFile(image);
    if (hMap) CloseHandle(hMap);
    if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);

    if (ret == ERROR_SUCCESS) wcscpy(info->file, file_name(file)); /* overflow? */
    SetLastError(ret);
    return ret == ERROR_SUCCESS;
}

/******************************************************************
 *      SymSrvGetFileIndexes (DBGHELP.@)
 *
 */
BOOL WINAPI SymSrvGetFileIndexes(PCSTR file, GUID* guid, PDWORD pdw1, PDWORD pdw2, DWORD flags)
{
    WCHAR fileW[MAX_PATH];

    TRACE("(%s, %p, %p, %p, 0x%08lx)\n", debugstr_a(file), guid, pdw1, pdw2, flags);

    MultiByteToWideChar(CP_ACP, 0, file, -1, fileW, ARRAY_SIZE(fileW));
    return SymSrvGetFileIndexesW(fileW, guid, pdw1, pdw2, flags);
}

/******************************************************************
 *      SymSrvGetFileIndexesW (DBGHELP.@)
 *
 */
BOOL WINAPI SymSrvGetFileIndexesW(PCWSTR file, GUID* guid, PDWORD pdw1, PDWORD pdw2, DWORD flags)
{
    FIXME("(%s, %p, %p, %p, 0x%08lx): stub!\n", debugstr_w(file), guid, pdw1, pdw2, flags);

    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return FALSE;
}