VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxAutostart/VBoxAutostart-posix.cpp@ 103068

Last change on this file since 103068 was 101227, checked in by vboxsync, 10 months ago

FE/VBoxAutostart-posix: Add a config key (per user) which sets VBOX_USER_HOME env variable. This assumes that users normally don't need it, and those who use it have no VMs in the usual place.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.4 KB
Line 
1/* $Id: VBoxAutostart-posix.cpp 101227 2023-09-21 19:14:56Z vboxsync $ */
2/** @file
3 * VBoxAutostart - VirtualBox Autostart service.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <VBox/com/com.h>
33#include <VBox/com/string.h>
34#include <VBox/com/Guid.h>
35#include <VBox/com/array.h>
36#include <VBox/com/ErrorInfo.h>
37#include <VBox/com/errorprint.h>
38
39#include <VBox/com/NativeEventQueue.h>
40#include <VBox/com/listeners.h>
41#include <VBox/com/VirtualBox.h>
42
43#include <iprt/errcore.h>
44#include <VBox/log.h>
45#include <VBox/version.h>
46
47#include <package-generated.h>
48
49#include <iprt/asm.h>
50#include <iprt/buildconfig.h>
51#include <iprt/critsect.h>
52#include <iprt/getopt.h>
53#include <iprt/initterm.h>
54#include <iprt/message.h>
55#include <iprt/path.h>
56#include <iprt/process.h>
57#include <iprt/semaphore.h>
58#include <iprt/stream.h>
59#include <iprt/string.h>
60#include <iprt/system.h>
61#include <iprt/time.h>
62#include <iprt/ctype.h>
63#include <iprt/dir.h>
64#include <iprt/env.h>
65
66#include <signal.h>
67
68#include "VBoxAutostart.h"
69
70using namespace com;
71
72#if defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) || defined(RT_OS_DARWIN)
73# define VBOXAUTOSTART_DAEMONIZE
74#endif
75
76ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
77ComPtr<IVirtualBox> g_pVirtualBox = NULL;
78ComPtr<ISession> g_pSession = NULL;
79
80/** Logging parameters. */
81static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
82static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
83static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
84
85/** Verbosity level. */
86unsigned g_cVerbosity = 0;
87
88/** Run in background. */
89static bool g_fDaemonize = false;
90
91/**
92 * Command line arguments.
93 */
94static const RTGETOPTDEF g_aOptions[] = {
95#ifdef VBOXAUTOSTART_DAEMONIZE
96 { "--background", 'b', RTGETOPT_REQ_NOTHING },
97#endif
98 /** For displayHelp(). */
99 { "--help", 'h', RTGETOPT_REQ_NOTHING },
100 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
101 { "--start", 's', RTGETOPT_REQ_NOTHING },
102 { "--stop", 'd', RTGETOPT_REQ_NOTHING },
103 { "--config", 'c', RTGETOPT_REQ_STRING },
104 { "--logfile", 'F', RTGETOPT_REQ_STRING },
105 { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
106 { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
107 { "--loginterval", 'I', RTGETOPT_REQ_UINT32 },
108 { "--quiet", 'Q', RTGETOPT_REQ_NOTHING }
109};
110
111/** Set by the signal handler. */
112static volatile bool g_fCanceled = false;
113
114
115/**
116 * Signal handler that sets g_fCanceled.
117 *
118 * This can be executed on any thread in the process, on Windows it may even be
119 * a thread dedicated to delivering this signal. Do not doing anything
120 * unnecessary here.
121 */
122static void showProgressSignalHandler(int iSignal)
123{
124 NOREF(iSignal);
125 ASMAtomicWriteBool(&g_fCanceled, true);
126}
127
128/**
129 * Print out progress on the console.
130 *
131 * This runs the main event queue every now and then to prevent piling up
132 * unhandled things (which doesn't cause real problems, just makes things
133 * react a little slower than in the ideal case).
134 */
135DECLHIDDEN(HRESULT) showProgress(ComPtr<IProgress> progress)
136{
137 using namespace com;
138
139 BOOL fCompleted = FALSE;
140 ULONG ulCurrentPercent = 0;
141 ULONG ulLastPercent = 0;
142
143 Bstr bstrOperationDescription;
144
145 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
146
147 ULONG cOperations = 1;
148 HRESULT hrc = progress->COMGETTER(OperationCount)(&cOperations);
149 if (FAILED(hrc))
150 {
151 RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc);
152 RTStrmFlush(g_pStdErr);
153 return hrc;
154 }
155
156 /*
157 * Note: Outputting the progress info to stderr (g_pStdErr) is intentional
158 * to not get intermixed with other (raw) stdout data which might get
159 * written in the meanwhile.
160 */
161 RTStrmPrintf(g_pStdErr, "0%%...");
162 RTStrmFlush(g_pStdErr);
163
164 /* setup signal handling if cancelable */
165 bool fCanceledAlready = false;
166 BOOL fCancelable;
167 hrc = progress->COMGETTER(Cancelable)(&fCancelable);
168 if (FAILED(hrc))
169 fCancelable = FALSE;
170 if (fCancelable)
171 {
172 signal(SIGINT, showProgressSignalHandler);
173#ifdef SIGBREAK
174 signal(SIGBREAK, showProgressSignalHandler);
175#endif
176 }
177
178 hrc = progress->COMGETTER(Completed(&fCompleted));
179 while (SUCCEEDED(hrc))
180 {
181 progress->COMGETTER(Percent(&ulCurrentPercent));
182
183 /* did we cross a 10% mark? */
184 if (ulCurrentPercent / 10 > ulLastPercent / 10)
185 {
186 /* make sure to also print out missed steps */
187 for (ULONG curVal = (ulLastPercent / 10) * 10 + 10; curVal <= (ulCurrentPercent / 10) * 10; curVal += 10)
188 {
189 if (curVal < 100)
190 {
191 RTStrmPrintf(g_pStdErr, "%u%%...", curVal);
192 RTStrmFlush(g_pStdErr);
193 }
194 }
195 ulLastPercent = (ulCurrentPercent / 10) * 10;
196 }
197
198 if (fCompleted)
199 break;
200
201 /* process async cancelation */
202 if (g_fCanceled && !fCanceledAlready)
203 {
204 hrc = progress->Cancel();
205 if (SUCCEEDED(hrc))
206 fCanceledAlready = true;
207 else
208 g_fCanceled = false;
209 }
210
211 /* make sure the loop is not too tight */
212 progress->WaitForCompletion(100);
213
214 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
215 hrc = progress->COMGETTER(Completed(&fCompleted));
216 }
217
218 /* undo signal handling */
219 if (fCancelable)
220 {
221 signal(SIGINT, SIG_DFL);
222#ifdef SIGBREAK
223 signal(SIGBREAK, SIG_DFL);
224#endif
225 }
226
227 /* complete the line. */
228 LONG iRc = E_FAIL;
229 hrc = progress->COMGETTER(ResultCode)(&iRc);
230 if (SUCCEEDED(hrc))
231 {
232 if (SUCCEEDED(iRc))
233 RTStrmPrintf(g_pStdErr, "100%%\n");
234 else if (g_fCanceled)
235 RTStrmPrintf(g_pStdErr, "CANCELED\n");
236 else
237 {
238 RTStrmPrintf(g_pStdErr, "\n");
239 RTStrmPrintf(g_pStdErr, "Progress state: %Rhrc\n", iRc);
240 }
241 hrc = iRc;
242 }
243 else
244 {
245 RTStrmPrintf(g_pStdErr, "\n");
246 RTStrmPrintf(g_pStdErr, "Progress object failure: %Rhrc\n", hrc);
247 }
248 RTStrmFlush(g_pStdErr);
249 return hrc;
250}
251
252DECLHIDDEN(void) autostartSvcOsLogStr(const char *pszMsg, AUTOSTARTLOGTYPE enmLogType)
253{
254 if ( enmLogType == AUTOSTARTLOGTYPE_VERBOSE
255 && !g_cVerbosity)
256 return;
257
258 LogRel(("%s", pszMsg));
259}
260
261/**
262 * Shows the help.
263 *
264 * @param pszImage Name of program name (image).
265 */
266static void showHelp(const char *pszImage)
267{
268 AssertPtrReturnVoid(pszImage);
269
270 autostartSvcShowHeader();
271
272 RTStrmPrintf(g_pStdErr,
273 "Usage: %s [-v|--verbose] [-h|-?|--help]\n"
274 " [-V|--version]\n"
275 " [-F|--logfile=<file>] [-R|--logrotate=<num>]\n"
276 " [-S|--logsize=<bytes>] [-I|--loginterval=<seconds>]\n"
277 " [-c|--config=<config file>]\n",
278 pszImage);
279
280 RTStrmPrintf(g_pStdErr,
281 "\n"
282 "Options:\n");
283 for (unsigned i = 0; i < RT_ELEMENTS(g_aOptions); i++)
284 {
285 const char *pcszDescr;
286 switch (g_aOptions[i].iShort)
287 {
288 case 'h':
289 pcszDescr = "Prints this help message and exit.";
290 break;
291
292#ifdef VBOXAUTOSTART_DAEMONIZE
293 case 'b':
294 pcszDescr = "Run in background (daemon mode).";
295 break;
296#endif
297
298 case 'F':
299 pcszDescr = "Name of file to write log to (no file).";
300 break;
301
302 case 'R':
303 pcszDescr = "Number of log files (0 disables log rotation).";
304 break;
305
306 case 'S':
307 pcszDescr = "Maximum size of a log file to trigger rotation (bytes).";
308 break;
309
310 case 'I':
311 pcszDescr = "Maximum time interval to trigger log rotation (seconds).";
312 break;
313
314 case 'c':
315 pcszDescr = "Name of the configuration file for the global overrides.";
316 break;
317
318 case 'V':
319 pcszDescr = "Shows the service version.";
320 break;
321
322 default:
323 AssertFailedBreakStmt(pcszDescr = "");
324 }
325
326 if (g_aOptions[i].iShort < 1000)
327 RTStrmPrintf(g_pStdErr,
328 " %s, -%c\n"
329 " %s\n", g_aOptions[i].pszLong, g_aOptions[i].iShort, pcszDescr);
330 else
331 RTStrmPrintf(g_pStdErr,
332 " %s\n"
333 " %s\n", g_aOptions[i].pszLong, pcszDescr);
334 }
335
336 RTStrmPrintf(g_pStdErr,
337 "\n"
338 "Use environment variable VBOXAUTOSTART_RELEASE_LOG for logging options.\n");
339}
340
341int main(int argc, char *argv[])
342{
343 /*
344 * Before we do anything, init the runtime without loading
345 * the support driver.
346 */
347 int rc = RTR3InitExe(argc, &argv, 0);
348 if (RT_FAILURE(rc))
349 return RTMsgInitFailure(rc);
350
351 /*
352 * Parse the global options
353 */
354 int c;
355 const char *pszLogFile = NULL;
356 const char *pszConfigFile = NULL;
357 bool fQuiet = false;
358 bool fStart = false;
359 bool fStop = false;
360 RTGETOPTUNION ValueUnion;
361 RTGETOPTSTATE GetState;
362 RTGetOptInit(&GetState, argc, argv,
363 g_aOptions, RT_ELEMENTS(g_aOptions), 1 /* First */, 0 /*fFlags*/);
364 while ((c = RTGetOpt(&GetState, &ValueUnion)))
365 {
366 switch (c)
367 {
368 case 'h':
369 showHelp(argv[0]);
370 return RTEXITCODE_SUCCESS;
371
372 case 'v':
373 g_cVerbosity++;
374 break;
375
376#ifdef VBOXAUTOSTART_DAEMONIZE
377 case 'b':
378 g_fDaemonize = true;
379 break;
380#endif
381 case 'V':
382 autostartSvcShowVersion(false);
383 return RTEXITCODE_SUCCESS;
384
385 case 'F':
386 pszLogFile = ValueUnion.psz;
387 break;
388
389 case 'R':
390 g_cHistory = ValueUnion.u32;
391 break;
392
393 case 'S':
394 g_uHistoryFileSize = ValueUnion.u64;
395 break;
396
397 case 'I':
398 g_uHistoryFileTime = ValueUnion.u32;
399 break;
400
401 case 'Q':
402 fQuiet = true;
403 break;
404
405 case 'c':
406 pszConfigFile = ValueUnion.psz;
407 break;
408
409 case 's':
410 fStart = true;
411 break;
412
413 case 'd':
414 fStop = true;
415 break;
416
417 default:
418 return RTGetOptPrintError(c, &ValueUnion);
419 }
420 }
421
422 if (!fStart && !fStop)
423 {
424 showHelp(argv[0]);
425 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Either --start or --stop must be present");
426 }
427 else if (fStart && fStop)
428 {
429 showHelp(argv[0]);
430 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--start or --stop are mutually exclusive");
431 }
432
433 if (!pszConfigFile)
434 {
435 showHelp(argv[0]);
436 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--config <config file> is missing");
437 }
438
439 if (!fQuiet)
440 autostartSvcShowHeader();
441
442 PCFGAST pCfgAst = NULL;
443 char *pszUser = NULL;
444 PCFGAST pCfgAstUser = NULL;
445 PCFGAST pCfgAstPolicy = NULL;
446 PCFGAST pCfgAstUserHome = NULL;
447 bool fAllow = false;
448
449 rc = autostartParseConfig(pszConfigFile, &pCfgAst);
450 if (RT_FAILURE(rc))
451 return RTEXITCODE_FAILURE;
452
453 rc = RTProcQueryUsernameA(RTProcSelf(), &pszUser);
454 if (RT_FAILURE(rc))
455 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to query username of the process");
456
457 pCfgAstUser = autostartConfigAstGetByName(pCfgAst, pszUser);
458 pCfgAstPolicy = autostartConfigAstGetByName(pCfgAst, "default_policy");
459
460 /* Check default policy. */
461 if (pCfgAstPolicy)
462 {
463 if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE
464 && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow")
465 || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "deny")))
466 {
467 if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "allow"))
468 fAllow = true;
469 }
470 else
471 return RTMsgErrorExit(RTEXITCODE_FAILURE, "'default_policy' must be either 'allow' or 'deny'");
472 }
473
474 if ( pCfgAstUser
475 && pCfgAstUser->enmType == CFGASTNODETYPE_COMPOUND)
476 {
477 pCfgAstPolicy = autostartConfigAstGetByName(pCfgAstUser, "allow");
478 if (pCfgAstPolicy)
479 {
480 if ( pCfgAstPolicy->enmType == CFGASTNODETYPE_KEYVALUE
481 && ( !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true")
482 || !RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "false")))
483 {
484 if (!RTStrCmp(pCfgAstPolicy->u.KeyValue.aszValue, "true"))
485 fAllow = true;
486 else
487 fAllow = false;
488 }
489 else
490 return RTMsgErrorExit(RTEXITCODE_FAILURE, "'allow' must be either 'true' or 'false'");
491 }
492 pCfgAstUserHome = autostartConfigAstGetByName(pCfgAstUser, "VBOX_USER_HOME");
493 if ( pCfgAstUserHome
494 && pCfgAstUserHome->enmType == CFGASTNODETYPE_KEYVALUE)
495 {
496 rc = RTEnvSet("VBOX_USER_HOME", pCfgAstUserHome->u.KeyValue.aszValue);
497 if (RT_FAILURE(rc))
498 return RTMsgErrorExit(RTEXITCODE_FAILURE, "'VBOX_USER_HOME' could not be set for this user");
499 }
500 }
501 else if (pCfgAstUser)
502 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid config, user is not a compound node");
503
504 if (!fAllow)
505 return RTMsgErrorExit(RTEXITCODE_FAILURE, "User is not allowed to autostart VMs");
506
507 RTStrFree(pszUser);
508
509 /* Don't start if the VirtualBox settings directory does not exist. */
510 char szUserHomeDir[RTPATH_MAX];
511 rc = com::GetVBoxUserHomeDirectory(szUserHomeDir, sizeof(szUserHomeDir), false /* fCreateDir */);
512 if (RT_FAILURE(rc))
513 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory: %Rrc", rc);
514 else if (!RTDirExists(szUserHomeDir))
515 return RTEXITCODE_SUCCESS;
516
517 /* create release logger, to stdout */
518 RTERRINFOSTATIC ErrInfo;
519 rc = com::VBoxLogRelCreate("Autostart", g_fDaemonize ? NULL : pszLogFile,
520 RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
521 "all", "VBOXAUTOSTART_RELEASE_LOG",
522 RTLOGDEST_STDOUT, UINT32_MAX /* cMaxEntriesPerGroup */,
523 g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
524 RTErrInfoInitStatic(&ErrInfo));
525 if (RT_FAILURE(rc))
526 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc);
527
528#ifdef VBOXAUTOSTART_DAEMONIZE
529 if (g_fDaemonize)
530 {
531 /* prepare release logging */
532 char szLogFile[RTPATH_MAX];
533
534 if (!pszLogFile || !*pszLogFile)
535 {
536 rc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
537 if (RT_FAILURE(rc))
538 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", rc);
539 rc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxautostart.log");
540 if (RT_FAILURE(rc))
541 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", rc);
542 pszLogFile = szLogFile;
543 }
544
545 rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, NULL);
546 if (RT_FAILURE(rc))
547 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc);
548 /* create release logger, to file */
549 rc = com::VBoxLogRelCreate("Autostart", pszLogFile,
550 RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
551 "all", "VBOXAUTOSTART_RELEASE_LOG",
552 RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */,
553 g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
554 RTErrInfoInitStatic(&ErrInfo));
555 if (RT_FAILURE(rc))
556 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, rc);
557 }
558#endif
559
560 /* Set up COM */
561 rc = autostartSetup();
562 if (RT_FAILURE(rc))
563 return RTEXITCODE_FAILURE;
564
565 if (fStart)
566 rc = autostartStartMain(pCfgAstUser);
567 else
568 {
569 Assert(fStop);
570 rc = autostartStopMain(pCfgAstUser);
571 }
572
573 autostartConfigAstDestroy(pCfgAst);
574 NativeEventQueue::getMainEventQueue()->processEventQueue(0);
575 autostartShutdown();
576
577 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
578}
579
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use