summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-all/MachineLaunchVMCommonWorker.cpp
blob: 01d7a9f3a3d0acbda1e3a96d26b3b7537d10bf3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/* $Id: MachineLaunchVMCommonWorker.cpp $ */
/** @file
 * VirtualBox Main - VM process launcher helper for VBoxSVC & VBoxSDS.
 */

/*
 * Copyright (C) 2011-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/dir.h>
#include <iprt/env.h>
#include <iprt/err.h>
#include <iprt/file.h>
#include <iprt/log.h>
#include <iprt/path.h>
#include <iprt/process.h>
#include "MachineLaunchVMCommonWorker.h"


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
# define HOSTSUFF_EXE ".exe"
#else
# define HOSTSUFF_EXE ""
#endif


/**
 * Launch a VM process.
 *
 * The function starts the new VM process. It is a caller's responsibility
 * to make any checks before and after calling the function.
 * The function is a part of both VBoxSVC and VBoxSDS, so any calls to IVirtualBox
 * and IMachine interfaces are performed using the client API.
 *
 * @returns VBox status code.
 * @retval  VINF_SUCCESS when new VM process started.
 * @retval  VERR_INVALID_PARAMETER when either aMachine is not Machine interface
 *          or invalid aFrontend is specified.  Hmm. Come to think of it, it
 *          could also be returned in some other cases, especially if the code
 *          is buggy, so I wouldn't rely on any exact meaning here!
 * @retval  VERR_INTERNAL_ERROR when something wrong.
 *
 * @param   aNameOrId            The Machine name or id interface the VM will start for.
 * @param   aComment             The comment for new VM process.
 * @param   aFrontend            The desired frontend for started VM.
 * @param   aEnvironmentChanges  Additional environment variables in the putenv style
 *                               (VAR=VAL for setting, VAR for unsetting) for new VM process.
 * @param   aExtraArg            Extra argument for the VM process.  Ignored if
 *                               empty string.
 * @param   aFilename            Start new VM using specified filename. Only filename
 *                               without path is allowed. Default filename is used if
 *                               empty.
 * @param   aFlags               Flags for RTProcCreateEx functions family if
 *                               required (RTPROC_FLAGS_XXX).
 * @param   aExtraData           Additional data for RTProcCreateX functions family
 *                               if required.  Content is defined by the flags.
 * @param   aPid                 The PID of created process is returned here
 */
int MachineLaunchVMCommonWorker(const Utf8Str &aNameOrId,
                                const Utf8Str &aComment,
                                const Utf8Str &aFrontend,
                                const std::vector<com::Utf8Str> &aEnvironmentChanges,
                                const Utf8Str &aExtraArg,
                                const Utf8Str &aFilename,
                                uint32_t      aFlags,
                                void         *aExtraData,
                                RTPROCESS     &aPid)
{
    NOREF(aNameOrId);
    NOREF(aComment);
    NOREF(aFlags);
    NOREF(aExtraData);
    NOREF(aExtraArg);
    NOREF(aFilename);

    /* Get the path to the executable directory w/ trailing slash: */
    char szPath[RTPATH_MAX];
    int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
    AssertRCReturn(vrc, vrc);
    size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
    AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
    char *pszNamePart = &szPath[cbBufLeft]; NOREF(pszNamePart);
    cbBufLeft = sizeof(szPath) - cbBufLeft;

    /* The process started when launching a VM with separate UI/VM processes is always
     * the UI process, i.e. needs special handling as it won't claim the session. */
    bool fSeparate = aFrontend.endsWith("separate", Utf8Str::CaseInsensitive); NOREF(fSeparate);

    aPid = NIL_RTPROCESS;

    RTENV hEnv = RTENV_DEFAULT;
    if (!aEnvironmentChanges.empty())
    {
#ifdef IN_VBOXSVC
        /* VBoxSVC: clone the current environment */
        vrc = RTEnvClone(&hEnv, RTENV_DEFAULT);
#else
        /* VBoxSDS: Create a change record environment since RTProcCreateEx has to
                    build the final environment from the profile of the VBoxSDS caller. */
        aFlags |= RTPROC_FLAGS_ENV_CHANGE_RECORD;
        vrc = RTEnvCreateChangeRecord(&hEnv);
#endif
        AssertRCReturn(vrc, vrc);

        /* Apply the specified environment changes. */
        for (std::vector<com::Utf8Str>::const_iterator itEnv = aEnvironmentChanges.begin();
             itEnv != aEnvironmentChanges.end();
             ++itEnv)
        {
            vrc = RTEnvPutEx(hEnv, itEnv->c_str());
            AssertRCReturnStmt(vrc, RTEnvDestroy(hEnv), vrc);
        }
    }

#ifdef VBOX_WITH_QTGUI
    if (   !aFrontend.compare("gui", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("GUI/Qt", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("separate", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("gui/separate", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("GUI/Qt/separate", Utf8Str::CaseInsensitive))
    {
# ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */

#  define OSX_APP_NAME           "VirtualBoxVM"
#  define OSX_APP_PATH_FMT       "/Resources/%s.app/Contents/MacOS/VirtualBoxVM"
#  define OSX_APP_PATH_WITH_NAME "/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM"

        /* Modify the base path so that we don't need to use ".." below. */
        RTPathStripTrailingSlash(szPath);
        RTPathStripFilename(szPath);
        cbBufLeft = strlen(szPath);
        pszNamePart = &szPath[cbBufLeft]; Assert(!*pszNamePart);
        cbBufLeft = sizeof(szPath) - cbBufLeft;

        if (aFilename.isNotEmpty() && strpbrk(aFilename.c_str(), "./\\:") == NULL)
        {
            ssize_t cch = RTStrPrintf2(pszNamePart, cbBufLeft, OSX_APP_PATH_FMT, aFilename.c_str());
            AssertReturn(cch > 0, VERR_FILENAME_TOO_LONG);
            /* there is a race, but people using this deserve the failure */
            if (!RTFileExists(szPath))
                *pszNamePart = '\0';
        }
        if (!*pszNamePart)
        {
            vrc = RTStrCopy(pszNamePart, cbBufLeft, OSX_APP_PATH_WITH_NAME);
            AssertRCReturn(vrc, vrc);
        }
# else
        static const char s_szVirtualBox_exe[] = "VirtualBoxVM" HOSTSUFF_EXE;
        vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
        AssertRCReturn(vrc, vrc);
# endif

        const char *apszArgs[] =
        {
            szPath,
            "--comment", aComment.c_str(),
            "--startvm", aNameOrId.c_str(),
            "--no-startvm-errormsgbox",
            NULL, /* For "--separate". */
            NULL, /* For "--sup-startup-log". */
            NULL
        };
        unsigned iArg = 6;
        if (fSeparate)
            apszArgs[iArg++] = "--separate";
        if (aExtraArg.isNotEmpty())
            apszArgs[iArg++] = aExtraArg.c_str();

        vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
    }
#else /* !VBOX_WITH_QTGUI */
    if (0)
        ;
#endif /* VBOX_WITH_QTGUI */

    else

#ifdef VBOX_WITH_VBOXSDL
    if (   !aFrontend.compare("sdl", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("GUI/SDL", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("sdl/separate", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("GUI/SDL/separate", Utf8Str::CaseInsensitive))
    {
        static const char s_szVBoxSDL_exe[] = "VBoxSDL" HOSTSUFF_EXE;
        vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxSDL_exe);
        AssertRCReturn(vrc, vrc);

        const char *apszArgs[] =
        {
            szPath,
            "--comment", aComment.c_str(),
            "--startvm", aNameOrId.c_str(),
            NULL, /* For "--separate". */
            NULL, /* For "--sup-startup-log". */
            NULL
        };
        unsigned iArg = 5;
        if (fSeparate)
            apszArgs[iArg++] = "--separate";
        if (aExtraArg.isNotEmpty())
            apszArgs[iArg++] = aExtraArg.c_str();

        vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
    }
#else /* !VBOX_WITH_VBOXSDL */
    if (0)
        ;
#endif /* !VBOX_WITH_VBOXSDL */

    else

#ifdef VBOX_WITH_HEADLESS
    if (   !aFrontend.compare("headless", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("capture", Utf8Str::CaseInsensitive)
        || !aFrontend.compare("vrdp", Utf8Str::CaseInsensitive) /* Deprecated. Same as headless. */
       )
    {
        /* On pre-4.0 the "headless" type was used for passing "--vrdp off" to VBoxHeadless to let it work in OSE,
         * which did not contain VRDP server. In VBox 4.0 the remote desktop server (VRDE) is optional,
         * and a VM works even if the server has not been installed.
         * So in 4.0 the "headless" behavior remains the same for default VBox installations.
         * Only if a VRDE has been installed and the VM enables it, the "headless" will work
         * differently in 4.0 and 3.x.
         */
        static const char s_szVBoxHeadless_exe[] = "VBoxHeadless" HOSTSUFF_EXE;
        vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVBoxHeadless_exe);
        AssertRCReturn(vrc, vrc);

        const char *apszArgs[] =
        {
            szPath,
            "--comment", aComment.c_str(),
            "--startvm", aNameOrId.c_str(),
            "--vrde", "config",
            NULL, /* For "--capture". */
            NULL, /* For "--sup-startup-log". */
            NULL
        };
        unsigned iArg = 7;
        if (!aFrontend.compare("capture", Utf8Str::CaseInsensitive))
            apszArgs[iArg++] = "--capture";
        if (aExtraArg.isNotEmpty())
            apszArgs[iArg++] = aExtraArg.c_str();

# ifdef RT_OS_WINDOWS
        aFlags |= RTPROC_FLAGS_NO_WINDOW;
# endif
        vrc = RTProcCreateEx(szPath, apszArgs, hEnv, aFlags, NULL, NULL, NULL, NULL, NULL, aExtraData, &aPid);
    }
#else /* !VBOX_WITH_HEADLESS */
    if (0)
        ;
#endif /* !VBOX_WITH_HEADLESS */
    else
        vrc = VERR_INVALID_PARAMETER;

    RTEnvDestroy(hEnv);

    if (RT_FAILURE(vrc))
        return vrc;

    return VINF_SUCCESS;
}