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