diff --git a/idd/LGIdd/LGIdd.inf b/idd/LGIdd/LGIdd.inf index 36688cea..160a6b8b 100644 Binary files a/idd/LGIdd/LGIdd.inf and b/idd/LGIdd/LGIdd.inf differ diff --git a/idd/LGIddInstall/LGIddInstall.c b/idd/LGIddInstall/LGIddInstall.c index b789877b..23d808b8 100644 --- a/idd/LGIddInstall/LGIddInstall.c +++ b/idd/LGIddInstall/LGIddInstall.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,8 @@ #include #include +#pragma comment(lib, "cfgmgr32.lib") + #define LGIDD_CLASS_GUID GUID_DEVCLASS_DISPLAY #define LGIDD_CLASS_NAME L"Display" #define LGIDD_NAME L"LGIdd" @@ -49,7 +52,12 @@ #define LGINPUT_INF_NAME L"LGInput.inf" #define LGINPUT_PACKAGE_DIR L"LGInput" #define LGIDD_REGKEY L"Software\\LookingGlass\\IDD" +#define LGIDD_HELPER_SERVICE L"LGIddHelper" #define DEVICE_COUNT 2 +#define STATE_WAIT_TIMEOUT_MS 30000 +#define EXIT_RESTART_REQUIRED 12 +// The operation failed, but a partial change or rollback still needs reboot. +#define EXIT_FAILURE_RESTART_REQUIRED 13 typedef struct DeviceDesc { @@ -131,6 +139,206 @@ void debugWinError(const wchar_t *desc, HRESULT status) LocalFree(buffer); } +void debugConfigError(const wchar_t *desc, CONFIGRET status) +{ + debugWinError(desc, CM_MapCrToWin32Err(status, ERROR_GEN_FAILURE)); +} + +bool queryServiceStatus( + SC_HANDLE service, + SERVICE_STATUS_PROCESS *status) +{ + DWORD bytes; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, + (LPBYTE) status, sizeof(*status), &bytes)) + { + debugWinError(L"QueryServiceStatusEx", GetLastError()); + return false; + } + return true; +} + +bool waitForServiceState(SC_HANDLE service, DWORD desiredState) +{ + const ULONGLONG deadline = GetTickCount64() + STATE_WAIT_TIMEOUT_MS; + for (;;) + { + SERVICE_STATUS_PROCESS status; + if (!queryServiceStatus(service, &status)) + return false; + if (status.dwCurrentState == desiredState) + return true; + + if (GetTickCount64() >= deadline) + { + debugWinError(L"Timed out waiting for LGIddHelper", ERROR_TIMEOUT); + return false; + } + + DWORD delay = status.dwWaitHint / 10; + if (delay < 100) + delay = 100; + else if (delay > 1000) + delay = 1000; + Sleep(delay); + } +} + +bool stopHelperService(bool *existed, bool *wasRunning) +{ + if (existed) + *existed = false; + *wasRunning = false; + + SC_HANDLE manager = OpenSCManagerW( + NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + debugWinError(L"OpenSCManagerW", GetLastError()); + return false; + } + + SC_HANDLE service = OpenServiceW(manager, LGIDD_HELPER_SERVICE, + SERVICE_QUERY_STATUS | SERVICE_STOP); + if (!service) + { + const DWORD error = GetLastError(); + CloseServiceHandle(manager); + if (error == ERROR_SERVICE_DOES_NOT_EXIST) + return true; + debugWinError(L"OpenServiceW(LGIddHelper)", error); + return false; + } + if (existed) + *existed = true; + + SERVICE_STATUS_PROCESS status; + bool result = queryServiceStatus(service, &status); + if (result && status.dwCurrentState != SERVICE_STOPPED) + { + *wasRunning = status.dwCurrentState != SERVICE_STOP_PENDING; + if (status.dwCurrentState == SERVICE_START_PENDING) + result = waitForServiceState(service, SERVICE_RUNNING); + + if (result && status.dwCurrentState != SERVICE_STOP_PENDING) + { + SERVICE_STATUS controlStatus; + if (!ControlService(service, SERVICE_CONTROL_STOP, &controlStatus)) + { + const DWORD error = GetLastError(); + if (error != ERROR_SERVICE_NOT_ACTIVE) + { + debugWinError(L"ControlService(LGIddHelper, STOP)", error); + result = false; + } + } + } + + if (result) + result = waitForServiceState(service, SERVICE_STOPPED); + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + return result; +} + +bool deleteHelperService() +{ + SC_HANDLE manager = OpenSCManagerW( + NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + debugWinError(L"OpenSCManagerW", GetLastError()); + return false; + } + + SC_HANDLE service = OpenServiceW(manager, LGIDD_HELPER_SERVICE, + DELETE); + if (!service) + { + const DWORD error = GetLastError(); + CloseServiceHandle(manager); + if (error == ERROR_SERVICE_DOES_NOT_EXIST) + return true; + debugWinError(L"OpenServiceW(LGIddHelper, DELETE)", error); + return false; + } + + bool result = true; + if (!DeleteService(service)) + { + const DWORD error = GetLastError(); + if (error != ERROR_SERVICE_MARKED_FOR_DELETE) + { + debugWinError(L"DeleteService(LGIddHelper)", error); + result = false; + } + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + return result; +} + +bool startHelperService() +{ + SC_HANDLE manager = OpenSCManagerW( + NULL, NULL, SC_MANAGER_CONNECT); + if (!manager) + { + debugWinError(L"OpenSCManagerW", GetLastError()); + return false; + } + + SC_HANDLE service = OpenServiceW(manager, LGIDD_HELPER_SERVICE, + SERVICE_QUERY_STATUS | SERVICE_START); + if (!service) + { + const DWORD error = GetLastError(); + CloseServiceHandle(manager); + debugWinError(L"OpenServiceW(LGIddHelper)", error); + return false; + } + + bool result = true; + SERVICE_STATUS_PROCESS status; + if (!queryServiceStatus(service, &status)) + result = false; + else if (status.dwCurrentState != SERVICE_RUNNING) + { + if (status.dwCurrentState == SERVICE_STOP_PENDING) + result = waitForServiceState(service, SERVICE_STOPPED); + + if (result && status.dwCurrentState != SERVICE_START_PENDING && + !StartServiceW(service, 0, NULL)) + { + const DWORD error = GetLastError(); + if (error != ERROR_SERVICE_ALREADY_RUNNING) + { + debugWinError(L"StartServiceW(LGIddHelper)", error); + result = false; + } + } + + if (result) + result = waitForServiceState(service, SERVICE_RUNNING); + } + + CloseServiceHandle(service); + CloseServiceHandle(manager); + return result; +} + +void logRestart( + const DeviceDesc *device, + const wchar_t *operation, + BOOL needRestart) +{ + wprintf(L"%s: %s completed; restart required: %s\n", + device->name, operation, needRestart ? L"yes" : L"no"); +} + bool ensureKeyWithAce() { bool result = false; @@ -253,8 +461,10 @@ typedef bool (*DEVICE_FOUND_PROC)(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfo, typedef struct DeviceRemovalContext { + const DeviceDesc *device; LPBOOL pbNeedRestart; bool success; + bool deviceRemoved; } DeviceRemovalContext; @@ -328,29 +538,190 @@ fail: return false; } -enum DeviceCreated { - DEVICE_CREATED, - DEVICE_NOT_CREATED, - DEVICE_UNKNOWN, -}; - -bool isDeviceCreatedEnum(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfo, void *pContext) +typedef struct DeviceRuntimeState { - enum DeviceCreated *result = pContext; - *result = DEVICE_CREATED; + const DeviceDesc *device; + bool success; + bool present; + bool started; + bool usable; + bool disabled; + bool created; + bool rollbackDeviceSafe; + WCHAR instanceId[MAX_DEVICE_ID_LEN]; + WCHAR stagedInfPath[MAX_PATH]; +} +DeviceRuntimeState; + +bool captureDeviceStateEnum( + HDEVINFO hDevInfo, + PSP_DEVINFO_DATA pDevInfo, + void *pContext) +{ + DeviceRuntimeState *state = pContext; + if (!SetupDiGetDeviceInstanceIdW(hDevInfo, pDevInfo, + state->instanceId, ARRAYSIZE(state->instanceId), NULL)) + { + debugWinError(L"SetupDiGetDeviceInstanceIdW", GetLastError()); + state->success = false; + return false; + } + + ULONG status; + ULONG problem; + const CONFIGRET cr = CM_Get_DevNode_Status( + &status, &problem, pDevInfo->DevInst, 0); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Get_DevNode_Status", cr); + state->success = false; + return false; + } + + state->present = true; + state->started = (status & DN_STARTED) != 0; + state->usable = state->started && + !(status & (DN_HAS_PROBLEM | DN_WILL_BE_REMOVED)); return false; } -enum DeviceCreated isDeviceCreated(const DeviceDesc *device) +bool captureDeviceState( + const DeviceDesc *device, + DeviceRuntimeState *state) { - enum DeviceCreated result = DEVICE_UNKNOWN; - if (findDevice(device, isDeviceCreatedEnum, &result) && result == DEVICE_UNKNOWN) - result = DEVICE_NOT_CREATED; - return result; + ZeroMemory(state, sizeof(*state)); + state->device = device; + state->success = true; + return findDevice(device, captureDeviceStateEnum, state) && + state->success; } -bool createDevice(const DeviceDesc *device) +bool deviceIsUsable(const DeviceDesc *device) { + DeviceRuntimeState state; + return captureDeviceState(device, &state) && + state.present && state.usable; +} + +bool waitForDeviceState( + const DeviceRuntimeState *state, + bool started) +{ + const ULONGLONG deadline = GetTickCount64() + STATE_WAIT_TIMEOUT_MS; + for (;;) + { + DEVINST devInst; + CONFIGRET cr = CM_Locate_DevNodeW( + &devInst, (DEVINSTID_W) state->instanceId, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Locate_DevNodeW", cr); + return false; + } + + ULONG status; + ULONG problem; + cr = CM_Get_DevNode_Status(&status, &problem, devInst, 0); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Get_DevNode_Status", cr); + return false; + } + if (((status & DN_STARTED) != 0) == started) + return true; + + if (GetTickCount64() >= deadline) + { + fwprintf(stderr, L"%s: timed out waiting for device to %s\n", + state->device->name, started ? L"start" : L"stop"); + return false; + } + Sleep(100); + } +} + +bool disableDevice(DeviceRuntimeState *state) +{ + if (!state->present || !state->started) + return true; + + DEVINST devInst; + CONFIGRET cr = CM_Locate_DevNodeW( + &devInst, state->instanceId, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Locate_DevNodeW", cr); + return false; + } + + wprintf(L"Temporarily disabling %s...\n", state->device->name); + cr = CM_Disable_DevNode(devInst, CM_DISABLE_UI_NOT_OK); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Disable_DevNode", cr); + return false; + } + + state->disabled = true; + return waitForDeviceState(state, false); +} + +bool enableDevice(DeviceRuntimeState *state) +{ + if (!state->disabled) + return true; + + DEVINST devInst; + CONFIGRET cr = CM_Locate_DevNodeW( + &devInst, state->instanceId, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Locate_DevNodeW", cr); + return false; + } + + wprintf(L"Re-enabling %s...\n", state->device->name); + cr = CM_Enable_DevNode(devInst, 0); + if (cr != CR_SUCCESS) + { + debugConfigError(L"CM_Enable_DevNode", cr); + return false; + } + + if (!waitForDeviceState(state, true)) + return false; + state->disabled = false; + return true; +} + +bool enableDevices( + DeviceRuntimeState *idd, + DeviceRuntimeState *input) +{ + if (!enableDevice(idd)) + return false; + + if (input->disabled && !deviceIsUsable(&LGIDD_DEVICE)) + { + fwprintf(stderr, + L"LGInput remains disabled because LGIdd is not usable\n"); + return false; + } + + return enableDevice(input); +} + +DeviceRuntimeState *runtimeStateForDevice( + const DeviceDesc *device, + DeviceRuntimeState *idd, + DeviceRuntimeState *input) +{ + return device == &LGIDD_DEVICE ? idd : input; +} + +bool createDevice(DeviceRuntimeState *state) +{ + const DeviceDesc *device = state->device; HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList(device->classGuid, NULL); if (hDevInfo == INVALID_HANDLE_VALUE) { @@ -372,12 +743,20 @@ bool createDevice(const DeviceDesc *device) goto fail; } + if (!SetupDiGetDeviceInstanceIdW(hDevInfo, &devInfo, + state->instanceId, ARRAYSIZE(state->instanceId), NULL)) + { + debugWinError(L"SetupDiGetDeviceInstanceIdW", GetLastError()); + goto fail; + } + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDevInfo, &devInfo)) { debugWinError(L"SetupDiCallClassInstaller", GetLastError()); goto fail; } + SetupDiDestroyDeviceInfoList(hDevInfo); return true; fail: @@ -388,34 +767,69 @@ fail: bool destroyDeviceEnum(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfo, void *pContext) { DeviceRemovalContext *context = pContext; - BOOL bNeedRestart; + BOOL bNeedRestart = FALSE; WCHAR szInfPath[MAX_PATH] = { 0 }; + SP_DEVINSTALL_PARAMS_W installParams = + { .cbSize = sizeof installParams }; + if (!SetupDiGetDeviceInstallParamsW(hDevInfo, pDevInfo, + &installParams)) + { + debugWinError(L"SetupDiGetDeviceInstallParamsW", GetLastError()); + context->success = false; + goto uninstall; + } + + installParams.FlagsEx |= DI_FLAGSEX_INSTALLEDDRIVER; + if (!SetupDiSetDeviceInstallParamsW(hDevInfo, pDevInfo, + &installParams)) + { + debugWinError(L"SetupDiSetDeviceInstallParamsW", GetLastError()); + context->success = false; + goto uninstall; + } + if (!SetupDiBuildDriverInfoList(hDevInfo, pDevInfo, SPDIT_COMPATDRIVER)) { debugWinError(L"SetupDiBuildDriverInfoList", GetLastError()); + context->success = false; goto uninstall; } SP_DRVINFO_DATA_W drvInfo = { .cbSize = sizeof drvInfo }; - if (!SetupDiEnumDriverInfoW(hDevInfo, pDevInfo, SPDIT_COMPATDRIVER, 0, &drvInfo)) + if (!SetupDiEnumDriverInfoW(hDevInfo, pDevInfo, + SPDIT_COMPATDRIVER, 0, &drvInfo)) { - debugWinError(L"SetupDiEnumDriverInfoW", GetLastError()); + const DWORD error = GetLastError(); + if (error == ERROR_NO_MORE_ITEMS) + goto uninstall; + debugWinError(L"SetupDiEnumDriverInfoW", error); + context->success = false; goto uninstall; } - SP_DRVINFO_DETAIL_DATA_W drvInfoDetail = { .cbSize = sizeof drvInfoDetail }; - SetupDiGetDriverInfoDetailW(hDevInfo, pDevInfo, &drvInfo, &drvInfoDetail, sizeof drvInfoDetail, NULL); - - DWORD dwLastError = GetLastError(); - if (dwLastError == ERROR_INSUFFICIENT_BUFFER) - wcscpy_s(szInfPath, MAX_PATH, drvInfoDetail.InfFileName); + SP_DRVINFO_DETAIL_DATA_W drvInfoDetail = + { .cbSize = sizeof drvInfoDetail }; + if (SetupDiGetDriverInfoDetailW(hDevInfo, pDevInfo, &drvInfo, + &drvInfoDetail, sizeof drvInfoDetail, NULL) || + GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + wcscpy_s(szInfPath, ARRAYSIZE(szInfPath), + drvInfoDetail.InfFileName); + } else - debugWinError(L"SetupDiEnumDriverInfoW", GetLastError()); + { + debugWinError(L"SetupDiGetDriverInfoDetailW", GetLastError()); + context->success = false; + } uninstall: if (DiUninstallDevice(NULL, hDevInfo, pDevInfo, 0, &bNeedRestart)) + { + logRestart(context->device, L"DiUninstallDevice", bNeedRestart); *context->pbNeedRestart |= bNeedRestart; + context->deviceRemoved = true; + } else { debugWinError(L"DiUninstallDevice", GetLastError()); @@ -425,8 +839,12 @@ uninstall: if (*szInfPath) { + bNeedRestart = FALSE; if (DiUninstallDriverW(NULL, szInfPath, 0, &bNeedRestart)) + { + logRestart(context->device, L"DiUninstallDriverW", bNeedRestart); *context->pbNeedRestart |= bNeedRestart; + } else { debugWinError(L"DiUninstallDriverW", GetLastError()); @@ -437,11 +855,23 @@ uninstall: return true; } -bool destroyDevice(const DeviceDesc *device, LPBOOL pbNeedRestart) +bool destroyDevice( + const DeviceDesc *device, + LPBOOL pbNeedRestart, + bool *deviceRemoved) { - DeviceRemovalContext context = { pbNeedRestart, true }; + DeviceRemovalContext context = + { + .device = device, + .pbNeedRestart = pbNeedRestart, + .success = true, + }; - return findDevice(device, destroyDeviceEnum, &context) && context.success; + const bool found = findDevice(device, destroyDeviceEnum, &context); + if (deviceRemoved) + *deviceRemoved = context.deviceRemoved; + + return found && context.success; } bool getInfPath(const DeviceDesc *device, LPWSTR lpszInf) @@ -481,6 +911,43 @@ bool getInfPath(const DeviceDesc *device, LPWSTR lpszInf) return true; } +bool stageInf(DeviceRuntimeState *state) +{ + WCHAR sourceInf[MAX_PATH]; + state->stagedInfPath[0] = 0; + + if (!getInfPath(state->device, sourceInf)) + return false; + + // NOOVERWRITE distinguishes packages introduced by this transaction + // from packages that must survive a failed fresh installation. + if (SetupCopyOEMInfW(sourceInf, NULL, SPOST_PATH, + SP_COPY_NOOVERWRITE, state->stagedInfPath, + ARRAYSIZE(state->stagedInfPath), NULL, NULL)) + { + wprintf(L"%s: staged new package as %s\n", + state->device->name, state->stagedInfPath); + return true; + } + + const DWORD error = GetLastError(); + if (error == ERROR_FILE_EXISTS) + { + if (*state->stagedInfPath) + wprintf(L"%s: package already staged as %s\n", + state->device->name, state->stagedInfPath); + else + wprintf(L"%s: package is already staged\n", + state->device->name); + state->stagedInfPath[0] = 0; + return true; + } + + state->stagedInfPath[0] = 0; + debugWinError(L"SetupCopyOEMInfW", error); + return false; +} + bool installInf(const DeviceDesc *device, PBOOL pbNeedRestart) { WCHAR szInf[MAX_PATH]; @@ -497,65 +964,274 @@ bool installInf(const DeviceDesc *device, PBOOL pbNeedRestart) return true; } +bool removeCreatedDevice( + const DeviceRuntimeState *state, + LPBOOL pbNeedRestart) +{ + HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList( + state->device->classGuid, NULL); + if (hDevInfo == INVALID_HANDLE_VALUE) + { + debugWinError(L"SetupDiCreateDeviceInfoList", GetLastError()); + return false; + } + + SP_DEVINFO_DATA devInfo = { .cbSize = sizeof devInfo }; + if (!SetupDiOpenDeviceInfoW(hDevInfo, state->instanceId, + NULL, 0, &devInfo)) + { + const DWORD error = GetLastError(); + SetupDiDestroyDeviceInfoList(hDevInfo); + if (error == ERROR_NO_SUCH_DEVINST) + return true; + debugWinError(L"SetupDiOpenDeviceInfoW", error); + return false; + } + + BOOL callNeedRestart = FALSE; + if (!DiUninstallDevice(NULL, hDevInfo, &devInfo, 0, + &callNeedRestart)) + { + const DWORD error = GetLastError(); + SetupDiDestroyDeviceInfoList(hDevInfo); + debugWinError(L"DiUninstallDevice (rollback)", error); + return false; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + logRestart(state->device, L"DiUninstallDevice (rollback)", + callNeedRestart); + *pbNeedRestart |= callNeedRestart; + return true; +} + +bool rollbackCreatedDevice( + DeviceRuntimeState *state, + LPBOOL pbNeedRestart) +{ + if (!state->created) + return true; + + wprintf(L"Rolling back %s device...\n", state->device->name); + if (!removeCreatedDevice(state, pbNeedRestart)) + return false; + + state->rollbackDeviceSafe = true; + state->present = false; + return true; +} + +bool rollbackStagedPackage( + DeviceRuntimeState *state, + LPBOOL pbNeedRestart) +{ + if (!*state->stagedInfPath) + return true; + + if (state->created && !state->rollbackDeviceSafe) + { + fwprintf(stderr, + L"%s: retaining newly staged package because device rollback " + L"failed\n", state->device->name); + return false; + } + + BOOL callNeedRestart = FALSE; + wprintf(L"Rolling back %s package...\n", state->device->name); + if (!DiUninstallDriverW(NULL, state->stagedInfPath, 0, + &callNeedRestart)) + { + debugWinError(L"DiUninstallDriverW (rollback)", GetLastError()); + return false; + } + + logRestart(state->device, L"DiUninstallDriverW (rollback)", + callNeedRestart); + *pbNeedRestart |= callNeedRestart; + state->stagedInfPath[0] = 0; + return true; +} + +bool rollbackInstall( + DeviceRuntimeState *idd, + DeviceRuntimeState *input, + LPBOOL pbNeedRestart) +{ + bool success = true; + + const bool inputDeviceSafe = + rollbackCreatedDevice(input, pbNeedRestart); + if (!inputDeviceSafe) + success = false; + + if (inputDeviceSafe) + { + if (!rollbackCreatedDevice(idd, pbNeedRestart)) + success = false; + } + else if (idd->created) + { + fwprintf(stderr, + L"LGIdd: retaining newly created device because LGInput " + L"rollback failed\n"); + } + + bool packageOrderSafe = inputDeviceSafe; + if (packageOrderSafe) + { + if (!rollbackStagedPackage(input, pbNeedRestart)) + { + packageOrderSafe = false; + success = false; + } + } + else if (*input->stagedInfPath) + { + fwprintf(stderr, + L"LGInput: retaining newly staged package because device " + L"rollback failed\n"); + } + + if (packageOrderSafe) + { + if (!rollbackStagedPackage(idd, pbNeedRestart)) + success = false; + } + else if (*idd->stagedInfPath) + { + fwprintf(stderr, + L"LGIdd: retaining newly staged package because LGInput package " + L"rollback failed\n"); + success = false; + } + + return success; +} + void install(const DeviceDesc *const devices[DEVICE_COUNT]) { - enum DeviceCreated created[DEVICE_COUNT]; + DeviceRuntimeState idd; + DeviceRuntimeState input; + bool helperExisted = false; + bool helperWasRunning = false; + bool installSucceeded = false; + bool rollbackSucceeded = true; + bool restoreSucceeded; + BOOL needRestart = FALSE; - for (size_t i = 0; i < DEVICE_COUNT; ++i) - { - created[i] = isDeviceCreated(devices[i]); - if (created[i] == DEVICE_UNKNOWN) - exit(1); - } + if (!captureDeviceState(&LGIDD_DEVICE, &idd) || + !captureDeviceState(&LGINPUT_DEVICE, &input)) + exit(1); + + _putws(L"Stopping LGIddHelper..."); + if (!stopHelperService(&helperExisted, &helperWasRunning)) + exit(1); + + // LGInput consumes LGIdd's input pipe, so quiesce the dependent device + // first and restore the provider first. + if (!disableDevice(&input) || !disableDevice(&idd)) + goto cleanup; _putws(L"Preparing registry key..."); if (!ensureKeyWithAce()) - exit(1); - - BOOL bNeedRestart = FALSE; - BOOL bInfNeedRestart = FALSE; + goto cleanup; for (size_t i = 0; i < DEVICE_COUNT; ++i) { const DeviceDesc *device = devices[i]; + DeviceRuntimeState *state = runtimeStateForDevice( + device, &idd, &input); + BOOL callNeedRestart = FALSE; - if (created[i] == DEVICE_NOT_CREATED) + if (!state->present) { - wprintf(L"Preinstalling %s INF...\n", device->name); - bInfNeedRestart = FALSE; - if (!installInf(device, &bInfNeedRestart)) - exit(1); - bNeedRestart |= bInfNeedRestart; + wprintf(L"Staging %s INF...\n", device->name); + if (!stageInf(state)) + goto cleanup; wprintf(L"Creating %s device: %s...\n", device->name, device->hardwareId); - if (!createDevice(device)) - exit(1); + if (!createDevice(state)) + goto cleanup; + state->present = true; + state->created = true; } wprintf(L"Installing %s INF...\n", device->name); - bInfNeedRestart = FALSE; - if (!installInf(device, &bInfNeedRestart)) - exit(1); - bNeedRestart |= bInfNeedRestart; + callNeedRestart = FALSE; + if (!installInf(device, &callNeedRestart)) + goto cleanup; + logRestart(device, L"DiInstallDriverW", callNeedRestart); + needRestart |= callNeedRestart; } - if (bNeedRestart) + installSucceeded = true; + +cleanup: + if (!installSucceeded) + { + rollbackSucceeded = rollbackInstall(&idd, &input, &needRestart); + if (!helperExisted && idd.created && idd.rollbackDeviceSafe) + { + _putws(L"Removing LGIddHelper created by failed installation..."); + if (!deleteHelperService()) + rollbackSucceeded = false; + } + } + + restoreSucceeded = enableDevices(&idd, &input); + + if (installSucceeded && restoreSucceeded && !needRestart) + { + _putws(L"Starting LGIddHelper..."); + if (!startHelperService()) + restoreSucceeded = false; + } + else if (!installSucceeded && restoreSucceeded && helperWasRunning) + { + _putws(L"Restoring LGIddHelper after failed installation..."); + if (!startHelperService()) + restoreSucceeded = false; + } + + if (!installSucceeded) + { + if (!rollbackSucceeded) + fwprintf(stderr, L"Installation rollback was incomplete\n"); + if (needRestart) + { + _putws(L"Restart required after failed installation"); + exit(EXIT_FAILURE_RESTART_REQUIRED); + } + exit(1); + } + + if (needRestart) { _putws(L"Restart required to complete installation"); - exit(12); + exit(EXIT_RESTART_REQUIRED); } + + if (!restoreSucceeded) + exit(1); } void uninstall() { - BOOL bNeedRestart = 0; - bool success = true; + BOOL bNeedRestart = FALSE; + bool helperWasRunning; + bool iddRemoved = false; + + _putws(L"Stopping LGIddHelper..."); + if (!stopHelperService(NULL, &helperWasRunning)) + exit(1); _putws(L"Uninstalling LGInput..."); - success &= destroyDevice(&LGINPUT_DEVICE, &bNeedRestart); + if (!destroyDevice(&LGINPUT_DEVICE, &bNeedRestart, NULL)) + goto failure; _putws(L"Uninstalling LGIdd..."); - success &= destroyDevice(&LGIDD_DEVICE, &bNeedRestart); + if (!destroyDevice(&LGIDD_DEVICE, &bNeedRestart, &iddRemoved)) + goto failure; DWORD ec = deleteKeyTreeHKLM(); if (ec != ERROR_SUCCESS) @@ -564,14 +1240,27 @@ void uninstall() // this is non-fatal } - if (!success) - exit(1); - if (bNeedRestart) { - _putws(L"Restart required to complete installation"); - exit(12); + _putws(L"Restart required to complete uninstallation"); + exit(EXIT_RESTART_REQUIRED); } + return; + +failure: + if (helperWasRunning && !iddRemoved && deviceIsUsable(&LGIDD_DEVICE)) + { + _putws(L"Restoring LGIddHelper after failed uninstallation..."); + if (!startHelperService()) + fwprintf(stderr, + L"LGIddHelper could not be restored after uninstall failure\n"); + } + if (bNeedRestart) + { + _putws(L"Restart required after failed uninstallation"); + exit(EXIT_FAILURE_RESTART_REQUIRED); + } + exit(1); } int wmain(int argc, wchar_t **argv) diff --git a/idd/LGInput/LGInput.inf b/idd/LGInput/LGInput.inf index 14c733b4..8abd59b2 100644 --- a/idd/LGInput/LGInput.inf +++ b/idd/LGInput/LGInput.inf @@ -60,7 +60,7 @@ ServiceBinary=%12%\UMDF\LGInput.dll UMDriverCopy=12,UMDF [UMDriverCopy] -LGInput.dll +LGInput.dll,,,0x00004000 [SourceDisksNames] 1=%DiskName% diff --git a/idd/installer.nsi b/idd/installer.nsi index a0b179d3..5111bd6f 100644 --- a/idd/installer.nsi +++ b/idd/installer.nsi @@ -50,6 +50,9 @@ InstallDir "$PROGRAMFILES64\Looking Glass (IDD)" !define MUI_TEXTCOLOR "ffffff" !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\nsis3-grey.bmp" !define /file VERSION "VERSION" +!define LGIDD_EXIT_RESTART_REQUIRED 12 +; The operation failed, but a partial change or rollback still needs reboot. +!define LGIDD_EXIT_FAILURE_RESTART_REQUIRED 13 !define MUI_WELCOMEPAGE_TEXT "\ You are about to install $(^Name) version ${VERSION}.$\n$\n\ @@ -118,18 +121,6 @@ Function .onInit FunctionEnd -!macro StopLGIddHelper - ;Attempt to stop existing LG service only if it exists - - nsExec::Exec 'sc.exe query LGIddHelper' - Pop $0 ; SC.exe error level - - ${If} $0 == 0 ; If error level is 0, service exists - DetailPrint "Stop service: LGIddHelper" - nsExec::ExecToLog 'net.exe STOP LGIddHelper' - ${EndIf} -!macroend - ;Install !ifdef IVSHMEM Section "IVSHMEM Driver" Section0 @@ -207,23 +198,44 @@ Section "!Indirect Display Driver (IDD)" Section1 WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (IDD)" \ "DisplayVersion" ${VERSION} - !insertmacro StopLGIddHelper - DetailPrint "Installing IDD" nsExec::ExecToLog '"$INSTDIR\LGIddInstall.exe" install' Pop $0 - ${If} $0 == 12 + ${If} $0 == ${LGIDD_EXIT_RESTART_REQUIRED} DetailPrint "Restart is required to complete driver install." + SetRebootFlag true + ${ElseIf} $0 == ${LGIDD_EXIT_FAILURE_RESTART_REQUIRED} + DetailPrint "Driver installation failed and a restart is required." + SetRebootFlag true + SetErrorLevel $0 + Abort + ${ElseIf} $0 != 0 + DetailPrint "Driver installation failed with exit code $0." + SetErrorLevel $0 + Abort ${EndIf} SectionEnd Section "Uninstall" Section6 - !insertmacro StopLGIddHelper - DetailPrint "Uninstalling IDD" nsExec::ExecToLog '"$INSTDIR\LGIddInstall.exe" uninstall' + Pop $0 + + ${If} $0 == ${LGIDD_EXIT_RESTART_REQUIRED} + DetailPrint "Restart is required to complete driver uninstall." + SetRebootFlag true + ${ElseIf} $0 == ${LGIDD_EXIT_FAILURE_RESTART_REQUIRED} + DetailPrint "Driver uninstallation failed and a restart is required." + SetRebootFlag true + SetErrorLevel $0 + Abort + ${ElseIf} $0 != 0 + DetailPrint "Driver uninstallation failed with exit code $0." + SetErrorLevel $0 + Abort + ${EndIf} DetailPrint "Clean up helper service" nsExec::Exec 'sc.exe delete LGIddHelper'