1 /* 2 * ReactOS kernel 3 * Copyright (C) 2005 ReactOS Team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 /* 20 * COPYRIGHT: See COPYING in the top level directory 21 * PROJECT: ReactOS kernel 22 * FILE: base/services/umpnpmgr/install.c 23 * PURPOSE: Device installer 24 * PROGRAMMER: Eric Kohl (eric.kohl@reactos.org) 25 * Hervé Poussineau (hpoussin@reactos.org) 26 * Colin Finck (colin@reactos.org) 27 */ 28 29 /* INCLUDES *****************************************************************/ 30 31 #include "precomp.h" 32 33 #define NDEBUG 34 #include <debug.h> 35 36 37 /* GLOBALS ******************************************************************/ 38 39 HANDLE hUserToken = NULL; 40 HANDLE hInstallEvent = NULL; 41 HANDLE hNoPendingInstalls = NULL; 42 43 /* Device-install event list */ 44 HANDLE hDeviceInstallListMutex; 45 LIST_ENTRY DeviceInstallListHead; 46 HANDLE hDeviceInstallListNotEmpty; 47 48 49 /* FUNCTIONS *****************************************************************/ 50 51 static BOOL 52 InstallDevice(PCWSTR DeviceInstance, BOOL ShowWizard) 53 { 54 BOOL DeviceInstalled = FALSE; 55 DWORD BytesWritten; 56 DWORD Value; 57 HANDLE hInstallEvent; 58 HANDLE hPipe = INVALID_HANDLE_VALUE; 59 LPVOID Environment = NULL; 60 PROCESS_INFORMATION ProcessInfo; 61 STARTUPINFOW StartupInfo; 62 UUID RandomUuid; 63 HKEY DeviceKey; 64 65 /* The following lengths are constant (see below), they cannot overflow */ 66 WCHAR CommandLine[116]; 67 WCHAR InstallEventName[73]; 68 WCHAR PipeName[74]; 69 WCHAR UuidString[39]; 70 71 DPRINT("InstallDevice(%S, %d)\n", DeviceInstance, ShowWizard); 72 73 ZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); 74 75 if (RegOpenKeyExW(hEnumKey, 76 DeviceInstance, 77 0, 78 KEY_QUERY_VALUE, 79 &DeviceKey) == ERROR_SUCCESS) 80 { 81 if (RegQueryValueExW(DeviceKey, 82 L"Class", 83 NULL, 84 NULL, 85 NULL, 86 NULL) == ERROR_SUCCESS) 87 { 88 DPRINT("No need to install: %S\n", DeviceInstance); 89 RegCloseKey(DeviceKey); 90 return TRUE; 91 } 92 93 BytesWritten = sizeof(DWORD); 94 if (RegQueryValueExW(DeviceKey, 95 L"ConfigFlags", 96 NULL, 97 NULL, 98 (PBYTE)&Value, 99 &BytesWritten) == ERROR_SUCCESS) 100 { 101 if (Value & CONFIGFLAG_FAILEDINSTALL) 102 { 103 DPRINT("No need to install: %S\n", DeviceInstance); 104 RegCloseKey(DeviceKey); 105 return TRUE; 106 } 107 } 108 109 RegCloseKey(DeviceKey); 110 } 111 112 DPRINT1("Installing: %S\n", DeviceInstance); 113 114 /* Create a random UUID for the named pipe & event*/ 115 UuidCreate(&RandomUuid); 116 swprintf(UuidString, L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", 117 RandomUuid.Data1, RandomUuid.Data2, RandomUuid.Data3, 118 RandomUuid.Data4[0], RandomUuid.Data4[1], RandomUuid.Data4[2], 119 RandomUuid.Data4[3], RandomUuid.Data4[4], RandomUuid.Data4[5], 120 RandomUuid.Data4[6], RandomUuid.Data4[7]); 121 122 /* Create the event */ 123 wcscpy(InstallEventName, L"Global\\PNP_Device_Install_Event_0."); 124 wcscat(InstallEventName, UuidString); 125 hInstallEvent = CreateEventW(NULL, TRUE, FALSE, InstallEventName); 126 if (!hInstallEvent) 127 { 128 DPRINT1("CreateEventW('%ls') failed with error %lu\n", InstallEventName, GetLastError()); 129 goto cleanup; 130 } 131 132 /* Create the named pipe */ 133 wcscpy(PipeName, L"\\\\.\\pipe\\PNP_Device_Install_Pipe_0."); 134 wcscat(PipeName, UuidString); 135 hPipe = CreateNamedPipeW(PipeName, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 1, 512, 512, 0, NULL); 136 if (hPipe == INVALID_HANDLE_VALUE) 137 { 138 DPRINT1("CreateNamedPipeW failed with error %u\n", GetLastError()); 139 goto cleanup; 140 } 141 142 /* Launch rundll32 to call ClientSideInstallW */ 143 wcscpy(CommandLine, L"rundll32.exe newdev.dll,ClientSideInstall "); 144 wcscat(CommandLine, PipeName); 145 146 ZeroMemory(&StartupInfo, sizeof(StartupInfo)); 147 StartupInfo.cb = sizeof(StartupInfo); 148 149 if (hUserToken) 150 { 151 /* newdev has to run under the environment of the current user */ 152 if (!CreateEnvironmentBlock(&Environment, hUserToken, FALSE)) 153 { 154 DPRINT1("CreateEnvironmentBlock failed with error %d\n", GetLastError()); 155 goto cleanup; 156 } 157 158 if (!CreateProcessAsUserW(hUserToken, NULL, CommandLine, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, Environment, NULL, &StartupInfo, &ProcessInfo)) 159 { 160 DPRINT1("CreateProcessAsUserW failed with error %u\n", GetLastError()); 161 goto cleanup; 162 } 163 } 164 else 165 { 166 /* FIXME: This is probably not correct, I guess newdev should never be run with SYSTEM privileges. 167 168 Still, we currently do that in 2nd stage setup and probably Console mode as well, so allow it here. 169 (ShowWizard is only set to FALSE for these two modes) */ 170 ASSERT(!ShowWizard); 171 172 if (!CreateProcessW(NULL, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcessInfo)) 173 { 174 DPRINT1("CreateProcessW failed with error %u\n", GetLastError()); 175 goto cleanup; 176 } 177 } 178 179 /* Wait for the function to connect to our pipe */ 180 if (!ConnectNamedPipe(hPipe, NULL)) 181 { 182 if (GetLastError() != ERROR_PIPE_CONNECTED) 183 { 184 DPRINT1("ConnectNamedPipe failed with error %u\n", GetLastError()); 185 goto cleanup; 186 } 187 } 188 189 /* Pass the data. The following output is partly compatible to Windows XP SP2 (researched using a modified newdev.dll to log this stuff) */ 190 Value = sizeof(InstallEventName); 191 WriteFile(hPipe, &Value, sizeof(Value), &BytesWritten, NULL); 192 WriteFile(hPipe, InstallEventName, Value, &BytesWritten, NULL); 193 194 /* I couldn't figure out what the following value means under WinXP. It's usually 0 in my tests, but was also 5 once. 195 Therefore the following line is entirely ReactOS-specific. We use the value here to pass the ShowWizard variable. */ 196 WriteFile(hPipe, &ShowWizard, sizeof(ShowWizard), &BytesWritten, NULL); 197 198 Value = (wcslen(DeviceInstance) + 1) * sizeof(WCHAR); 199 WriteFile(hPipe, &Value, sizeof(Value), &BytesWritten, NULL); 200 WriteFile(hPipe, DeviceInstance, Value, &BytesWritten, NULL); 201 202 /* Wait for newdev.dll to finish processing */ 203 WaitForSingleObject(ProcessInfo.hProcess, INFINITE); 204 205 /* If the event got signalled, this is success */ 206 DeviceInstalled = WaitForSingleObject(hInstallEvent, 0) == WAIT_OBJECT_0; 207 208 cleanup: 209 if (hInstallEvent) 210 CloseHandle(hInstallEvent); 211 212 if (hPipe != INVALID_HANDLE_VALUE) 213 CloseHandle(hPipe); 214 215 if (Environment) 216 DestroyEnvironmentBlock(Environment); 217 218 if (ProcessInfo.hProcess) 219 CloseHandle(ProcessInfo.hProcess); 220 221 if (ProcessInfo.hThread) 222 CloseHandle(ProcessInfo.hThread); 223 224 if (!DeviceInstalled) 225 { 226 DPRINT1("InstallDevice failed for DeviceInstance '%ws'\n", DeviceInstance); 227 } 228 229 return DeviceInstalled; 230 } 231 232 233 static LONG 234 ReadRegSzKey( 235 IN HKEY hKey, 236 IN LPCWSTR pszKey, 237 OUT LPWSTR* pValue) 238 { 239 LONG rc; 240 DWORD dwType; 241 DWORD cbData = 0; 242 LPWSTR Value; 243 244 if (!pValue) 245 return ERROR_INVALID_PARAMETER; 246 247 *pValue = NULL; 248 rc = RegQueryValueExW(hKey, pszKey, NULL, &dwType, NULL, &cbData); 249 if (rc != ERROR_SUCCESS) 250 return rc; 251 if (dwType != REG_SZ) 252 return ERROR_FILE_NOT_FOUND; 253 Value = HeapAlloc(GetProcessHeap(), 0, cbData + sizeof(WCHAR)); 254 if (!Value) 255 return ERROR_NOT_ENOUGH_MEMORY; 256 rc = RegQueryValueExW(hKey, pszKey, NULL, NULL, (LPBYTE)Value, &cbData); 257 if (rc != ERROR_SUCCESS) 258 { 259 HeapFree(GetProcessHeap(), 0, Value); 260 return rc; 261 } 262 /* NULL-terminate the string */ 263 Value[cbData / sizeof(WCHAR)] = '\0'; 264 265 *pValue = Value; 266 return ERROR_SUCCESS; 267 } 268 269 270 BOOL 271 SetupIsActive(VOID) 272 { 273 HKEY hKey = NULL; 274 DWORD regType, active, size; 275 LONG rc; 276 BOOL ret = FALSE; 277 278 rc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\Setup", 0, KEY_QUERY_VALUE, &hKey); 279 if (rc != ERROR_SUCCESS) 280 goto cleanup; 281 282 size = sizeof(DWORD); 283 rc = RegQueryValueExW(hKey, L"SystemSetupInProgress", NULL, ®Type, (LPBYTE)&active, &size); 284 if (rc != ERROR_SUCCESS) 285 goto cleanup; 286 if (regType != REG_DWORD || size != sizeof(DWORD)) 287 goto cleanup; 288 289 ret = (active != 0); 290 291 cleanup: 292 if (hKey != NULL) 293 RegCloseKey(hKey); 294 295 DPRINT("System setup in progress? %S\n", ret ? L"YES" : L"NO"); 296 297 return ret; 298 } 299 300 301 static BOOL 302 IsConsoleBoot(VOID) 303 { 304 HKEY ControlKey = NULL; 305 LPWSTR SystemStartOptions = NULL; 306 LPWSTR CurrentOption, NextOption; /* Pointers into SystemStartOptions */ 307 BOOL ConsoleBoot = FALSE; 308 LONG rc; 309 310 rc = RegOpenKeyExW( 311 HKEY_LOCAL_MACHINE, 312 L"SYSTEM\\CurrentControlSet\\Control", 313 0, 314 KEY_QUERY_VALUE, 315 &ControlKey); 316 317 rc = ReadRegSzKey(ControlKey, L"SystemStartOptions", &SystemStartOptions); 318 if (rc != ERROR_SUCCESS) 319 goto cleanup; 320 321 /* Check for CONSOLE switch in SystemStartOptions */ 322 CurrentOption = SystemStartOptions; 323 while (CurrentOption) 324 { 325 NextOption = wcschr(CurrentOption, L' '); 326 if (NextOption) 327 *NextOption = L'\0'; 328 if (_wcsicmp(CurrentOption, L"CONSOLE") == 0) 329 { 330 DPRINT("Found %S. Switching to console boot\n", CurrentOption); 331 ConsoleBoot = TRUE; 332 goto cleanup; 333 } 334 CurrentOption = NextOption ? NextOption + 1 : NULL; 335 } 336 337 cleanup: 338 if (ControlKey != NULL) 339 RegCloseKey(ControlKey); 340 HeapFree(GetProcessHeap(), 0, SystemStartOptions); 341 return ConsoleBoot; 342 } 343 344 345 FORCEINLINE 346 BOOL 347 IsUISuppressionAllowed(VOID) 348 { 349 /* Display the newdev.dll wizard UI only if it's allowed */ 350 return (g_IsUISuppressed || GetSuppressNewUIValue()); 351 } 352 353 354 /* Loop to install all queued devices installations */ 355 DWORD 356 WINAPI 357 DeviceInstallThread(LPVOID lpParameter) 358 { 359 PLIST_ENTRY ListEntry; 360 DeviceInstallParams* Params; 361 BOOL showWizard; 362 363 UNREFERENCED_PARAMETER(lpParameter); 364 365 WaitForSingleObject(hInstallEvent, INFINITE); 366 367 showWizard = !SetupIsActive() && !IsConsoleBoot(); 368 369 while (TRUE) 370 { 371 /* Dequeue the next oldest device-install event */ 372 WaitForSingleObject(hDeviceInstallListMutex, INFINITE); 373 ListEntry = (IsListEmpty(&DeviceInstallListHead) 374 ? NULL : RemoveHeadList(&DeviceInstallListHead)); 375 ReleaseMutex(hDeviceInstallListMutex); 376 377 if (ListEntry == NULL) 378 { 379 SetEvent(hNoPendingInstalls); 380 WaitForSingleObject(hDeviceInstallListNotEmpty, INFINITE); 381 } 382 else 383 { 384 ResetEvent(hNoPendingInstalls); 385 Params = CONTAINING_RECORD(ListEntry, DeviceInstallParams, ListEntry); 386 InstallDevice(Params->DeviceIds, showWizard && !IsUISuppressionAllowed()); 387 HeapFree(GetProcessHeap(), 0, Params); 388 } 389 } 390 391 return 0; 392 } 393