1 | /* $Id: MachineLaunchVMCommonWorker.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * VirtualBox Main - VM process launcher helper for VBoxSVC & VBoxSDS.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2011-2024 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 <iprt/dir.h>
|
---|
33 | #include <iprt/env.h>
|
---|
34 | #include <iprt/err.h>
|
---|
35 | #include <iprt/file.h>
|
---|
36 | #include <iprt/log.h>
|
---|
37 | #include <iprt/path.h>
|
---|
38 | #include <iprt/process.h>
|
---|
39 | #include "MachineLaunchVMCommonWorker.h"
|
---|
40 |
|
---|
41 |
|
---|
42 | /*********************************************************************************************************************************
|
---|
43 | * Defined Constants And Macros *
|
---|
44 | *********************************************************************************************************************************/
|
---|
45 | #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
|
---|
46 | # define HOSTSUFF_EXE ".exe"
|
---|
47 | #else
|
---|
48 | # define HOSTSUFF_EXE ""
|
---|
49 | #endif
|
---|
50 |
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * Launch a VM process.
|
---|
54 | *
|
---|
55 | * The function starts the new VM process. It is a caller's responsibility
|
---|
56 | * to make any checks before and after calling the function.
|
---|
57 | * The function is a part of both VBoxSVC and VBoxSDS, so any calls to IVirtualBox
|
---|
58 | * and IMachine interfaces are performed using the client API.
|
---|
59 | *
|
---|
60 | * @returns VBox status code.
|
---|
61 | * @retval VINF_SUCCESS when new VM process started.
|
---|
62 | * @retval VERR_INVALID_PARAMETER when either aMachine is not Machine interface
|
---|
63 | * or invalid aFrontend is specified. Hmm. Come to think of it, it
|
---|
64 | * could also be returned in some other cases, especially if the code
|
---|
65 | * is buggy, so I wouldn't rely on any exact meaning here!
|
---|
66 | * @retval VERR_INTERNAL_ERROR when something wrong.
|
---|
67 | *
|
---|
68 | * @param aNameOrId The Machine name or id interface the VM will start for.
|
---|
69 | * @param aComment The comment for new VM process.
|
---|
70 | * @param aFrontend The desired frontend for started VM.
|
---|
71 | * @param aEnvironmentChanges Additional environment variables in the putenv style
|
---|
72 | * (VAR=VAL for setting, VAR for unsetting) for new VM process.
|
---|
73 | * @param aExtraArg Extra argument for the VM process. Ignored if
|
---|
74 | * empty string.
|
---|
75 | * @param aFilename Start new VM using specified filename. Only filename
|
---|
76 | * without path is allowed. Default filename is used if
|
---|
77 | * empty.
|
---|
78 | * @param aFlags Flags for RTProcCreateEx functions family if
|
---|
79 | * required (RTPROC_FLAGS_XXX).
|
---|
80 | * @param aExtraData Additional data for RTProcCreateX functions family
|
---|
81 | * if required. Content is defined by the flags.
|
---|
82 | * @param aPid The PID of created process is returned here
|
---|
83 | */
|
---|
84 | int MachineLaunchVMCommonWorker(const Utf8Str &aNameOrId,
|
---|
85 | const Utf8Str &aComment,
|
---|
86 | const Utf8Str &aFrontend,
|
---|
87 | const std::vector<com::Utf8Str> &aEnvironmentChanges,
|
---|
88 | const Utf8Str &aExtraArg,
|
---|
89 | const Utf8Str &aFilename,
|
---|
90 | uint32_t aFlags,
|
---|
91 | void *aExtraData,
|
---|
92 | RTPROCESS &aPid)
|
---|
93 | {
|
---|
94 | NOREF(aNameOrId);
|
---|
95 | NOREF(aComment);
|
---|
96 | NOREF(aFlags);
|
---|
97 | NOREF(aExtraData);
|
---|
98 | NOREF(aExtraArg);
|
---|
99 | NOREF(aFilename);
|
---|
100 |
|
---|
101 | /* Get the path to the executable directory w/ trailing slash: */
|
---|
102 | char szPath[RTPATH_MAX];
|
---|
103 | int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
|
---|
104 | AssertRCReturn(vrc, vrc);
|
---|
105 | size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
|
---|
106 | AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
|
---|
107 | char *pszNamePart = &szPath[cbBufLeft]; NOREF(pszNamePart);
|
---|
108 | cbBufLeft = sizeof(szPath) - cbBufLeft;
|
---|
109 |
|
---|
110 | /* The process started when launching a VM with separate UI/VM processes is always
|
---|
111 | * the UI process, i.e. needs special handling as it won't claim the session. */
|
---|
112 | bool fSeparate = aFrontend.endsWith("separate", Utf8Str::CaseInsensitive); NOREF(fSeparate);
|
---|
113 |
|
---|
114 | aPid = NIL_RTPROCESS;
|
---|
115 |
|
---|
116 | RTENV hEnv = RTENV_DEFAULT;
|
---|
117 | if (!aEnvironmentChanges.empty())
|
---|
118 | {
|
---|
119 | #ifdef IN_VBOXSVC
|
---|
120 | /* VBoxSVC: clone the current environment */
|
---|
121 | vrc = RTEnvClone(&hEnv, RTENV_DEFAULT);
|
---|
122 | #else
|
---|
123 | /* VBoxSDS: Create a change record environment since RTProcCreateEx has to
|
---|
124 | build the final environment from the profile of the VBoxSDS caller. */
|
---|
125 | aFlags |= RTPROC_FLAGS_ENV_CHANGE_RECORD;
|
---|
126 | vrc = RTEnvCreateChangeRecord(&hEnv);
|
---|
127 | #endif
|
---|
128 | AssertRCReturn(vrc, vrc);
|
---|
129 |
|
---|
130 | /* Apply the specified environment changes. */
|
---|
131 | for (std::vector<com::Utf8Str>::const_iterator itEnv = aEnvironmentChanges.begin();
|
---|
132 | itEnv != aEnvironmentChanges.end();
|
---|
133 | ++itEnv)
|
---|
134 | {
|
---|
135 | vrc = RTEnvPutEx(hEnv, itEnv->c_str());
|
---|
136 | AssertRCReturnStmt(vrc, RTEnvDestroy(hEnv), vrc);
|
---|
137 | }
|
---|
138 | }
|
---|
139 |
|
---|
140 | #ifdef VBOX_WITH_QTGUI
|
---|
141 | if ( !aFrontend.compare("gui", Utf8Str::CaseInsensitive)
|
---|
142 | || !aFrontend.compare("GUI/Qt", Utf8Str::CaseInsensitive)
|
---|
143 | || !aFrontend.compare("separate", Utf8Str::CaseInsensitive)
|
---|
144 | || !aFrontend.compare("gui/separate", Utf8Str::CaseInsensitive)
|
---|
145 | || !aFrontend.compare("GUI/Qt/separate", Utf8Str::CaseInsensitive))
|
---|
146 | {
|
---|
147 | # ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */
|
---|
148 |
|
---|
149 | # define OSX_APP_NAME "VirtualBoxVM"
|
---|
150 | # define OSX_APP_PATH_FMT "/Resources/%s.app/Contents/MacOS/VirtualBoxVM"
|
---|
151 | # define OSX_APP_PATH_WITH_NAME "/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM"
|
---|
152 |
|
---|
153 | /* Modify the base path so that we don't need to use ".." below. */
|
---|
154 | RTPathStripTrailingSlash(szPath);
|
---|
155 | RTPathStripFilename(szPath);
|
---|
156 | cbBufLeft = strlen(szPath);
|
---|
157 | pszNamePart = &szPath[cbBufLeft]; Assert(!*pszNamePart);
|
---|
158 | cbBufLeft = sizeof(szPath) - cbBufLeft;
|
---|
159 |
|
---|
160 | if (aFilename.isNotEmpty() && strpbrk(aFilename.c_str(), "./\\:") == NULL)
|
---|
161 | {
|
---|
162 | ssize_t cch = RTStrPrintf2(pszNamePart, cbBufLeft, OSX_APP_PATH_FMT, aFilename.c_str());
|
---|
163 | AssertReturn(cch > 0, VERR_FILENAME_TOO_LONG);
|
---|
164 | /* there is a race, but people using this deserve the failure */
|
---|
165 | if (!RTFileExists(szPath))
|
---|
166 | *pszNamePart = '\0';
|
---|
167 | }
|
---|
168 | if (!*pszNamePart)
|
---|
169 | {
|
---|
170 | vrc = RTStrCopy(pszNamePart, cbBufLeft, OSX_APP_PATH_WITH_NAME);
|
---|
171 | AssertRCReturn(vrc, vrc);
|
---|
172 | }
|
---|
173 | # else
|
---|
174 | static const char s_szVirtualBox_exe[] = "VirtualBoxVM" HOSTSUFF_EXE;
|
---|
175 | vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
|
---|
176 | AssertRCReturn(vrc, vrc);
|
---|
177 | # endif
|
---|
178 |
|
---|
179 | const char *apszArgs[] =
|
---|
180 | {
|
---|
181 | szPath,
|
---|
182 | "--comment", aComment.c_str(),
|
---|
183 | "--startvm", aNameOrId.c_str(),
|
---|
184 | "--no-startvm-errormsgbox",
|
---|
185 | NULL, /* For "--separate". */
|
---|
186 | NULL, /* For "--sup-startup-log". */
|
---|
187 | NULL
|
---|
188 | };
|
---|
189 | unsigned iArg = 6;
|
---|
190 | if (fSeparate)
|
---|
191 | apszArgs[iArg++] = "--separate";
|
---|
192 | if (aExtraArg.isNotEmpty())
|
---|
193 | apszArgs[iArg++] = aExtraArg.c_str();
|
---|
194 |
|
---|
195 | vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
|
---|
196 | }
|
---|
197 | #else /* !VBOX_WITH_QTGUI */
|
---|
198 | if (0)
|
---|
199 | ;
|
---|
200 | #endif /* VBOX_WITH_QTGUI */
|
---|
201 |
|
---|
202 | else
|
---|
203 |
|
---|
204 | #ifdef VBOX_WITH_VBOXSDL
|
---|
205 | if ( !aFrontend.compare("sdl", Utf8Str::CaseInsensitive)
|
---|
206 | || !aFrontend.compare("GUI/SDL", Utf8Str::CaseInsensitive)
|
---|
207 | || !aFrontend.compare("sdl/separate", Utf8Str::CaseInsensitive)
|
---|
208 | || !aFrontend.compare("GUI/SDL/separate", Utf8Str::CaseInsensitive))
|
---|
209 | {
|
---|
210 | static const char s_szVBoxSDL_exe[] = "VBoxSDL" HOSTSUFF_EXE;
|
---|
211 | vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxSDL_exe);
|
---|
212 | AssertRCReturn(vrc, vrc);
|
---|
213 |
|
---|
214 | const char *apszArgs[] =
|
---|
215 | {
|
---|
216 | szPath,
|
---|
217 | "--comment", aComment.c_str(),
|
---|
218 | "--startvm", aNameOrId.c_str(),
|
---|
219 | NULL, /* For "--separate". */
|
---|
220 | NULL, /* For "--sup-startup-log". */
|
---|
221 | NULL
|
---|
222 | };
|
---|
223 | unsigned iArg = 5;
|
---|
224 | if (fSeparate)
|
---|
225 | apszArgs[iArg++] = "--separate";
|
---|
226 | if (aExtraArg.isNotEmpty())
|
---|
227 | apszArgs[iArg++] = aExtraArg.c_str();
|
---|
228 |
|
---|
229 | vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
|
---|
230 | }
|
---|
231 | #else /* !VBOX_WITH_VBOXSDL */
|
---|
232 | if (0)
|
---|
233 | ;
|
---|
234 | #endif /* !VBOX_WITH_VBOXSDL */
|
---|
235 |
|
---|
236 | else
|
---|
237 |
|
---|
238 | #ifdef VBOX_WITH_HEADLESS
|
---|
239 | if ( !aFrontend.compare("headless", Utf8Str::CaseInsensitive)
|
---|
240 | || !aFrontend.compare("capture", Utf8Str::CaseInsensitive)
|
---|
241 | || !aFrontend.compare("vrdp", Utf8Str::CaseInsensitive) /* Deprecated. Same as headless. */
|
---|
242 | )
|
---|
243 | {
|
---|
244 | /* On pre-4.0 the "headless" type was used for passing "--vrdp off" to VBoxHeadless to let it work in OSE,
|
---|
245 | * which did not contain VRDP server. In VBox 4.0 the remote desktop server (VRDE) is optional,
|
---|
246 | * and a VM works even if the server has not been installed.
|
---|
247 | * So in 4.0 the "headless" behavior remains the same for default VBox installations.
|
---|
248 | * Only if a VRDE has been installed and the VM enables it, the "headless" will work
|
---|
249 | * differently in 4.0 and 3.x.
|
---|
250 | */
|
---|
251 | static const char s_szVBoxHeadless_exe[] = "VBoxHeadless" HOSTSUFF_EXE;
|
---|
252 | vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxHeadless_exe);
|
---|
253 | AssertRCReturn(vrc, vrc);
|
---|
254 |
|
---|
255 | const char *apszArgs[] =
|
---|
256 | {
|
---|
257 | szPath,
|
---|
258 | "--comment", aComment.c_str(),
|
---|
259 | "--startvm", aNameOrId.c_str(),
|
---|
260 | "--vrde", "config",
|
---|
261 | NULL, /* For "--capture". */
|
---|
262 | NULL, /* For "--sup-startup-log". */
|
---|
263 | NULL
|
---|
264 | };
|
---|
265 | unsigned iArg = 7;
|
---|
266 | if (!aFrontend.compare("capture", Utf8Str::CaseInsensitive))
|
---|
267 | apszArgs[iArg++] = "--capture";
|
---|
268 | if (aExtraArg.isNotEmpty())
|
---|
269 | apszArgs[iArg++] = aExtraArg.c_str();
|
---|
270 |
|
---|
271 | # ifdef RT_OS_WINDOWS
|
---|
272 | aFlags |= RTPROC_FLAGS_NO_WINDOW;
|
---|
273 | # endif
|
---|
274 | vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
|
---|
275 | }
|
---|
276 | #else /* !VBOX_WITH_HEADLESS */
|
---|
277 | if (0)
|
---|
278 | ;
|
---|
279 | #endif /* !VBOX_WITH_HEADLESS */
|
---|
280 | else
|
---|
281 | vrc = VERR_INVALID_PARAMETER;
|
---|
282 |
|
---|
283 | RTEnvDestroy(hEnv);
|
---|
284 |
|
---|
285 | if (RT_FAILURE(vrc))
|
---|
286 | return vrc;
|
---|
287 |
|
---|
288 | return VINF_SUCCESS;
|
---|
289 | }
|
---|