Signed-off-by: Zebediah Figura z.figura12@gmail.com --- dlls/setupapi/devinst.c | 72 ++++++++++++++++++- dlls/setupapi/tests/coinst.c | 20 ++++++ dlls/setupapi/tests/coinst.spec | 3 + dlls/setupapi/tests/devinst.c | 121 ++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 1 deletion(-)
diff --git a/dlls/setupapi/devinst.c b/dlls/setupapi/devinst.c index 281d7d3178..4d44e0086c 100644 --- a/dlls/setupapi/devinst.c +++ b/dlls/setupapi/devinst.c @@ -3565,19 +3565,73 @@ BOOL WINAPI SetupDiSetClassInstallParamsW( return FALSE; }
+static BOOL call_coinstallers(WCHAR *list, DI_FUNCTION function, HDEVINFO devinfo, SP_DEVINFO_DATA *device_data) +{ + DWORD (CALLBACK *coinst_proc)(DI_FUNCTION, HDEVINFO, SP_DEVINFO_DATA *, COINSTALLER_CONTEXT_DATA *); + COINSTALLER_CONTEXT_DATA coinst_ctx; + WCHAR *p, *procnameW; + HMODULE module; + char *procname; + DWORD ret; + + for (p = list; *p; p += strlenW(p) + 1) + { + TRACE("Found co-installer %s.\n", debugstr_w(p)); + if ((procnameW = strchrW(p, ','))) + *procnameW = 0; + + if ((module = LoadLibraryExW(p, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) + { + if (procnameW) + { + procname = strdupWtoA(procnameW + 1); + coinst_proc = (void *)GetProcAddress(module, procname); + heap_free(procname); + } + else + coinst_proc = (void *)GetProcAddress(module, "CoDeviceInstall"); + if (coinst_proc) + { + memset(&coinst_ctx, 0, sizeof(coinst_ctx)); + TRACE("Calling co-installer %p.\n", coinst_proc); + ret = coinst_proc(function, devinfo, device_data, &coinst_ctx); + TRACE("Co-installer %p returned %#x.\n", coinst_proc, ret); + if (ret == ERROR_DI_POSTPROCESSING_REQUIRED) + FIXME("Co-installer postprocessing not implemented.\n"); + else if (ret) + { + ERR("Co-installer returned error %#x.\n", ret); + FreeLibrary(module); + SetLastError(ret); + return FALSE; + } + } + FreeLibrary(module); + } + } + + return TRUE; +} + /*********************************************************************** * SetupDiCallClassInstaller (SETUPAPI.@) */ BOOL WINAPI SetupDiCallClassInstaller(DI_FUNCTION function, HDEVINFO devinfo, SP_DEVINFO_DATA *device_data) { + static const WCHAR class_coinst_pathW[] = {'S','y','s','t','e','m', + '\','C','u','r','r','e','n','t','C','o','n','t','r','o','l','S','e','t', + '\','C','o','n','t','r','o','l', + '\','C','o','D','e','v','i','c','e','I','n','s','t','a','l','l','e','r','s',0}; static const WCHAR installer32W[] = {'I','n','s','t','a','l','l','e','r','3','2',0}; DWORD (CALLBACK *classinst_proc)(DI_FUNCTION, HDEVINFO, SP_DEVINFO_DATA *); DWORD ret = ERROR_DI_DO_DEFAULT; + HKEY class_key, coinst_key; WCHAR *path, *procnameW; struct device *device; + WCHAR guidstr[39]; + BOOL coret = TRUE; HMODULE module; char *procname; - HKEY class_key; DWORD size;
TRACE("function %#x, devinfo %p, device_data %p.\n", function, devinfo, device_data); @@ -3585,6 +3639,22 @@ BOOL WINAPI SetupDiCallClassInstaller(DI_FUNCTION function, HDEVINFO devinfo, SP if (!(device = get_device(devinfo, device_data))) return FALSE;
+ if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, class_coinst_pathW, 0, KEY_READ, &coinst_key)) + { + SETUPDI_GuidToString(&device->class, guidstr); + if (!RegGetValueW(coinst_key, NULL, guidstr, RRF_RT_REG_MULTI_SZ, NULL, NULL, &size)) + { + path = heap_alloc(size); + if (!RegGetValueW(coinst_key, NULL, guidstr, RRF_RT_REG_MULTI_SZ, NULL, path, &size)) + coret = call_coinstallers(path, function, devinfo, device_data); + heap_free(path); + } + RegCloseKey(coinst_key); + } + + if (!coret) + return FALSE; + if ((class_key = SetupDiOpenClassRegKey(&device->class, KEY_READ)) != INVALID_HANDLE_VALUE) { if (!RegGetValueW(class_key, NULL, installer32W, RRF_RT_REG_SZ, NULL, NULL, &size)) diff --git a/dlls/setupapi/tests/coinst.c b/dlls/setupapi/tests/coinst.c index dd64b15cca..5e8d890cfa 100644 --- a/dlls/setupapi/tests/coinst.c +++ b/dlls/setupapi/tests/coinst.c @@ -49,3 +49,23 @@ DWORD WINAPI class_error(DI_FUNCTION function, HDEVINFO set, SP_DEVINFO_DATA *de { return 0xdeadbeef; } + +DWORD WINAPI co_success(DI_FUNCTION function, HDEVINFO set, SP_DEVINFO_DATA *device, + COINSTALLER_CONTEXT_DATA *context) +{ + callback_count++; + last_message = function; + return ERROR_SUCCESS; +} + +DWORD WINAPI CoDeviceInstall(DI_FUNCTION function, HDEVINFO set, SP_DEVINFO_DATA *device, + COINSTALLER_CONTEXT_DATA *context) +{ + return co_success(function, set, device, context); +} + +DWORD WINAPI co_error(DI_FUNCTION function, HDEVINFO set, SP_DEVINFO_DATA *device, + COINSTALLER_CONTEXT_DATA *context) +{ + return 0xdeadbeef; +} diff --git a/dlls/setupapi/tests/coinst.spec b/dlls/setupapi/tests/coinst.spec index 34a5fdde6d..bbfd316b4b 100644 --- a/dlls/setupapi/tests/coinst.spec +++ b/dlls/setupapi/tests/coinst.spec @@ -2,5 +2,8 @@ @ stdcall ClassInstall(long ptr ptr) @ stdcall class_default(long ptr ptr) @ stdcall class_error(long ptr ptr) +@ stdcall co_success(long ptr ptr ptr) +@ stdcall CoDeviceInstall(long ptr ptr ptr) +@ stdcall co_error(long ptr ptr ptr) @ extern callback_count @ extern last_message diff --git a/dlls/setupapi/tests/devinst.c b/dlls/setupapi/tests/devinst.c index 0bd9a5bf73..abf6fe9ed8 100644 --- a/dlls/setupapi/tests/devinst.c +++ b/dlls/setupapi/tests/devinst.c @@ -2515,6 +2515,126 @@ static void test_class_installer(void) RegCloseKey(class_key); }
+static void test_class_coinstaller(void) +{ + SP_DEVINFO_DATA device = {sizeof(device)}; + char regdata[200]; + HKEY coinst_key; + HDEVINFO set; + BOOL ret; + LONG res; + + res = RegCreateKeyA(HKEY_LOCAL_MACHINE, "System\CurrentControlSet\Control\CoDeviceInstallers", &coinst_key); + ok(!res, "Failed to open CoDeviceInstallers key, error %u.\n", res); + strcpy(regdata, "winetest_coinst.dll,co_success"); + regdata[strlen(regdata) + 1] = 0; + res = RegSetValueExA(coinst_key, "{6a55b5a4-3f65-11db-b704-0011955c2bdb}", 0, + REG_MULTI_SZ, (BYTE *)regdata, strlen(regdata) + 2); + ok(!res, "Failed to set registry value, error %u.\n", res); + + /* We must recreate the device list, or Windows will not recognize that the + * class co-installer exists. */ + set = SetupDiCreateDeviceInfoList(&guid, NULL); + ok(set != INVALID_HANDLE_VALUE, "Failed to create device list, error %#x.\n", GetLastError()); + ret = SetupDiCreateDeviceInfoA(set, "Root\LEGACY_BOGUS\0000", &guid, NULL, NULL, 0, &device); + ok(ret, "Failed to create device, error %#x.\n", GetLastError()); + + ret = SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, set, &device); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == ERROR_DI_DO_DEFAULT, "Got unexpected error %#x.\n", GetLastError()); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == DIF_ALLOW_INSTALL, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + ret = SetupDiCallClassInstaller(0xdeadbeef, set, &device); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == ERROR_DI_DO_DEFAULT, "Got unexpected error %#x.\n", GetLastError()); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == 0xdeadbeef, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + ok(!device_is_registered(set, &device), "Expected device not to be registered.\n"); + ret = SetupDiCallClassInstaller(DIF_REGISTERDEVICE, set, &device); + ok(ret, "Failed to call class installer, error %#x.\n", GetLastError()); + ok(device_is_registered(set, &device), "Expected device to be registered.\n"); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == DIF_REGISTERDEVICE, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + ret = SetupDiCallClassInstaller(DIF_REMOVE, set, &device); + ok(ret, "Failed to call class installer, error %#x.\n", GetLastError()); + ok(!device_is_registered(set, &device), "Expected device not to be registered.\n"); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == DIF_REMOVE, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + SetupDiDestroyDeviceInfoList(set); + + todo_wine ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + todo_wine ok(*coinst_last_message == DIF_DESTROYPRIVATEDATA, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + /* Test returning an error from the co-installer. */ + + strcpy(regdata, "winetest_coinst.dll,co_error"); + regdata[strlen(regdata) + 1] = 0; + res = RegSetValueExA(coinst_key, "{6a55b5a4-3f65-11db-b704-0011955c2bdb}", 0, + REG_MULTI_SZ, (BYTE *)regdata, strlen(regdata) + 2); + ok(!res, "Failed to set registry value, error %u.\n", res); + + set = SetupDiCreateDeviceInfoList(&guid, NULL); + ok(set != INVALID_HANDLE_VALUE, "Failed to create device list, error %#x.\n", GetLastError()); + ret = SetupDiCreateDeviceInfoA(set, "Root\LEGACY_BOGUS\0000", &guid, NULL, NULL, 0, &device); + ok(ret, "Failed to create device, error %#x.\n", GetLastError()); + + ret = SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, set, &device); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == 0xdeadbeef, "Got unexpected error %#x.\n", GetLastError()); + + ok(!device_is_registered(set, &device), "Expected device not to be registered.\n"); + ret = SetupDiCallClassInstaller(DIF_REGISTERDEVICE, set, &device); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == 0xdeadbeef, "Got unexpected error %#x.\n", GetLastError()); + ok(!device_is_registered(set, &device), "Expected device not to be registered.\n"); + + SetupDiDestroyDeviceInfoList(set); + + /* The default entry point is CoDeviceInstall(). */ + + strcpy(regdata, "winetest_coinst.dll"); + regdata[strlen(regdata) + 1] = 0; + res = RegSetValueExA(coinst_key, "{6a55b5a4-3f65-11db-b704-0011955c2bdb}", 0, + REG_MULTI_SZ, (BYTE *)regdata, strlen(regdata) + 2); + ok(!res, "Failed to set registry value, error %u.\n", res); + + set = SetupDiCreateDeviceInfoList(&guid, NULL); + ok(set != INVALID_HANDLE_VALUE, "Failed to create device list, error %#x.\n", GetLastError()); + ret = SetupDiCreateDeviceInfoA(set, "Root\LEGACY_BOGUS\0000", &guid, NULL, NULL, 0, &device); + ok(ret, "Failed to create device, error %#x.\n", GetLastError()); + + ret = SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, set, &device); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == ERROR_DI_DO_DEFAULT, "Got unexpected error %#x.\n", GetLastError()); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == DIF_ALLOW_INSTALL, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + SetupDiDestroyDeviceInfoList(set); + + ok(*coinst_callback_count == 1, "Got %d callbacks.\n", *coinst_callback_count); + ok(*coinst_last_message == DIF_DESTROYPRIVATEDATA, "Got unexpected message %#x.\n", *coinst_last_message); + *coinst_callback_count = 0; + + res = RegDeleteValueA(coinst_key, "{6a55b5a4-3f65-11db-b704-0011955c2bdb}"); + ok(!res, "Failed to delete value, error %u.\n", res); + RegCloseKey(coinst_key); +} + static void test_call_class_installer(void) { SP_DEVINFO_DATA device = {sizeof(device)}; @@ -2561,6 +2681,7 @@ static void test_call_class_installer(void) coinst_last_message = (void *)GetProcAddress(coinst, "last_message");
test_class_installer(); + test_class_coinstaller();
FreeLibrary(coinst);