From: Elizabeth Figura zfigura@codeweavers.com
This patch introduces the bulk of the Driver Store logic.
For some unfathomable reason, Windows driver files are not installed to their "proper" place until the device is actually plugged in and used. Once that happens, the INF file in C:/windows/inf/ is used as an installation script.
However, Microsoft also wanted to make it possible for drivers to be installed before a device is plugged in and used, without putting the onus on the driver distributor to keep the source files somewhere that the INF will know where to find them.
Thus the Driver Store was introduced. When an INF is "preinstalled", a "package" containing the INF, catalog, and any file referenced by the INF is copied to a location in C:/windows/system32/DriverStore/. The INF itself is then copied again to C:/windows/inf/ so that it can be probed when a new device is enumerated, and a PNF is created referencing the driver store location.
The Driver Store attempts to avoid duplication, but also treats any difference in any file as creating a different driver package.
The Driver Store APIs introduced here are actually completely undocumented. The Driver Store is normally accessed through one of several apparently higher-level components, apparently introduced at different points in time, and all with apparently the same function:
* DriverPackageInstall() et al. from difxapi.dll
* DiInstallDriver() et al. from newdev.dll
* SetupCopyOEMInf() [this not only installs the INF but also implicitly also installs to the driver store]
* The DIFx MSI-based framework, including the DIFxApp.dll redistributable and dpinst.exe
The first three are Windows components, but the last is a redistributable. More pressingly, DIFxApp.dll is a custom action DLL, which means that MSI will invariably rename it, so we cannot even in practice substitute a builtin version.
We cannot easily substitute a builtin dpinst.exe, either, since our current load order logic always uses a native EXE if specified by path (which in practice is the case). It's not clear that changing this logic is worthwhile. --- dlls/setupapi/devinst.c | 573 +++++++++++++++++++++++++++++++++++- dlls/setupapi/setupapi.spec | 2 + 2 files changed, 573 insertions(+), 2 deletions(-)
diff --git a/dlls/setupapi/devinst.c b/dlls/setupapi/devinst.c index 2c46c8c5fef..ee3b4bb2a34 100644 --- a/dlls/setupapi/devinst.c +++ b/dlls/setupapi/devinst.c @@ -18,7 +18,9 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <assert.h> #include <stdarg.h> +#include <stdbool.h> #include <stdlib.h>
#include "windef.h" @@ -161,6 +163,56 @@ struct device_iface struct list entry; };
+static bool array_reserve(void **elements, size_t *capacity, size_t count, size_t size) +{ + unsigned int new_capacity, max_capacity; + void *new_elements; + + if (count <= *capacity) + return true; + + max_capacity = ~(size_t)0 / size; + if (count > max_capacity) + return false; + + new_capacity = max(4, *capacity); + while (new_capacity < count && new_capacity <= max_capacity / 2) + new_capacity *= 2; + if (new_capacity < count) + new_capacity = max_capacity; + + if (!(new_elements = realloc(*elements, new_capacity * size))) + return false; + + *elements = new_elements; + *capacity = new_capacity; + + return true; +} + +static WCHAR *sprintf_path(const WCHAR *format, ...) +{ + va_list args, args_copy; + WCHAR *buffer; + size_t len; + + va_start(args, format); + + va_copy(args_copy, args); + len = _vsnwprintf(NULL, 0, format, args_copy) + 1; + va_end(args_copy); + + buffer = malloc(len * sizeof(WCHAR)); + _vsnwprintf(buffer, len, format, args); + va_end(args); + return buffer; +} + +static WCHAR *concat_path(const WCHAR *root, const WCHAR *path) +{ + return sprintf_path(L"%s\%s", root, path); +} + static struct DeviceInfoSet *get_device_set(HDEVINFO devinfo) { struct DeviceInfoSet *set = devinfo; @@ -5420,6 +5472,351 @@ static int compare_files(HANDLE file1, HANDLE file2) return 0; }
+struct driver_package +{ + const WCHAR *inf_name; + HINF hinf; + WCHAR *src_root, *dst_root; + bool already_installed; + + struct file + { + WCHAR *desc, *tag, *subdir, *filename; + } *files; + size_t file_count, files_size; +}; + +static void driver_package_cleanup(struct driver_package *package) +{ + free(package->src_root); + free(package->dst_root); + for (size_t i = 0; i < package->file_count; ++i) + { + free(package->files[i].desc); + free(package->files[i].tag); + free(package->files[i].subdir); + free(package->files[i].filename); + } + free(package->files); + SetupCloseInfFile(package->hinf); +} + +static WCHAR *get_string_field(INFCONTEXT *ctx, DWORD index) +{ + WCHAR *ret; + DWORD len; + + if (!SetupGetStringFieldW(ctx, index, NULL, 0, &len) || len <= 1) + return NULL; + + ret = malloc(len * sizeof(WCHAR)); + SetupGetStringFieldW(ctx, index, ret, len, NULL); + return ret; +} + +static bool get_source_info(HINF hinf, const WCHAR *filename, WCHAR **desc, WCHAR **tag, WCHAR **subdir) +{ + WCHAR *file_subdir = NULL, *disk_subdir = NULL; + UINT diskid; + DWORD len; + + if (!SetupGetSourceFileLocationW(hinf, NULL, filename, &diskid, NULL, 0, &len)) + { + ERR("Failed to get location for %s, error %lu.\n", debugstr_w(filename), GetLastError()); + return false; + } + + if (len > 1) + { + if (!(file_subdir = malloc(len * sizeof(WCHAR)))) + return false; + SetupGetSourceFileLocationW(hinf, NULL, filename, &diskid, file_subdir, len, NULL); + } + + if (SetupGetSourceInfoW(hinf, diskid, SRCINFO_DESCRIPTION, NULL, 0, &len) && len > 1 + && (*desc = malloc(len * sizeof(WCHAR)))) + SetupGetSourceInfoW(hinf, diskid, SRCINFO_DESCRIPTION, *desc, len, NULL); + + if (SetupGetSourceInfoW(hinf, diskid, SRCINFO_TAGFILE, NULL, 0, &len) && len > 1 + && (*tag = malloc(len * sizeof(WCHAR)))) + SetupGetSourceInfoW(hinf, diskid, SRCINFO_TAGFILE, *tag, len, NULL); + + if (SetupGetSourceInfoW(hinf, diskid, SRCINFO_PATH, NULL, 0, &len) && len > 1 + && (disk_subdir = malloc(len * sizeof(WCHAR)))) + SetupGetSourceInfoW(hinf, diskid, SRCINFO_PATH, disk_subdir, len, NULL); + + if (disk_subdir) + { + if (file_subdir) + { + *subdir = concat_path(disk_subdir, file_subdir); + free(disk_subdir); + free(file_subdir); + } + else + { + *subdir = disk_subdir; + } + } + else + { + *subdir = file_subdir; + } + + return true; +} + +static void add_file(struct driver_package *package, + WCHAR *filename, WCHAR *desc, WCHAR *tag, WCHAR *subdir) +{ + struct file *file; + + array_reserve((void **)&package->files, &package->files_size, + package->file_count + 1, sizeof(*package->files)); + + file = &package->files[package->file_count++]; + file->filename = filename; + file->desc = desc; + file->tag = tag; + file->subdir = subdir; + + TRACE("Adding file %s, desc %s, tag %s, subdir %s.\n", + debugstr_w(filename), debugstr_w(desc), debugstr_w(tag), debugstr_w(subdir)); +} + +static void add_file_from_copy_section(struct driver_package *package, + const WCHAR *dst_filename, const WCHAR *src_filename) +{ + WCHAR *desc = NULL, *tag = NULL, *subdir = NULL; + + if (get_source_info(package->hinf, src_filename, &desc, &tag, &subdir)) + add_file(package, wcsdup(src_filename), desc, tag, subdir); +} + +static void add_copy_section(struct driver_package *package, const WCHAR *section) +{ + TRACE("Building file list from CopyFiles section %s.\n", debugstr_w(section)); + + if (section[0] == '@') + { + add_file_from_copy_section(package, section + 1, section + 1); + } + else + { + INFCONTEXT context; + + if (!SetupFindFirstLineW(package->hinf, section, NULL, &context)) + return; + do + { + WCHAR *dst_filename = get_string_field(&context, 1); + WCHAR *src_filename = get_string_field(&context, 2); + + add_file_from_copy_section(package, dst_filename, src_filename ? src_filename : dst_filename); + + free(dst_filename); + free(src_filename); + } while (SetupFindNextLine(&context, &context)); + } +} + +static void add_driver_files(struct driver_package *package, const WCHAR *driver_section) +{ + INFCONTEXT ctx; + BOOL found; + + TRACE("Building file list for driver section %s.\n", debugstr_w(driver_section)); + + found = SetupFindFirstLineW(package->hinf, driver_section, L"CopyFiles", &ctx); + while (found) + { + DWORD count = SetupGetFieldCount(&ctx); + + for (DWORD i = 1; i <= count; ++i) + { + WCHAR *section = get_string_field(&ctx, i); + + add_copy_section(package, section); + free(section); + } + + found = SetupFindNextMatchLineW(&ctx, L"CopyFiles", &ctx); + } +} + +static bool driver_store_files_are_equal(const WCHAR *src_path, const WCHAR *store_path) +{ + HANDLE src_file, store_file; + bool ret; + + src_file = CreateFileW(src_path, FILE_READ_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); + if (src_file == INVALID_HANDLE_VALUE) + { + ERR("Source file %s doesn't exist.\n", debugstr_w(src_path)); + return false; + } + + store_file = CreateFileW(store_path, FILE_READ_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); + if (store_file == INVALID_HANDLE_VALUE) + { + TRACE("File %s doesn't exist in driver store; this is the wrong package.\n", debugstr_w(store_path)); + CloseHandle(src_file); + return false; + } + + ret = !compare_files(src_file, store_file); + + CloseHandle(src_file); + CloseHandle(store_file); + return ret; +} + +static void find_driver_store_path(struct driver_package *package, const WCHAR *inf_path) +{ + static const WCHAR file_repository[] = L"C:\windows\system32\driverstore\filerepository"; + WCHAR *search_path = sprintf_path(L"%s\%s_*", file_repository, package->inf_name); + unsigned int index = 1; + WIN32_FIND_DATAW data; + HANDLE handle; + + /* Windows names directories using the inf name and what appears to be a + * hash, separated by an underscore. For simplicity we don't implement the + * hash; instead we just use an integer to discriminate different packages + * with the same name. */ + + handle = FindFirstFileW(search_path, &data); + free(search_path); + + if (handle != INVALID_HANDLE_VALUE) + { + do + { + const size_t prefix_len = wcslen(package->inf_name) + 1; + WCHAR *store_root, *end_ptr, *store_inf; + unsigned int dir_index; + + /* FindFirstFile() should have given us a name at least as long as + * the INF name followed by an underscore. */ + assert(wcslen(data.cFileName) >= prefix_len); + if (!(dir_index = wcstoul(data.cFileName + prefix_len, &end_ptr, 10))) + ERR("Malformed directory name %s.\n", debugstr_w(data.cFileName)); + index = max(index, dir_index + 1); + + store_root = concat_path(file_repository, data.cFileName); + store_inf = concat_path(store_root, package->inf_name); + + if (driver_store_files_are_equal(inf_path, store_inf)) + { + TRACE("Found matching driver package %s.\n", debugstr_w(store_root)); + free(store_inf); + FindClose(handle); + package->already_installed = true; + package->dst_root = store_root; + return; + } + + free(store_root); + } while (FindNextFileW(handle, &data)); + + FindClose(handle); + } + else + { + if (GetLastError() != ERROR_PATH_NOT_FOUND && GetLastError() != ERROR_FILE_NOT_FOUND) + { + ERR("Failed to enumerate file repository, error %lu.\n", GetLastError()); + return; + } + } + + package->dst_root = sprintf_path(L"%s\%s_%u", file_repository, package->inf_name, index); + TRACE("No matching driver package found; using new path %s.\n", debugstr_w(package->dst_root)); +} + +static DWORD parse_inf(struct driver_package *package, const WCHAR *filename) +{ + WCHAR mfg_key[LINE_LEN], manufacturer[LINE_LEN]; + WCHAR *filename_abs, *file_part, *catalog; + INFCONTEXT ctx; + DWORD len; + + memset(package, 0, sizeof(*package)); + + if (!*filename) + return ERROR_FILE_NOT_FOUND; + + len = GetFullPathNameW(filename, 0, NULL, NULL); + filename_abs = malloc(len * sizeof(WCHAR)); + GetFullPathNameW(filename, len, filename_abs, NULL); + + TRACE("Parsing %s.\n", debugstr_w(filename_abs)); + + if ((package->hinf = SetupOpenInfFileW(filename_abs, NULL, INF_STYLE_WIN4, NULL)) == INVALID_HANDLE_VALUE) + { + ERR("Failed to open %s, error %lu.\n", debugstr_w(filename_abs), GetLastError()); + driver_package_cleanup(package); + return GetLastError(); + } + + file_part = wcsrchr(filename_abs, '\'); + assert(file_part); + package->inf_name = file_part + 1; + + *file_part = 0; + package->src_root = filename_abs; + + add_file(package, wcsdup(package->inf_name), NULL, NULL, NULL); + + if (SetupFindFirstLineW(package->hinf, L"Version", L"CatalogFile", &ctx) + && (catalog = get_string_field(&ctx, 1))) + add_file(package, catalog, NULL, NULL, NULL); + + for (DWORD i = 0; SetupGetLineByIndexW(package->hinf, L"Manufacturer", i, &ctx); ++i) + { + SetupGetStringFieldW(&ctx, 0, manufacturer, ARRAY_SIZE(manufacturer), NULL); + if (!SetupGetStringFieldW(&ctx, 1, mfg_key, ARRAY_SIZE(mfg_key), NULL)) + wcscpy(mfg_key, manufacturer); + + if (!any_version_is_compatible(&ctx)) + continue; + + if (!SetupDiGetActualSectionToInstallW(package->hinf, mfg_key, mfg_key, ARRAY_SIZE(mfg_key), NULL, NULL)) + { + WARN("Failed to find section for %s, skipping.\n", debugstr_w(mfg_key)); + continue; + } + + for (DWORD j = 0; SetupGetLineByIndexW(package->hinf, mfg_key, j, &ctx); ++j) + { + WCHAR *driver_section = get_string_field(&ctx, 1); + WCHAR arch_driver_section[LINE_LEN]; + + if (SetupDiGetActualSectionToInstallW(package->hinf, driver_section, + arch_driver_section, ARRAY_SIZE(arch_driver_section), NULL, NULL)) + { + WCHAR coinst_section[LINE_LEN]; + + add_driver_files(package, arch_driver_section); + + swprintf(coinst_section, ARRAY_SIZE(coinst_section), L"%s.CoInstallers", arch_driver_section); + add_driver_files(package, coinst_section); + } + else + { + WARN("Failed to find driver section for %s, skipping.\n", debugstr_w(driver_section)); + } + + free(driver_section); + } + } + + find_driver_store_path(package, filename); + + return ERROR_SUCCESS; +} + static BOOL find_existing_inf(const WCHAR *source, WCHAR *target) { LARGE_INTEGER source_file_size, dest_file_size; @@ -5461,6 +5858,7 @@ static BOOL find_existing_inf(const WCHAR *source, WCHAR *target) CloseHandle(dest_file); CloseHandle(source_file); FindClose(find_handle); + TRACE("Found matching INF %s.\n", debugstr_w(target)); return TRUE; } CloseHandle(dest_file); @@ -5470,6 +5868,7 @@ static BOOL find_existing_inf(const WCHAR *source, WCHAR *target) }
CloseHandle(source_file); + TRACE("No matching INF found.\n"); return FALSE; }
@@ -5487,7 +5886,8 @@ static DWORD copy_inf(const WCHAR *source, DWORD style, WCHAR *ret_path) { TRACE("Found existing INF %s.\n", debugstr_w(target));
- wcscpy(ret_path, target); + if (ret_path) + wcscpy(ret_path, target); if (style & SP_COPY_NOOVERWRITE) return ERROR_FILE_EXISTS; else @@ -5570,10 +5970,78 @@ static DWORD copy_inf(const WCHAR *source, DWORD style, WCHAR *ret_path) fclose(pnf_file); }
- wcscpy(ret_path, target); + if (ret_path) + wcscpy(ret_path, target); return ERROR_SUCCESS; }
+static void queue_copy_file(const struct driver_package *package, HSPFILEQ queue, const struct file *file) +{ + SP_FILE_COPY_PARAMS_W params = + { + .cbSize = sizeof(params), + .QueueHandle = queue, + .SourceRootPath = package->src_root, + .CopyStyle = SP_COPY_NODECOMP, + .SourceFilename = file->filename, + .TargetFilename = file->filename, + }; + + params.SourceDescription = file->desc; + params.SourceTagfile = file->tag; + params.SourcePath = file->subdir; + + TRACE("Queueing copy from subdir %s, filename %s.\n", + debugstr_w(file->subdir), debugstr_w(file->filename)); + + if (file->subdir) + { + WCHAR *dst_dir = concat_path(package->dst_root, file->subdir); + + params.TargetDirectory = dst_dir; + if (!SetupQueueCopyIndirectW(¶ms)) + ERR("Failed to queue copy, error %lu.\n", GetLastError()); + free(dst_dir); + } + else + { + params.TargetDirectory = package->dst_root; + if (!SetupQueueCopyIndirectW(¶ms)) + ERR("Failed to queue copy, error %lu.\n", GetLastError()); + } +} + +static DWORD driver_package_install_to_store(const struct driver_package *package, DWORD style, WCHAR *infdir_path) +{ + HSPFILEQ queue = SetupOpenFileQueue(); + DWORD ret = ERROR_SUCCESS; + void *setupapi_ctx; + WCHAR *store_inf; + + for (size_t i = 0; i < package->file_count; ++i) + queue_copy_file(package, queue, &package->files[i]); + + setupapi_ctx = SetupInitDefaultQueueCallback(NULL); + if (!SetupCommitFileQueueW(NULL, queue, SetupDefaultQueueCallbackW, setupapi_ctx)) + { + ERR("Failed to commit queue, error %lu.\n", GetLastError()); + ret = GetLastError(); + } + + SetupTermDefaultQueueCallback(setupapi_ctx); + SetupCloseFileQueue(queue); + + if (!ret) + { + store_inf = concat_path(package->dst_root, package->inf_name); + if ((ret = copy_inf(store_inf, style, infdir_path))) + ERR("Failed to copy INF %s, error %lu.\n", debugstr_w(store_inf), GetLastError()); + free(store_inf); + } + + return ret; +} + /*********************************************************************** * SetupCopyOEMInfW (SETUPAPI.@) */ @@ -5672,3 +6140,104 @@ BOOL WINAPI SetupUninstallOEMInfW(const WCHAR *inf_file, DWORD flags, void *rese
return TRUE; } + +HRESULT WINAPI DriverStoreAddDriverPackageW(const WCHAR *inf_path, void *unk1, + void *unk2, WORD architecture, WCHAR *ret_path, DWORD *ret_len) +{ + struct driver_package package; + SYSTEM_INFO system_info; + DWORD ret, len; + HRESULT hr; + + TRACE("inf_path %s, unk1 %p, unk2 %p, architecture %#x, ret_path %p, ret_len %p.\n", + debugstr_w(inf_path), unk1, unk2, architecture, ret_path, ret_len); + + if (unk1) + FIXME("Ignoring unk1 %p.\n", unk1); + if (unk2) + FIXME("Ignoring unk2 %p.\n", unk2); + + if (*ret_len < MAX_PATH) + { + FIXME("Length %lu too short, returning E_INVALIDARG.\n", *ret_len); + return E_INVALIDARG; + } + + GetSystemInfo(&system_info); + if (architecture != system_info.wProcessorArchitecture) + { + FIXME("Wrong architecture %#x, expected %#x.\n", architecture, system_info.wProcessorArchitecture); + return E_INVALIDARG; + } + + if ((ret = parse_inf(&package, inf_path))) + return HRESULT_FROM_WIN32(ret); + + if (!package.already_installed) + { + if ((ret = driver_package_install_to_store(&package, 0, NULL))) + { + driver_package_cleanup(&package); + return HRESULT_FROM_WIN32(ret); + } + } + + len = wcslen(package.dst_root) + 1 + wcslen(package.inf_name) + 1; + + if (len > *ret_len) + { + FIXME("Buffer too small.\n"); + /* FIXME: What do we return here? */ + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else + { + swprintf(ret_path, len, L"%s\%s", package.dst_root, package.inf_name); + hr = S_OK; + } + *ret_len = len; + + driver_package_cleanup(&package); + return hr; +} + +HRESULT WINAPI DriverStoreAddDriverPackageA(const char *inf_path, void *unk1, + void *unk2, WORD architecture, char *ret_path, DWORD *ret_len) +{ + WCHAR ret_pathW[MAX_PATH]; + WCHAR *inf_pathW; + DWORD lenW, len; + HRESULT hr; + + TRACE("inf_path %s, unk1 %p, unk2 %p, architecture %#x, ret_path %p, ret_len %p.\n", + debugstr_a(inf_path), unk1, unk2, architecture, ret_path, ret_len); + + if (*ret_len < MAX_PATH) + { + FIXME("Length %lu too short, returning E_INVALIDARG.\n", *ret_len); + return E_INVALIDARG; + } + + if (!(inf_pathW = strdupAtoW(inf_path))) + return E_OUTOFMEMORY; + + lenW = ARRAY_SIZE(ret_pathW); + hr = DriverStoreAddDriverPackageW(inf_pathW, unk1, unk2, architecture, ret_pathW, &lenW); + if (!hr) + { + len = WideCharToMultiByte(CP_ACP, 0, ret_pathW, lenW, NULL, 0, NULL, NULL); + if (len > *ret_len) + { + FIXME("Buffer too small.\n"); + /* FIXME: What do we return here? */ + hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else + { + WideCharToMultiByte(CP_ACP, 0, ret_pathW, lenW, ret_path, len, NULL, NULL); + } + *ret_len = len; + } + free(inf_pathW); + return hr; +} diff --git a/dlls/setupapi/setupapi.spec b/dlls/setupapi/setupapi.spec index 4d144fe739b..5dddbe4fb5e 100644 --- a/dlls/setupapi/setupapi.spec +++ b/dlls/setupapi/setupapi.spec @@ -208,6 +208,8 @@ @ stub DelimStringToMultiSz @ stub DestroyTextFileReadBuffer @ stdcall DoesUserHavePrivilege(wstr) +@ stdcall DriverStoreAddDriverPackageA(ptr ptr ptr long ptr ptr) +@ stdcall DriverStoreAddDriverPackageW(ptr ptr ptr long ptr ptr) @ stdcall DuplicateString(wstr) @ stdcall EnablePrivilege(wstr long) @ stub ExtensionPropSheetPageProc