VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-win.cpp@ 85416

Last change on this file since 85416 was 85121, checked in by vboxsync, 4 years ago

iprt/cdefs.h: Refactored the typedef use of DECLCALLBACK as well as DECLCALLBACKMEMBER to wrap the whole expression, similar to the DECLR?CALLBACKMEMBER macros. This allows adding a throw() at the end when compiling with the VC++ compiler to indicate that the callbacks won't throw anything, so we can stop supressing the C5039 warning about passing functions that can potential throw C++ exceptions to extern C code that can't necessarily cope with such (unwind,++). Introduced a few _EX variations that allows specifying different/no calling convention too, as that's handy when dynamically resolving host APIs. Fixed numerous places missing DECLCALLBACK and such. Left two angry @todos regarding use of CreateThread. bugref:9794

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 42.5 KB
Line 
1/* $Id: VBoxAutostart-win.cpp 85121 2020-07-08 19:33:26Z vboxsync $ */
2/** @file
3 * VirtualBox Autostart Service - Windows Specific Code.
4 */
5
6/*
7 * Copyright (C) 2012-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/win/windows.h>
23#include <tchar.h>
24
25#define SECURITY_WIN32
26#include <Security.h>
27
28#include <VBox/com/array.h>
29#include <VBox/com/com.h>
30#include <VBox/com/ErrorInfo.h>
31#include <VBox/com/errorprint.h>
32#include <VBox/com/Guid.h>
33#include <VBox/com/listeners.h>
34#include <VBox/com/NativeEventQueue.h>
35#include <VBox/com/string.h>
36#include <VBox/com/VirtualBox.h>
37
38#include <VBox/log.h>
39#include <VBox/version.h>
40
41#include <iprt/env.h>
42#include <iprt/errcore.h>
43#include <iprt/getopt.h>
44#include <iprt/initterm.h>
45#include <iprt/mem.h>
46#include <iprt/process.h>
47#include <iprt/path.h>
48#include <iprt/semaphore.h>
49#include <iprt/stream.h>
50#include <iprt/string.h>
51#include <iprt/thread.h>
52
53#include "VBoxAutostart.h"
54#include "PasswordInput.h"
55
56
57/*********************************************************************************************************************************
58* Defined Constants And Macros *
59*********************************************************************************************************************************/
60/** The service name. */
61#define AUTOSTART_SERVICE_NAME "VBoxAutostartSvc"
62/** The service display name. */
63#define AUTOSTART_SERVICE_DISPLAY_NAME "VirtualBox Autostart Service"
64
65ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
66bool g_fVerbose = false;
67ComPtr<IVirtualBox> g_pVirtualBox = NULL;
68ComPtr<ISession> g_pSession = NULL;
69
70
71/*********************************************************************************************************************************
72* Global Variables *
73*********************************************************************************************************************************/
74/** The service control handler handle. */
75static SERVICE_STATUS_HANDLE g_hSupSvcWinCtrlHandler = NULL;
76/** The service status. */
77static uint32_t volatile g_u32SupSvcWinStatus = SERVICE_STOPPED;
78/** The semaphore the main service thread is waiting on in autostartSvcWinServiceMain. */
79static RTSEMEVENTMULTI g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI;
80/** The service name is used for send to service main. */
81static com::Bstr g_bstrServiceName;
82
83
84/*********************************************************************************************************************************
85* Internal Functions *
86*********************************************************************************************************************************/
87static SC_HANDLE autostartSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess);
88
89static int autostartGetProcessDomainUser(com::Utf8Str &aUser)
90{
91 int rc = VERR_NOT_SUPPORTED;
92
93 RTUTF16 wszUsername[1024] = { 0 };
94 ULONG cwcUsername = RT_ELEMENTS(wszUsername);
95 char *pszUser = NULL;
96 if (!GetUserNameExW(NameSamCompatible, &wszUsername[0], &cwcUsername))
97 return RTErrConvertFromWin32(GetLastError());
98 rc = RTUtf16ToUtf8(wszUsername, &pszUser);
99 aUser = pszUser;
100 aUser.toLower();
101 RTStrFree(pszUser);
102 return rc;
103}
104
105static int autostartGetLocalDomain(com::Utf8Str &aDomain)
106{
107 RTUTF16 pwszDomain[MAX_COMPUTERNAME_LENGTH + 1] = { 0 };
108 uint32_t cwcDomainSize = MAX_COMPUTERNAME_LENGTH + 1;
109 if (!GetComputerNameW(pwszDomain, (LPDWORD)&cwcDomainSize))
110 return RTErrConvertFromWin32(GetLastError());
111 char *pszDomain = NULL;
112 int rc = RTUtf16ToUtf8(pwszDomain, &pszDomain);
113 aDomain = pszDomain;
114 aDomain.toLower();
115 RTStrFree(pszDomain);
116 return rc;
117}
118
119static int autostartGetDomainAndUser(const com::Utf8Str &aDomainAndUser, com::Utf8Str &aDomain, com::Utf8Str &aUser)
120{
121 size_t offDelim = aDomainAndUser.find("\\");
122 if (offDelim != aDomainAndUser.npos)
123 {
124 // if only domain is specified
125 if (aDomainAndUser.length() - offDelim == 1)
126 return VERR_INVALID_PARAMETER;
127
128 if (offDelim == 1 && aDomainAndUser[0] == '.')
129 {
130 int rc = autostartGetLocalDomain(aDomain);
131 aUser = aDomainAndUser.substr(offDelim + 1);
132 return rc;
133 }
134 aDomain = aDomainAndUser.substr(0, offDelim);
135 aUser = aDomainAndUser.substr(offDelim + 1);
136 aDomain.toLower();
137 aUser.toLower();
138 return VINF_SUCCESS;
139 }
140
141 offDelim = aDomainAndUser.find("@");
142 if (offDelim != aDomainAndUser.npos)
143 {
144 // if only domain is specified
145 if (offDelim == 0)
146 return VERR_INVALID_PARAMETER;
147
148 // with '@' but without domain
149 if (aDomainAndUser.length() - offDelim == 1)
150 {
151 int rc = autostartGetLocalDomain(aDomain);
152 aUser = aDomainAndUser.substr(0, offDelim);
153 return rc;
154 }
155 aDomain = aDomainAndUser.substr(offDelim + 1);
156 aUser = aDomainAndUser.substr(0, offDelim);
157 aDomain.toLower();
158 aUser.toLower();
159 return VINF_SUCCESS;
160 }
161
162 // only user is specified
163 int rc = autostartGetLocalDomain(aDomain);
164 aUser = aDomainAndUser;
165 aDomain.toLower();
166 aUser.toLower();
167 return rc;
168}
169
170/** Common helper for formatting the service name. */
171static void autostartFormatServiceName(const com::Utf8Str &aDomain, const com::Utf8Str &aUser, com::Utf8Str &aServiceName)
172{
173 aServiceName.printf("%s%s%s", AUTOSTART_SERVICE_NAME, aDomain.c_str(), aUser.c_str());
174}
175
176/** Used by the delete service operation. */
177static int autostartGetServiceName(const com::Utf8Str &aDomainAndUser, com::Utf8Str &aServiceName)
178{
179 com::Utf8Str sDomain;
180 com::Utf8Str sUser;
181 int rc = autostartGetDomainAndUser(aDomainAndUser, sDomain, sUser);
182 if (RT_FAILURE(rc))
183 return rc;
184 autostartFormatServiceName(sDomain, sUser, aServiceName);
185 return VINF_SUCCESS;
186}
187
188/**
189 * Print out progress on the console.
190 *
191 * This runs the main event queue every now and then to prevent piling up
192 * unhandled things (which doesn't cause real problems, just makes things
193 * react a little slower than in the ideal case).
194 */
195DECLHIDDEN(HRESULT) showProgress(ComPtr<IProgress> progress)
196{
197 using namespace com;
198
199 BOOL fCompleted = FALSE;
200 ULONG uCurrentPercent = 0;
201 Bstr bstrOperationDescription;
202
203 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
204
205 ULONG cOperations = 1;
206 HRESULT hrc = progress->COMGETTER(OperationCount)(&cOperations);
207 if (FAILED(hrc))
208 return hrc;
209
210 /* setup signal handling if cancelable */
211 bool fCanceledAlready = false;
212 BOOL fCancelable;
213 hrc = progress->COMGETTER(Cancelable)(&fCancelable);
214 if (FAILED(hrc))
215 fCancelable = FALSE;
216
217 hrc = progress->COMGETTER(Completed(&fCompleted));
218 while (SUCCEEDED(hrc))
219 {
220 progress->COMGETTER(Percent(&uCurrentPercent));
221
222 if (fCompleted)
223 break;
224
225 /* process async cancelation */
226 if (!fCanceledAlready)
227 {
228 hrc = progress->Cancel();
229 if (SUCCEEDED(hrc))
230 fCanceledAlready = true;
231 }
232
233 /* make sure the loop is not too tight */
234 progress->WaitForCompletion(100);
235
236 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
237 hrc = progress->COMGETTER(Completed(&fCompleted));
238 }
239
240 /* complete the line. */
241 LONG iRc = E_FAIL;
242 hrc = progress->COMGETTER(ResultCode)(&iRc);
243 if (SUCCEEDED(hrc))
244 {
245 hrc = iRc;
246 }
247
248 return hrc;
249}
250
251DECLHIDDEN(void) autostartSvcOsLogStr(const char *pszMsg, AUTOSTARTLOGTYPE enmLogType)
252{
253 HANDLE hEventLog = RegisterEventSourceA(NULL /* local computer */, "VBoxAutostartSvc");
254 AssertReturnVoid(hEventLog != NULL);
255 WORD wType = 0;
256 const char *apsz[2];
257 apsz[0] = "VBoxAutostartSvc";
258 apsz[1] = pszMsg;
259
260 switch (enmLogType)
261 {
262 case AUTOSTARTLOGTYPE_INFO:
263 wType = 0;
264 break;
265 case AUTOSTARTLOGTYPE_ERROR:
266 wType = EVENTLOG_ERROR_TYPE;
267 break;
268 case AUTOSTARTLOGTYPE_WARNING:
269 wType = EVENTLOG_WARNING_TYPE;
270 break;
271 case AUTOSTARTLOGTYPE_VERBOSE:
272 if (!g_fVerbose)
273 return;
274 wType = EVENTLOG_INFORMATION_TYPE;
275 break;
276 default:
277 AssertMsgFailed(("Invalid log type %d\n", enmLogType));
278 }
279
280 BOOL fRc = ReportEventA(hEventLog, /* hEventLog */
281 wType, /* wType */
282 0, /* wCategory */
283 0 /** @todo mc */, /* dwEventID */
284 NULL, /* lpUserSid */
285 RT_ELEMENTS(apsz), /* wNumStrings */
286 0, /* dwDataSize */
287 apsz, /* lpStrings */
288 NULL); /* lpRawData */
289 AssertMsg(fRc, ("%u\n", GetLastError())); NOREF(fRc);
290 DeregisterEventSource(hEventLog);
291}
292
293/**
294 * Opens the service control manager.
295 *
296 * When this fails, an error message will be displayed.
297 *
298 * @returns Valid handle on success.
299 * NULL on failure, will display an error message.
300 *
301 * @param pszAction The action which is requesting access to SCM.
302 * @param dwAccess The desired access.
303 */
304static SC_HANDLE autostartSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess)
305{
306 SC_HANDLE hSCM = OpenSCManager(NULL /* lpMachineName*/, NULL /* lpDatabaseName */, dwAccess);
307 if (hSCM == NULL)
308 {
309 DWORD err = GetLastError();
310 switch (err)
311 {
312 case ERROR_ACCESS_DENIED:
313 autostartSvcDisplayError("%s - OpenSCManager failure: access denied\n", pszAction);
314 break;
315 default:
316 autostartSvcDisplayError("%s - OpenSCManager failure: %d\n", pszAction, err);
317 break;
318 }
319 }
320 return hSCM;
321}
322
323
324/**
325 * Opens the service.
326 *
327 * Last error is preserved on failure and set to 0 on success.
328 *
329 * @returns Valid service handle on success.
330 * NULL on failure, will display an error message unless it's ignored.
331 *
332 * @param pszAction The action which is requesting access to the service.
333 * @param dwSCMAccess The service control manager access.
334 * @param dwSVCAccess The desired service access.
335 * @param cIgnoredErrors The number of ignored errors.
336 * @param ... Errors codes that should not cause a message to be displayed.
337 */
338static SC_HANDLE autostartSvcWinOpenService(const PRTUTF16 pwszServiceName, const char *pszAction, DWORD dwSCMAccess, DWORD dwSVCAccess,
339 unsigned cIgnoredErrors, ...)
340{
341 SC_HANDLE hSCM = autostartSvcWinOpenSCManager(pszAction, dwSCMAccess);
342 if (!hSCM)
343 return NULL;
344
345 SC_HANDLE hSvc = OpenServiceW(hSCM, pwszServiceName, dwSVCAccess);
346 if (hSvc)
347 {
348 CloseServiceHandle(hSCM);
349 SetLastError(0);
350 }
351 else
352 {
353 DWORD err = GetLastError();
354 bool fIgnored = false;
355 va_list va;
356 va_start(va, cIgnoredErrors);
357 while (!fIgnored && cIgnoredErrors-- > 0)
358 fIgnored = (DWORD)va_arg(va, int) == err;
359 va_end(va);
360 if (!fIgnored)
361 {
362 switch (err)
363 {
364 case ERROR_ACCESS_DENIED:
365 autostartSvcDisplayError("%s - OpenService failure: access denied\n", pszAction);
366 break;
367 case ERROR_SERVICE_DOES_NOT_EXIST:
368 autostartSvcDisplayError("%s - OpenService failure: The service %ls does not exist. Reinstall it.\n",
369 pszAction, pwszServiceName);
370 break;
371 default:
372 autostartSvcDisplayError("%s - OpenService failure: %d\n", pszAction, err);
373 break;
374 }
375 }
376
377 CloseServiceHandle(hSCM);
378 SetLastError(err);
379 }
380 return hSvc;
381}
382
383static RTEXITCODE autostartSvcWinInterrogate(int argc, char **argv)
384{
385 RT_NOREF(argc, argv);
386 RTPrintf("VBoxAutostartSvc: The \"interrogate\" action is not implemented.\n");
387 return RTEXITCODE_FAILURE;
388}
389
390
391static RTEXITCODE autostartSvcWinStop(int argc, char **argv)
392{
393 RT_NOREF(argc, argv);
394 RTPrintf("VBoxAutostartSvc: The \"stop\" action is not implemented.\n");
395 return RTEXITCODE_FAILURE;
396}
397
398
399static RTEXITCODE autostartSvcWinContinue(int argc, char **argv)
400{
401 RT_NOREF(argc, argv);
402 RTPrintf("VBoxAutostartSvc: The \"continue\" action is not implemented.\n");
403 return RTEXITCODE_FAILURE;
404}
405
406
407static RTEXITCODE autostartSvcWinPause(int argc, char **argv)
408{
409 RT_NOREF(argc, argv);
410 RTPrintf("VBoxAutostartSvc: The \"pause\" action is not implemented.\n");
411 return RTEXITCODE_FAILURE;
412}
413
414
415static RTEXITCODE autostartSvcWinStart(int argc, char **argv)
416{
417 RT_NOREF(argc, argv);
418 RTPrintf("VBoxAutostartSvc: The \"start\" action is not implemented.\n");
419 return RTEXITCODE_SUCCESS;
420}
421
422
423static RTEXITCODE autostartSvcWinQueryDescription(int argc, char **argv)
424{
425 RT_NOREF(argc, argv);
426 RTPrintf("VBoxAutostartSvc: The \"qdescription\" action is not implemented.\n");
427 return RTEXITCODE_FAILURE;
428}
429
430
431static RTEXITCODE autostartSvcWinQueryConfig(int argc, char **argv)
432{
433 RT_NOREF(argc, argv);
434 RTPrintf("VBoxAutostartSvc: The \"qconfig\" action is not implemented.\n");
435 return RTEXITCODE_FAILURE;
436}
437
438
439static RTEXITCODE autostartSvcWinDisable(int argc, char **argv)
440{
441 RT_NOREF(argc, argv);
442 RTPrintf("VBoxAutostartSvc: The \"disable\" action is not implemented.\n");
443 return RTEXITCODE_FAILURE;
444}
445
446static RTEXITCODE autostartSvcWinEnable(int argc, char **argv)
447{
448 RT_NOREF(argc, argv);
449 RTPrintf("VBoxAutostartSvc: The \"enable\" action is not implemented.\n");
450 return RTEXITCODE_FAILURE;
451}
452
453
454/**
455 * Handle the 'delete' action.
456 *
457 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
458 * @param argc The action argument count.
459 * @param argv The action argument vector.
460 */
461static int autostartSvcWinDelete(int argc, char **argv)
462{
463 /*
464 * Parse the arguments.
465 */
466 bool fVerbose = false;
467 const char *pszUser = NULL;
468 static const RTGETOPTDEF s_aOptions[] =
469 {
470 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
471 { "--user", 'u', RTGETOPT_REQ_STRING },
472 };
473 int ch;
474 RTGETOPTUNION Value;
475 RTGETOPTSTATE GetState;
476 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
477 while ((ch = RTGetOpt(&GetState, &Value)))
478 {
479 switch (ch)
480 {
481 case 'v':
482 fVerbose = true;
483 break;
484 case 'u':
485 pszUser = Value.psz;
486 break;
487 default:
488 return autostartSvcDisplayGetOptError("delete", ch, &Value);
489 }
490 }
491
492 if (!pszUser)
493 return autostartSvcDisplayError("delete - DeleteService failed, user name required.\n");
494
495 com::Utf8Str sServiceName;
496 int vrc = autostartGetServiceName(pszUser, sServiceName);
497 if (RT_FAILURE(vrc))
498 return autostartSvcDisplayError("delete - DeleteService failed, service name for user %s can not be constructed.\n",
499 pszUser);
500 /*
501 * Create the service.
502 */
503 RTEXITCODE rc = RTEXITCODE_FAILURE;
504 SC_HANDLE hSvc = autostartSvcWinOpenService(com::Bstr(sServiceName).raw(), "delete", SERVICE_CHANGE_CONFIG, DELETE,
505 1, ERROR_SERVICE_DOES_NOT_EXIST);
506 if (hSvc)
507 {
508 if (DeleteService(hSvc))
509 {
510 RTPrintf("Successfully deleted the %s service.\n", sServiceName.c_str());
511 rc = RTEXITCODE_SUCCESS;
512 }
513 else
514 autostartSvcDisplayError("delete - DeleteService failed, err=%d.\n", GetLastError());
515 CloseServiceHandle(hSvc);
516 }
517 else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
518 {
519
520 if (fVerbose)
521 RTPrintf("The service %s was not installed, nothing to be done.", sServiceName.c_str());
522 else
523 RTPrintf("Successfully deleted the %s service.\n", sServiceName.c_str());
524 rc = RTEXITCODE_SUCCESS;
525 }
526 return rc;
527}
528
529
530/**
531 * Handle the 'create' action.
532 *
533 * @returns 0 or 1.
534 * @param argc The action argument count.
535 * @param argv The action argument vector.
536 */
537static RTEXITCODE autostartSvcWinCreate(int argc, char **argv)
538{
539 /*
540 * Parse the arguments.
541 */
542 bool fVerbose = false;
543 const char *pszUser = NULL;
544 com::Utf8Str strPwd;
545 const char *pszPwdFile = NULL;
546 static const RTGETOPTDEF s_aOptions[] =
547 {
548 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
549 { "--user", 'u', RTGETOPT_REQ_STRING },
550 { "--password-file", 'p', RTGETOPT_REQ_STRING }
551 };
552 int ch;
553 RTGETOPTUNION Value;
554 RTGETOPTSTATE GetState;
555 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
556 while ((ch = RTGetOpt(&GetState, &Value)))
557 {
558 switch (ch)
559 {
560 case 'v':
561 fVerbose = true;
562 break;
563 case 'u':
564 pszUser = Value.psz;
565 break;
566 case 'p':
567 pszPwdFile = Value.psz;
568 break;
569 default:
570 return autostartSvcDisplayGetOptError("create", ch, &Value);
571 }
572 }
573
574 if (!pszUser)
575 return autostartSvcDisplayError("Username is missing");
576
577 if (pszPwdFile)
578 {
579 /* Get password from file. */
580 RTEXITCODE rcExit = readPasswordFile(pszPwdFile, &strPwd);
581 if (rcExit == RTEXITCODE_FAILURE)
582 return rcExit;
583 }
584 else
585 {
586 /* Get password from console. */
587 RTEXITCODE rcExit = readPasswordFromConsole(&strPwd, "Enter password:");
588 if (rcExit == RTEXITCODE_FAILURE)
589 return rcExit;
590 }
591
592 if (strPwd.isEmpty())
593 return autostartSvcDisplayError("Password is missing");
594
595 com::Utf8Str sDomain;
596 com::Utf8Str sUserTmp;
597 int vrc = autostartGetDomainAndUser(pszUser, sDomain, sUserTmp);
598 if (RT_FAILURE(vrc))
599 return autostartSvcDisplayError("create - CreateService failed, failed to get domain and user from string %s (%d).\n",
600 pszUser, vrc);
601 com::Utf8StrFmt sUserFullName("%s\\%s", sDomain.c_str(), sUserTmp.c_str());
602 com::Utf8StrFmt sDisplayName("%s %s@%s", AUTOSTART_SERVICE_DISPLAY_NAME, sUserTmp.c_str(), sDomain.c_str());
603 com::Utf8Str sServiceName;
604 autostartFormatServiceName(sDomain, sUserTmp, sServiceName);
605
606 /*
607 * Create the service.
608 */
609 RTEXITCODE rc = RTEXITCODE_FAILURE;
610 SC_HANDLE hSCM = autostartSvcWinOpenSCManager("create", SC_MANAGER_CREATE_SERVICE); /*SC_MANAGER_ALL_ACCESS*/
611 if (hSCM)
612 {
613 char szExecPath[RTPATH_MAX];
614 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)))
615 {
616 if (fVerbose)
617 RTPrintf("Creating the %s service, binary \"%s\"...\n",
618 sServiceName.c_str(), szExecPath); /* yea, the binary name isn't UTF-8, but wtf. */
619
620 /*
621 * Add service name as command line parameter for the service
622 */
623 com::Utf8StrFmt sCmdLine("\"%s\" --service=%s", szExecPath, sServiceName.c_str());
624 com::Bstr bstrServiceName(sServiceName);
625 com::Bstr bstrDisplayName(sDisplayName);
626 com::Bstr bstrCmdLine(sCmdLine);
627 com::Bstr bstrUserFullName(sUserFullName);
628 com::Bstr bstrPwd(strPwd);
629
630 SC_HANDLE hSvc = CreateServiceW(hSCM, /* hSCManager */
631 bstrServiceName.raw(), /* lpServiceName */
632 bstrDisplayName.raw(), /* lpDisplayName */
633 SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG, /* dwDesiredAccess */
634 SERVICE_WIN32_OWN_PROCESS, /* dwServiceType ( | SERVICE_INTERACTIVE_PROCESS? ) */
635 SERVICE_AUTO_START, /* dwStartType */
636 SERVICE_ERROR_NORMAL, /* dwErrorControl */
637 bstrCmdLine.raw(), /* lpBinaryPathName */
638 NULL, /* lpLoadOrderGroup */
639 NULL, /* lpdwTagId */
640 NULL, /* lpDependencies */
641 bstrUserFullName.raw(), /* lpServiceStartName (NULL => LocalSystem) */
642 bstrPwd.raw()); /* lpPassword */
643 if (hSvc)
644 {
645 RTPrintf("Successfully created the %s service.\n", sServiceName.c_str());
646 /** @todo Set the service description or it'll look weird in the vista service manager.
647 * Anything else that should be configured? Start access or something? */
648 rc = RTEXITCODE_SUCCESS;
649 CloseServiceHandle(hSvc);
650 }
651 else
652 {
653 DWORD err = GetLastError();
654 switch (err)
655 {
656 case ERROR_SERVICE_EXISTS:
657 autostartSvcDisplayError("create - The service already exists.\n");
658 break;
659 default:
660 autostartSvcDisplayError("create - CreateService failed, err=%d.\n", GetLastError());
661 break;
662 }
663 }
664 CloseServiceHandle(hSvc);
665 }
666 else
667 autostartSvcDisplayError("create - Failed to obtain the executable path: %d\n", GetLastError());
668 }
669 return rc;
670}
671
672
673/**
674 * Sets the service status, just a SetServiceStatus Wrapper.
675 *
676 * @returns See SetServiceStatus.
677 * @param dwStatus The current status.
678 * @param iWaitHint The wait hint, if < 0 then supply a default.
679 * @param dwExitCode The service exit code.
680 */
681static bool autostartSvcWinSetServiceStatus(DWORD dwStatus, int iWaitHint, DWORD dwExitCode)
682{
683 SERVICE_STATUS SvcStatus;
684 SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
685 SvcStatus.dwWin32ExitCode = dwExitCode;
686 SvcStatus.dwServiceSpecificExitCode = 0;
687 SvcStatus.dwWaitHint = iWaitHint >= 0 ? iWaitHint : 3000;
688 SvcStatus.dwCurrentState = dwStatus;
689 LogFlow(("autostartSvcWinSetServiceStatus: %d -> %d\n", g_u32SupSvcWinStatus, dwStatus));
690 g_u32SupSvcWinStatus = dwStatus;
691 switch (dwStatus)
692 {
693 case SERVICE_START_PENDING:
694 SvcStatus.dwControlsAccepted = 0;
695 break;
696 default:
697 SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
698 break;
699 }
700
701 static DWORD dwCheckPoint = 0;
702 switch (dwStatus)
703 {
704 case SERVICE_RUNNING:
705 case SERVICE_STOPPED:
706 SvcStatus.dwCheckPoint = 0;
707 default:
708 SvcStatus.dwCheckPoint = ++dwCheckPoint;
709 break;
710 }
711 return SetServiceStatus(g_hSupSvcWinCtrlHandler, &SvcStatus) != FALSE;
712}
713
714
715/**
716 * Service control handler (extended).
717 *
718 * @returns Windows status (see HandlerEx).
719 * @retval NO_ERROR if handled.
720 * @retval ERROR_CALL_NOT_IMPLEMENTED if not handled.
721 *
722 * @param dwControl The control code.
723 * @param dwEventType Event type. (specific to the control?)
724 * @param pvEventData Event data, specific to the event.
725 * @param pvContext The context pointer registered with the handler.
726 * Currently not used.
727 */
728static DWORD WINAPI
729autostartSvcWinServiceCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID pvEventData, LPVOID pvContext) RT_NOTHROW_DEF
730{
731 LogFlow(("autostartSvcWinServiceCtrlHandlerEx: dwControl=%#x dwEventType=%#x pvEventData=%p\n",
732 dwControl, dwEventType, pvEventData));
733
734 switch (dwControl)
735 {
736 /*
737 * Interrogate the service about it's current status.
738 * MSDN says that this should just return NO_ERROR and does
739 * not need to set the status again.
740 */
741 case SERVICE_CONTROL_INTERROGATE:
742 return NO_ERROR;
743
744 /*
745 * Request to stop the service.
746 */
747 case SERVICE_CONTROL_STOP:
748 {
749 /*
750 * Check if the real services can be stopped and then tell them to stop.
751 */
752 autostartSvcWinSetServiceStatus(SERVICE_STOP_PENDING, 3000, NO_ERROR);
753 /*
754 * Notify the main thread that we're done, it will wait for the
755 * VMs to stop, and set the windows service status to SERVICE_STOPPED
756 * and return.
757 */
758 int rc = RTSemEventMultiSignal(g_hSupSvcWinEvent);
759 if (RT_FAILURE(rc))
760 autostartSvcLogError("SERVICE_CONTROL_STOP: RTSemEventMultiSignal failed, %Rrc\n", rc);
761
762 return NO_ERROR;
763 }
764
765 case SERVICE_CONTROL_PAUSE:
766 case SERVICE_CONTROL_CONTINUE:
767 case SERVICE_CONTROL_SHUTDOWN:
768 case SERVICE_CONTROL_PARAMCHANGE:
769 case SERVICE_CONTROL_NETBINDADD:
770 case SERVICE_CONTROL_NETBINDREMOVE:
771 case SERVICE_CONTROL_NETBINDENABLE:
772 case SERVICE_CONTROL_NETBINDDISABLE:
773 case SERVICE_CONTROL_DEVICEEVENT:
774 case SERVICE_CONTROL_HARDWAREPROFILECHANGE:
775 case SERVICE_CONTROL_POWEREVENT:
776 case SERVICE_CONTROL_SESSIONCHANGE:
777#ifdef SERVICE_CONTROL_PRESHUTDOWN /* vista */
778 case SERVICE_CONTROL_PRESHUTDOWN:
779#endif
780 default:
781 return ERROR_CALL_NOT_IMPLEMENTED;
782 }
783
784 NOREF(dwEventType);
785 NOREF(pvEventData);
786 NOREF(pvContext);
787 /* not reached */
788}
789
790static RTEXITCODE autostartStartVMs()
791{
792 int rc = autostartSetup();
793 if (RT_FAILURE(rc))
794 return RTEXITCODE_FAILURE;
795
796 const char *pszConfigFile = RTEnvGet("VBOXAUTOSTART_CONFIG");
797 if (!pszConfigFile)
798 return autostartSvcLogError("Starting VMs failed. VBOXAUTOSTART_CONFIG environment variable is not defined.\n");
799 bool fAllow = false;
800
801 PCFGAST pCfgAst = NULL;
802 rc = autostartParseConfig(pszConfigFile, &pCfgAst);
803 if (RT_FAILURE(rc))
804 return autostartSvcLogError("Starting VMs failed. Failed to parse the config file. Check the access permissions and file structure.\n");
805
806 PCFGAST pCfgAstPolicy = autostartConfigAstGetByName(pCfgAst, "default_policy");
807 /* Check default policy. */
808 if (pCfgAstPolicy)
809 {
810 if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE
811 && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow")
812 || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "deny")))
813 {
814 if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow"))
815 fAllow = true;
816 }
817 else
818 {
819 autostartConfigAstDestroy(pCfgAst);
820 return autostartSvcLogError("'default_policy' must be either 'allow' or 'deny'.\n");
821 }
822 }
823
824 com::Utf8Str sUser;
825 rc = autostartGetProcessDomainUser(sUser);
826 if (RT_FAILURE(rc))
827 {
828 autostartConfigAstDestroy(pCfgAst);
829 return autostartSvcLogError("Failed to query username of the process (%Rrc).\n", rc);
830 }
831
832 PCFGAST pCfgAstUser = NULL;
833 for (unsigned i = 0; i < pCfgAst->u.Compound.cAstNodes; i++)
834 {
835 PCFGAST pNode = pCfgAst->u.Compound.apAstNodes[i];
836 com::Utf8Str sDomain;
837 com::Utf8Str sUserTmp;
838 rc = autostartGetDomainAndUser(pNode->pszKey, sDomain, sUserTmp);
839 if (RT_FAILURE(rc))
840 continue;
841 com::Utf8StrFmt sDomainUser("%s\\%s", sDomain.c_str(), sUserTmp.c_str());
842 if (sDomainUser == sUser)
843 {
844 pCfgAstUser = pNode;
845 break;
846 }
847 }
848
849 if ( pCfgAstUser
850 && pCfgAstUser->enmType == CFGASTNODETYPE_COMPOUND)
851 {
852 pCfgAstPolicy = autostartConfigAstGetByName(pCfgAstUser, "allow");
853 if (pCfgAstPolicy)
854 {
855 if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE
856 && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true")
857 || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "false")))
858 fAllow = RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true") == 0;
859 else
860 {
861 autostartConfigAstDestroy(pCfgAst);
862 return autostartSvcLogError("'allow' must be either 'true' or 'false'.\n");
863 }
864 }
865 }
866 else if (pCfgAstUser)
867 {
868 autostartConfigAstDestroy(pCfgAst);
869 return autostartSvcLogError("Invalid config, user is not a compound node.\n");
870 }
871
872 if (!fAllow)
873 {
874 autostartConfigAstDestroy(pCfgAst);
875 return autostartSvcLogError("User is not allowed to autostart VMs.\n");
876 }
877
878 RTEXITCODE rcExit = autostartStartMain(pCfgAstUser);
879 autostartConfigAstDestroy(pCfgAst);
880 if (rcExit != RTEXITCODE_SUCCESS)
881 autostartSvcLogError("Starting VMs failed\n");
882
883 return rcExit;
884}
885
886/**
887 * Windows Service Main.
888 *
889 * This is invoked when the service is started and should not return until
890 * the service has been stopped.
891 *
892 * @param cArgs Argument count.
893 * @param papwszArgs Argument vector.
894 */
895static VOID WINAPI autostartSvcWinServiceMain(DWORD cArgs, LPWSTR *papwszArgs)
896{
897 RT_NOREF(papwszArgs);
898 LogFlowFuncEnter();
899
900 /*
901 * Register the control handler function for the service and report to SCM.
902 */
903 Assert(g_u32SupSvcWinStatus == SERVICE_STOPPED);
904 g_hSupSvcWinCtrlHandler = RegisterServiceCtrlHandlerExW(g_bstrServiceName.raw(), autostartSvcWinServiceCtrlHandlerEx, NULL);
905 if (g_hSupSvcWinCtrlHandler)
906 {
907 DWORD err = ERROR_GEN_FAILURE;
908 if (autostartSvcWinSetServiceStatus(SERVICE_START_PENDING, 3000, NO_ERROR))
909 {
910 if (cArgs == 1)
911 {
912 /*
913 * Create the event semaphore we'll be waiting on and
914 * then instantiate the actual services.
915 */
916 int rc = RTSemEventMultiCreate(&g_hSupSvcWinEvent);
917 if (RT_SUCCESS(rc))
918 {
919 /*
920 * Update the status and enter the work loop.
921 */
922 if (autostartSvcWinSetServiceStatus(SERVICE_RUNNING, 0, 0))
923 {
924 LogFlow(("autostartSvcWinServiceMain: calling autostartStartVMs\n"));
925 RTEXITCODE ec = autostartStartVMs();
926 if (ec == RTEXITCODE_SUCCESS)
927 {
928 LogFlow(("autostartSvcWinServiceMain: done string VMs\n"));
929 err = NO_ERROR;
930 rc = RTSemEventMultiWait(g_hSupSvcWinEvent, RT_INDEFINITE_WAIT);
931 if (RT_SUCCESS(rc))
932 {
933 LogFlow(("autostartSvcWinServiceMain: woke up\n"));
934 /** @todo Autostop part. */
935 err = NO_ERROR;
936 }
937 else
938 autostartSvcLogError("RTSemEventWait failed, rc=%Rrc", rc);
939 }
940
941 autostartShutdown();
942 }
943 else
944 {
945 err = GetLastError();
946 autostartSvcLogError("SetServiceStatus failed, err=%u", err);
947 }
948
949 RTSemEventMultiDestroy(g_hSupSvcWinEvent);
950 g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI;
951 }
952 else
953 autostartSvcLogError("RTSemEventMultiCreate failed, rc=%Rrc", rc);
954 }
955 else
956 autostartSvcLogTooManyArgsError("main", cArgs, NULL, 0);
957 }
958 else
959 {
960 err = GetLastError();
961 autostartSvcLogError("SetServiceStatus failed, err=%u", err);
962 }
963 autostartSvcWinSetServiceStatus(SERVICE_STOPPED, 0, err);
964 }
965 else
966 autostartSvcLogError("RegisterServiceCtrlHandlerEx failed, err=%u", GetLastError());
967
968 LogFlowFuncLeave();
969}
970
971
972/**
973 * Handle the 'create' action.
974 *
975 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
976 * @param argc The action argument count.
977 * @param argv The action argument vector.
978 */
979static int autostartSvcWinRunIt(int argc, char **argv)
980{
981 LogFlowFuncEnter();
982
983 /*
984 * Initialize release logging.
985 */
986 /** @todo release logging of the system-wide service. */
987
988 /*
989 * Parse the arguments.
990 */
991 static const RTGETOPTDEF s_aOptions[] =
992 {
993 { "--service", 's', RTGETOPT_REQ_STRING },
994 };
995
996 const char *pszServiceName = NULL;
997 int ch;
998 RTGETOPTUNION Value;
999 RTGETOPTSTATE GetState;
1000 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1001 while ((ch = RTGetOpt(&GetState, &Value)))
1002 {
1003 switch (ch)
1004 {
1005 case 's':
1006 pszServiceName = Value.psz;
1007 try
1008 {
1009 g_bstrServiceName = com::Bstr(Value.psz);
1010 }
1011 catch (...)
1012 {
1013 autostartSvcLogError("runit failed, service name is not valid utf-8 string or out of memory");
1014 return RTEXITCODE_FAILURE;
1015 }
1016 break;
1017 default:
1018 return autostartSvcDisplayGetOptError("runit", ch, &Value);
1019 }
1020 }
1021
1022 if (!pszServiceName)
1023 {
1024 autostartSvcLogError("runit failed, service name is missing");
1025 return RTEXITCODE_FAILURE;
1026 }
1027
1028 /*
1029 * Register the service with the service control manager
1030 * and start dispatching requests from it (all done by the API).
1031 */
1032 SERVICE_TABLE_ENTRYW const s_aServiceStartTable[] =
1033 {
1034 { g_bstrServiceName.raw(), autostartSvcWinServiceMain },
1035 { NULL, NULL}
1036 };
1037 if (StartServiceCtrlDispatcherW(&s_aServiceStartTable[0]))
1038 {
1039 LogFlowFuncLeave();
1040 return RTEXITCODE_SUCCESS; /* told to quit, so quit. */
1041 }
1042
1043 DWORD err = GetLastError();
1044 switch (err)
1045 {
1046 case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
1047 autostartSvcWinServiceMain(0, NULL);//autostartSvcDisplayError("Cannot run a service from the command line. Use the 'start' action to start it the right way.\n");
1048 break;
1049 default:
1050 autostartSvcLogError("StartServiceCtrlDispatcher failed, err=%u", err);
1051 break;
1052 }
1053 return RTEXITCODE_FAILURE;
1054}
1055
1056
1057/**
1058 * Show the version info.
1059 *
1060 * @returns RTEXITCODE_SUCCESS.
1061 */
1062static RTEXITCODE autostartSvcWinShowVersion(int argc, char **argv)
1063{
1064 /*
1065 * Parse the arguments.
1066 */
1067 bool fBrief = false;
1068 static const RTGETOPTDEF s_aOptions[] =
1069 {
1070 { "--brief", 'b', RTGETOPT_REQ_NOTHING }
1071 };
1072 int ch;
1073 RTGETOPTUNION Value;
1074 RTGETOPTSTATE GetState;
1075 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1076 while ((ch = RTGetOpt(&GetState, &Value)))
1077 switch (ch)
1078 {
1079 case 'b': fBrief = true; break;
1080 default: return autostartSvcDisplayGetOptError("version", ch, &Value);
1081 }
1082
1083 /*
1084 * Do the printing.
1085 */
1086 if (fBrief)
1087 RTPrintf("%s\n", VBOX_VERSION_STRING);
1088 else
1089 RTPrintf("VirtualBox Autostart Service Version %s\n"
1090 "(C) 2012 Oracle Corporation\n"
1091 "All rights reserved.\n",
1092 VBOX_VERSION_STRING);
1093 return RTEXITCODE_SUCCESS;
1094}
1095
1096
1097/**
1098 * Show the usage help screen.
1099 *
1100 * @returns RTEXITCODE_SUCCESS.
1101 */
1102static RTEXITCODE autostartSvcWinShowHelp(void)
1103{
1104 RTPrintf("VirtualBox Autostart Service Version %s\n"
1105 "(C) 2012 Oracle Corporation\n"
1106 "All rights reserved.\n"
1107 "\n",
1108 VBOX_VERSION_STRING);
1109 RTPrintf("Usage:\n"
1110 "\n"
1111 "VBoxAutostartSvc\n"
1112 " Runs the service.\n"
1113 "VBoxAutostartSvc <version|-v|--version> [-brief]\n"
1114 " Displays the version.\n"
1115 "VBoxAutostartSvc <help|-?|-h|--help> [...]\n"
1116 " Displays this help screen.\n"
1117 "\n"
1118 "VBoxAutostartSvc <install|/RegServer|/i>\n"
1119 " Installs the service.\n"
1120 "VBoxAutostartSvc <uninstall|delete|/UnregServer|/u>\n"
1121 " Uninstalls the service.\n"
1122 );
1123 return RTEXITCODE_SUCCESS;
1124}
1125
1126
1127/**
1128 * VBoxAutostart main(), Windows edition.
1129 *
1130 *
1131 * @returns 0 on success.
1132 *
1133 * @param argc Number of arguments in argv.
1134 * @param argv Argument vector.
1135 */
1136int main(int argc, char **argv)
1137{
1138 /*
1139 * Initialize the IPRT first of all.
1140 */
1141 int rc = RTR3InitExe(argc, &argv, 0);
1142 if (RT_FAILURE(rc))
1143 {
1144 autostartSvcLogError("RTR3InitExe failed with rc=%Rrc", rc);
1145 return RTEXITCODE_FAILURE;
1146 }
1147
1148 RTThreadSleep(10 * 1000);
1149
1150 /*
1151 * Parse the initial arguments to determine the desired action.
1152 */
1153 enum
1154 {
1155 kAutoSvcAction_RunIt,
1156
1157 kAutoSvcAction_Create,
1158 kAutoSvcAction_Delete,
1159
1160 kAutoSvcAction_Enable,
1161 kAutoSvcAction_Disable,
1162 kAutoSvcAction_QueryConfig,
1163 kAutoSvcAction_QueryDescription,
1164
1165 kAutoSvcAction_Start,
1166 kAutoSvcAction_Pause,
1167 kAutoSvcAction_Continue,
1168 kAutoSvcAction_Stop,
1169 kAutoSvcAction_Interrogate,
1170
1171 kAutoSvcAction_End
1172 } enmAction = kAutoSvcAction_RunIt;
1173 int iArg = 1;
1174 if (argc > 1)
1175 {
1176 if ( !stricmp(argv[iArg], "/RegServer")
1177 || !stricmp(argv[iArg], "install")
1178 || !stricmp(argv[iArg], "/i"))
1179 enmAction = kAutoSvcAction_Create;
1180 else if ( !stricmp(argv[iArg], "/UnregServer")
1181 || !stricmp(argv[iArg], "/u")
1182 || !stricmp(argv[iArg], "uninstall")
1183 || !stricmp(argv[iArg], "delete"))
1184 enmAction = kAutoSvcAction_Delete;
1185
1186 else if (!stricmp(argv[iArg], "enable"))
1187 enmAction = kAutoSvcAction_Enable;
1188 else if (!stricmp(argv[iArg], "disable"))
1189 enmAction = kAutoSvcAction_Disable;
1190 else if (!stricmp(argv[iArg], "qconfig"))
1191 enmAction = kAutoSvcAction_QueryConfig;
1192 else if (!stricmp(argv[iArg], "qdescription"))
1193 enmAction = kAutoSvcAction_QueryDescription;
1194
1195 else if ( !stricmp(argv[iArg], "start")
1196 || !stricmp(argv[iArg], "/t"))
1197 enmAction = kAutoSvcAction_Start;
1198 else if (!stricmp(argv[iArg], "pause"))
1199 enmAction = kAutoSvcAction_Start;
1200 else if (!stricmp(argv[iArg], "continue"))
1201 enmAction = kAutoSvcAction_Continue;
1202 else if (!stricmp(argv[iArg], "stop"))
1203 enmAction = kAutoSvcAction_Stop;
1204 else if (!stricmp(argv[iArg], "interrogate"))
1205 enmAction = kAutoSvcAction_Interrogate;
1206 else if ( !stricmp(argv[iArg], "help")
1207 || !stricmp(argv[iArg], "?")
1208 || !stricmp(argv[iArg], "/?")
1209 || !stricmp(argv[iArg], "-?")
1210 || !stricmp(argv[iArg], "/h")
1211 || !stricmp(argv[iArg], "-h")
1212 || !stricmp(argv[iArg], "/help")
1213 || !stricmp(argv[iArg], "-help")
1214 || !stricmp(argv[iArg], "--help"))
1215 return autostartSvcWinShowHelp();
1216 else if ( !stricmp(argv[iArg], "version")
1217 || !stricmp(argv[iArg], "/v")
1218 || !stricmp(argv[iArg], "-v")
1219 || !stricmp(argv[iArg], "/version")
1220 || !stricmp(argv[iArg], "-version")
1221 || !stricmp(argv[iArg], "--version"))
1222 return autostartSvcWinShowVersion(argc - iArg - 1, argv + iArg + 1);
1223 else
1224 iArg--;
1225 iArg++;
1226 }
1227
1228 /*
1229 * Dispatch it.
1230 */
1231 switch (enmAction)
1232 {
1233 case kAutoSvcAction_RunIt:
1234 return autostartSvcWinRunIt(argc - iArg, argv + iArg);
1235
1236 case kAutoSvcAction_Create:
1237 return autostartSvcWinCreate(argc - iArg, argv + iArg);
1238 case kAutoSvcAction_Delete:
1239 return autostartSvcWinDelete(argc - iArg, argv + iArg);
1240
1241 case kAutoSvcAction_Enable:
1242 return autostartSvcWinEnable(argc - iArg, argv + iArg);
1243 case kAutoSvcAction_Disable:
1244 return autostartSvcWinDisable(argc - iArg, argv + iArg);
1245 case kAutoSvcAction_QueryConfig:
1246 return autostartSvcWinQueryConfig(argc - iArg, argv + iArg);
1247 case kAutoSvcAction_QueryDescription:
1248 return autostartSvcWinQueryDescription(argc - iArg, argv + iArg);
1249
1250 case kAutoSvcAction_Start:
1251 return autostartSvcWinStart(argc - iArg, argv + iArg);
1252 case kAutoSvcAction_Pause:
1253 return autostartSvcWinPause(argc - iArg, argv + iArg);
1254 case kAutoSvcAction_Continue:
1255 return autostartSvcWinContinue(argc - iArg, argv + iArg);
1256 case kAutoSvcAction_Stop:
1257 return autostartSvcWinStop(argc - iArg, argv + iArg);
1258 case kAutoSvcAction_Interrogate:
1259 return autostartSvcWinInterrogate(argc - iArg, argv + iArg);
1260
1261 default:
1262 AssertMsgFailed(("enmAction=%d\n", enmAction));
1263 return RTEXITCODE_FAILURE;
1264 }
1265}
1266
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette