xref: /reactos/base/services/umpnpmgr/install.c (revision 5c7ce447)
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, &regType, (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