[14732] | 1 | /* $Id: VBoxManageGuestProp.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
|
---|
[11031] | 2 | /** @file
|
---|
[32701] | 3 | * VBoxManage - Implementation of guestproperty command.
|
---|
[11031] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2006-2023 Oracle and/or its affiliates.
|
---|
[11031] | 8 | *
|
---|
[96407] | 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
|
---|
[11031] | 26 | */
|
---|
| 27 |
|
---|
| 28 |
|
---|
[57358] | 29 | /*********************************************************************************************************************************
|
---|
| 30 | * Header Files *
|
---|
| 31 | *********************************************************************************************************************************/
|
---|
[14258] | 32 | #include "VBoxManage.h"
|
---|
| 33 |
|
---|
[11031] | 34 | #include <VBox/com/com.h>
|
---|
| 35 | #include <VBox/com/string.h>
|
---|
[11042] | 36 | #include <VBox/com/array.h>
|
---|
[11031] | 37 | #include <VBox/com/ErrorInfo.h>
|
---|
[20928] | 38 | #include <VBox/com/errorprint.h>
|
---|
[11031] | 39 | #include <VBox/com/VirtualBox.h>
|
---|
| 40 |
|
---|
[14258] | 41 | #include <VBox/log.h>
|
---|
[22686] | 42 | #include <iprt/asm.h>
|
---|
[11031] | 43 | #include <iprt/stream.h>
|
---|
[22686] | 44 | #include <iprt/string.h>
|
---|
| 45 | #include <iprt/time.h>
|
---|
[14258] | 46 | #include <iprt/thread.h>
|
---|
[11031] | 47 |
|
---|
[14258] | 48 | #ifdef USE_XPCOM_QUEUE
|
---|
| 49 | # include <sys/select.h>
|
---|
[22686] | 50 | # include <errno.h>
|
---|
[14258] | 51 | #endif
|
---|
[11031] | 52 |
|
---|
[22690] | 53 | #ifdef RT_OS_DARWIN
|
---|
| 54 | # include <CoreFoundation/CFRunLoop.h>
|
---|
| 55 | #endif
|
---|
| 56 |
|
---|
[11031] | 57 | using namespace com;
|
---|
| 58 |
|
---|
[92372] | 59 | DECLARE_TRANSLATION_CONTEXT(GuestProp);
|
---|
| 60 |
|
---|
| 61 |
|
---|
[56118] | 62 | static RTEXITCODE handleGetGuestProperty(HandlerArg *a)
|
---|
[11031] | 63 | {
|
---|
[95140] | 64 | HRESULT hrc = S_OK;
|
---|
[11031] | 65 |
|
---|
[94208] | 66 | setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_GET);
|
---|
| 67 |
|
---|
[11031] | 68 | bool verbose = false;
|
---|
[16724] | 69 | if ( a->argc == 3
|
---|
[18772] | 70 | && ( !strcmp(a->argv[2], "--verbose")
|
---|
| 71 | || !strcmp(a->argv[2], "-verbose")))
|
---|
[11031] | 72 | verbose = true;
|
---|
[16052] | 73 | else if (a->argc != 2)
|
---|
[94208] | 74 | return errorSyntax(GuestProp::tr("Incorrect parameters"));
|
---|
[11031] | 75 |
|
---|
| 76 | ComPtr<IMachine> machine;
|
---|
[33294] | 77 | CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
|
---|
| 78 | machine.asOutParam()));
|
---|
[11031] | 79 | if (machine)
|
---|
| 80 | {
|
---|
[11113] | 81 | /* open a session for the VM - new or existing */
|
---|
[56118] | 82 | CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
|
---|
[11088] | 83 |
|
---|
| 84 | /* get the mutable session machine */
|
---|
[16052] | 85 | a->session->COMGETTER(Machine)(machine.asOutParam());
|
---|
[11088] | 86 |
|
---|
[11031] | 87 | Bstr value;
|
---|
[31698] | 88 | LONG64 i64Timestamp;
|
---|
[11031] | 89 | Bstr flags;
|
---|
[32718] | 90 | CHECK_ERROR(machine, GetGuestProperty(Bstr(a->argv[1]).raw(),
|
---|
| 91 | value.asOutParam(),
|
---|
[31698] | 92 | &i64Timestamp, flags.asOutParam()));
|
---|
[32718] | 93 | if (value.isEmpty())
|
---|
[92372] | 94 | RTPrintf(GuestProp::tr("No value set!\n"));
|
---|
[32718] | 95 | else
|
---|
[92372] | 96 | RTPrintf(GuestProp::tr("Value: %ls\n"), value.raw());
|
---|
[32718] | 97 | if (!value.isEmpty() && verbose)
|
---|
[26587] | 98 | {
|
---|
[92372] | 99 | RTPrintf(GuestProp::tr("Timestamp: %lld\n"), i64Timestamp);
|
---|
| 100 | RTPrintf(GuestProp::tr("Flags: %ls\n"), flags.raw());
|
---|
[11031] | 101 | }
|
---|
| 102 | }
|
---|
[95140] | 103 | return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
[11031] | 104 | }
|
---|
| 105 |
|
---|
[56118] | 106 | static RTEXITCODE handleSetGuestProperty(HandlerArg *a)
|
---|
[11031] | 107 | {
|
---|
[95140] | 108 | HRESULT hrc = S_OK;
|
---|
[11031] | 109 |
|
---|
[94208] | 110 | setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_SET);
|
---|
| 111 |
|
---|
[16724] | 112 | /*
|
---|
| 113 | * Check the syntax. We can deduce the correct syntax from the number of
|
---|
| 114 | * arguments.
|
---|
| 115 | */
|
---|
[11031] | 116 | bool usageOK = true;
|
---|
| 117 | const char *pszName = NULL;
|
---|
| 118 | const char *pszValue = NULL;
|
---|
| 119 | const char *pszFlags = NULL;
|
---|
[16724] | 120 | if (a->argc == 3)
|
---|
[16052] | 121 | pszValue = a->argv[2];
|
---|
[16724] | 122 | else if (a->argc == 4)
|
---|
[13294] | 123 | usageOK = false;
|
---|
[16724] | 124 | else if (a->argc == 5)
|
---|
[11031] | 125 | {
|
---|
[16052] | 126 | pszValue = a->argv[2];
|
---|
[22734] | 127 | if ( strcmp(a->argv[3], "--flags")
|
---|
| 128 | && strcmp(a->argv[3], "-flags"))
|
---|
[11031] | 129 | usageOK = false;
|
---|
[16052] | 130 | pszFlags = a->argv[4];
|
---|
[11031] | 131 | }
|
---|
[16052] | 132 | else if (a->argc != 2)
|
---|
[11031] | 133 | usageOK = false;
|
---|
| 134 | if (!usageOK)
|
---|
[94208] | 135 | return errorSyntax(GuestProp::tr("Incorrect parameters"));
|
---|
[11097] | 136 | /* This is always needed. */
|
---|
[16052] | 137 | pszName = a->argv[1];
|
---|
[11031] | 138 |
|
---|
| 139 | ComPtr<IMachine> machine;
|
---|
[33294] | 140 | CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
|
---|
| 141 | machine.asOutParam()));
|
---|
[11031] | 142 | if (machine)
|
---|
| 143 | {
|
---|
[11113] | 144 | /* open a session for the VM - new or existing */
|
---|
[56118] | 145 | CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
|
---|
[11079] | 146 |
|
---|
| 147 | /* get the mutable session machine */
|
---|
[16052] | 148 | a->session->COMGETTER(Machine)(machine.asOutParam());
|
---|
[11079] | 149 |
|
---|
[44322] | 150 | if (!pszFlags)
|
---|
[32718] | 151 | CHECK_ERROR(machine, SetGuestPropertyValue(Bstr(pszName).raw(),
|
---|
| 152 | Bstr(pszValue).raw()));
|
---|
[11031] | 153 | else
|
---|
[32718] | 154 | CHECK_ERROR(machine, SetGuestProperty(Bstr(pszName).raw(),
|
---|
| 155 | Bstr(pszValue).raw(),
|
---|
| 156 | Bstr(pszFlags).raw()));
|
---|
[11079] | 157 |
|
---|
[95140] | 158 | if (SUCCEEDED(hrc))
|
---|
[11079] | 159 | CHECK_ERROR(machine, SaveSettings());
|
---|
| 160 |
|
---|
[31070] | 161 | a->session->UnlockMachine();
|
---|
[11031] | 162 | }
|
---|
[95140] | 163 | return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
[11031] | 164 | }
|
---|
| 165 |
|
---|
[56118] | 166 | static RTEXITCODE handleDeleteGuestProperty(HandlerArg *a)
|
---|
[44322] | 167 | {
|
---|
[95140] | 168 | HRESULT hrc = S_OK;
|
---|
[44322] | 169 |
|
---|
[94208] | 170 | setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_UNSET);
|
---|
| 171 |
|
---|
[44322] | 172 | /*
|
---|
| 173 | * Check the syntax. We can deduce the correct syntax from the number of
|
---|
| 174 | * arguments.
|
---|
| 175 | */
|
---|
| 176 | bool usageOK = true;
|
---|
| 177 | const char *pszName = NULL;
|
---|
| 178 | if (a->argc != 2)
|
---|
| 179 | usageOK = false;
|
---|
| 180 | if (!usageOK)
|
---|
[94208] | 181 | return errorSyntax(GuestProp::tr("Incorrect parameters"));
|
---|
[44322] | 182 | /* This is always needed. */
|
---|
| 183 | pszName = a->argv[1];
|
---|
| 184 |
|
---|
| 185 | ComPtr<IMachine> machine;
|
---|
| 186 | CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
|
---|
| 187 | machine.asOutParam()));
|
---|
| 188 | if (machine)
|
---|
| 189 | {
|
---|
| 190 | /* open a session for the VM - new or existing */
|
---|
[56118] | 191 | CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
|
---|
[44322] | 192 |
|
---|
| 193 | /* get the mutable session machine */
|
---|
| 194 | a->session->COMGETTER(Machine)(machine.asOutParam());
|
---|
| 195 |
|
---|
| 196 | CHECK_ERROR(machine, DeleteGuestProperty(Bstr(pszName).raw()));
|
---|
| 197 |
|
---|
[95140] | 198 | if (SUCCEEDED(hrc))
|
---|
[44322] | 199 | CHECK_ERROR(machine, SaveSettings());
|
---|
| 200 |
|
---|
| 201 | a->session->UnlockMachine();
|
---|
| 202 | }
|
---|
[95140] | 203 | return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
[44322] | 204 | }
|
---|
| 205 |
|
---|
[11042] | 206 | /**
|
---|
| 207 | * Enumerates the properties in the guest property store.
|
---|
| 208 | *
|
---|
| 209 | * @returns 0 on success, 1 on failure
|
---|
| 210 | * @note see the command line API description for parameters
|
---|
| 211 | */
|
---|
[56118] | 212 | static RTEXITCODE handleEnumGuestProperty(HandlerArg *a)
|
---|
[11042] | 213 | {
|
---|
[94208] | 214 | setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_ENUMERATE);
|
---|
| 215 |
|
---|
[16724] | 216 | /*
|
---|
[96623] | 217 | * Parse arguments.
|
---|
| 218 | *
|
---|
| 219 | * The old syntax was a little boinkers. The --patterns argument just
|
---|
| 220 | * indicates that the rest of the arguments are options. Sort of like '--'.
|
---|
| 221 | * This has been normalized a little now, by accepting patterns w/o a
|
---|
| 222 | * preceding --pattern argument via the VINF_GETOPT_NOT_OPTION.
|
---|
| 223 | * Though, the first non-option is always the VM name.
|
---|
[16724] | 224 | */
|
---|
[96623] | 225 | static const RTGETOPTDEF s_aOptions[] =
|
---|
| 226 | {
|
---|
| 227 | { "--old-format", 'o', RTGETOPT_REQ_NOTHING },
|
---|
| 228 | { "--sort", 's', RTGETOPT_REQ_NOTHING },
|
---|
| 229 | { "--unsort", 'u', RTGETOPT_REQ_NOTHING },
|
---|
| 230 | { "--timestamp", 't', RTGETOPT_REQ_NOTHING },
|
---|
| 231 | { "--ts", 't', RTGETOPT_REQ_NOTHING },
|
---|
| 232 | { "--no-timestamp", 'T', RTGETOPT_REQ_NOTHING },
|
---|
| 233 | { "--abs", 'a', RTGETOPT_REQ_NOTHING },
|
---|
| 234 | { "--absolute", 'a', RTGETOPT_REQ_NOTHING },
|
---|
| 235 | { "--rel", 'r', RTGETOPT_REQ_NOTHING },
|
---|
| 236 | { "--relative", 'r', RTGETOPT_REQ_NOTHING },
|
---|
| 237 | { "--no-ts", 'T', RTGETOPT_REQ_NOTHING },
|
---|
| 238 | { "--flags", 'f', RTGETOPT_REQ_NOTHING },
|
---|
| 239 | { "--no-flags", 'F', RTGETOPT_REQ_NOTHING },
|
---|
| 240 | /* unnecessary legacy: */
|
---|
| 241 | { "--patterns", 'p', RTGETOPT_REQ_STRING },
|
---|
| 242 | { "-patterns", 'p', RTGETOPT_REQ_STRING },
|
---|
| 243 | };
|
---|
[11031] | 244 |
|
---|
[96623] | 245 | int ch;
|
---|
| 246 | RTGETOPTUNION ValueUnion;
|
---|
| 247 | RTGETOPTSTATE GetState;
|
---|
| 248 | RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
|
---|
[11042] | 249 |
|
---|
[96623] | 250 | const char *pszVmNameOrUuid = NULL;
|
---|
| 251 | Utf8Str strPatterns;
|
---|
| 252 | bool fSort = true;
|
---|
| 253 | bool fNewStyle = true;
|
---|
| 254 | bool fTimestamp = true;
|
---|
| 255 | bool fAbsTime = true;
|
---|
| 256 | bool fFlags = true;
|
---|
| 257 |
|
---|
| 258 | while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
|
---|
| 259 | {
|
---|
| 260 | /* For options that require an argument, ValueUnion has received the value. */
|
---|
| 261 | switch (ch)
|
---|
| 262 | {
|
---|
| 263 | case VINF_GETOPT_NOT_OPTION:
|
---|
| 264 | /* The first one is the VM name. */
|
---|
| 265 | if (!pszVmNameOrUuid)
|
---|
| 266 | {
|
---|
| 267 | pszVmNameOrUuid = ValueUnion.psz;
|
---|
| 268 | break;
|
---|
| 269 | }
|
---|
| 270 | /* Everything else would be patterns by the new syntax. */
|
---|
| 271 | RT_FALL_THROUGH();
|
---|
| 272 | case 'p':
|
---|
| 273 | if (strPatterns.isNotEmpty())
|
---|
| 274 | if (RT_FAILURE(strPatterns.appendNoThrow(',')))
|
---|
| 275 | return RTMsgErrorExitFailure("out of memory!");
|
---|
| 276 | if (RT_FAILURE(strPatterns.appendNoThrow(ValueUnion.psz)))
|
---|
| 277 | return RTMsgErrorExitFailure("out of memory!");
|
---|
| 278 | break;
|
---|
| 279 |
|
---|
| 280 | case 'o':
|
---|
| 281 | fNewStyle = false;
|
---|
| 282 | break;
|
---|
| 283 |
|
---|
| 284 | case 's':
|
---|
| 285 | fSort = true;
|
---|
| 286 | break;
|
---|
| 287 | case 'u':
|
---|
| 288 | fSort = false;
|
---|
| 289 | break;
|
---|
| 290 |
|
---|
| 291 | case 't':
|
---|
| 292 | fTimestamp = true;
|
---|
| 293 | break;
|
---|
| 294 | case 'T':
|
---|
| 295 | fTimestamp = false;
|
---|
| 296 | break;
|
---|
| 297 |
|
---|
| 298 | case 'a':
|
---|
| 299 | fAbsTime = true;
|
---|
| 300 | break;
|
---|
| 301 | case 'r':
|
---|
| 302 | fAbsTime = false;
|
---|
| 303 | break;
|
---|
| 304 |
|
---|
| 305 | case 'f':
|
---|
| 306 | fFlags = true;
|
---|
| 307 | break;
|
---|
| 308 | case 'F':
|
---|
| 309 | fFlags = false;
|
---|
| 310 | break;
|
---|
| 311 |
|
---|
| 312 | default:
|
---|
| 313 | return errorGetOpt(ch, &ValueUnion);
|
---|
| 314 | }
|
---|
| 315 | }
|
---|
| 316 |
|
---|
| 317 | /* Only the VM name is required. */
|
---|
| 318 | if (!pszVmNameOrUuid)
|
---|
| 319 | return errorSyntax(GuestProp::tr("No VM name or UUID was specified"));
|
---|
| 320 |
|
---|
[16724] | 321 | /*
|
---|
| 322 | * Make the actual call to Main.
|
---|
| 323 | */
|
---|
[11042] | 324 | ComPtr<IMachine> machine;
|
---|
[96623] | 325 | CHECK_ERROR2I_RET(a->virtualBox, FindMachine(Bstr(pszVmNameOrUuid).raw(), machine.asOutParam()), RTEXITCODE_FAILURE);
|
---|
| 326 |
|
---|
| 327 | /* open a session for the VM - new or existing */
|
---|
| 328 | CHECK_ERROR2I_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
|
---|
| 329 |
|
---|
| 330 | /* get the mutable session machine */
|
---|
| 331 | a->session->COMGETTER(Machine)(machine.asOutParam());
|
---|
| 332 |
|
---|
| 333 | com::SafeArray<BSTR> names;
|
---|
| 334 | com::SafeArray<BSTR> values;
|
---|
| 335 | com::SafeArray<LONG64> timestamps;
|
---|
| 336 | com::SafeArray<BSTR> flags;
|
---|
| 337 | CHECK_ERROR2I_RET(machine, EnumerateGuestProperties(Bstr(strPatterns).raw(),
|
---|
| 338 | ComSafeArrayAsOutParam(names),
|
---|
| 339 | ComSafeArrayAsOutParam(values),
|
---|
| 340 | ComSafeArrayAsOutParam(timestamps),
|
---|
| 341 | ComSafeArrayAsOutParam(flags)),
|
---|
| 342 | RTEXITCODE_FAILURE);
|
---|
| 343 |
|
---|
| 344 | size_t const cEntries = names.size();
|
---|
| 345 | if (cEntries == 0)
|
---|
| 346 | RTPrintf(GuestProp::tr("No properties found.\n"));
|
---|
| 347 | else
|
---|
[11042] | 348 | {
|
---|
[96623] | 349 | /* Whether we sort it or not, we work it via a indirect index: */
|
---|
| 350 | size_t *paidxSorted = (size_t *)RTMemAlloc(sizeof(paidxSorted[0]) * cEntries);
|
---|
| 351 | if (!paidxSorted)
|
---|
| 352 | return RTMsgErrorExitFailure("out of memory!");
|
---|
| 353 | for (size_t i = 0; i < cEntries; i++)
|
---|
| 354 | paidxSorted[i] = i;
|
---|
[11088] | 355 |
|
---|
[96623] | 356 | /* Do the sorting: */
|
---|
| 357 | if (fSort && cEntries > 1)
|
---|
| 358 | for (size_t i = 0; i < cEntries - 1; i++)
|
---|
| 359 | for (size_t j = 0; j < cEntries - i - 1; j++)
|
---|
| 360 | if (RTUtf16Cmp(names[paidxSorted[j]], names[paidxSorted[j + 1]]) > 0)
|
---|
| 361 | {
|
---|
| 362 | size_t iTmp = paidxSorted[j];
|
---|
| 363 | paidxSorted[j] = paidxSorted[j + 1];
|
---|
| 364 | paidxSorted[j + 1] = iTmp;
|
---|
| 365 | }
|
---|
[11088] | 366 |
|
---|
[96623] | 367 | if (fNewStyle)
|
---|
[11042] | 368 | {
|
---|
[96623] | 369 | /* figure the width of the main columns: */
|
---|
| 370 | size_t cwcMaxName = 1;
|
---|
| 371 | size_t cwcMaxValue = 1;
|
---|
| 372 | for (size_t i = 0; i < cEntries; ++i)
|
---|
| 373 | {
|
---|
| 374 | size_t cwcName = RTUtf16Len(names[i]);
|
---|
| 375 | cwcMaxName = RT_MAX(cwcMaxName, cwcName);
|
---|
| 376 | size_t cwcValue = RTUtf16Len(values[i]);
|
---|
| 377 | cwcMaxValue = RT_MAX(cwcMaxValue, cwcValue);
|
---|
| 378 | }
|
---|
| 379 | cwcMaxName = RT_MIN(cwcMaxName, 48);
|
---|
| 380 | cwcMaxValue = RT_MIN(cwcMaxValue, 28);
|
---|
| 381 |
|
---|
| 382 | /* Get the current time for relative time formatting: */
|
---|
| 383 | RTTIMESPEC Now;
|
---|
| 384 | RTTimeNow(&Now);
|
---|
| 385 |
|
---|
| 386 | /* Print the table: */
|
---|
| 387 | for (size_t iSorted = 0; iSorted < cEntries; ++iSorted)
|
---|
| 388 | {
|
---|
| 389 | size_t const i = paidxSorted[iSorted];
|
---|
| 390 | char szTime[80];
|
---|
| 391 | if (fTimestamp)
|
---|
| 392 | {
|
---|
| 393 | RTTIMESPEC TimestampTS;
|
---|
| 394 | RTTimeSpecSetNano(&TimestampTS, timestamps[i]);
|
---|
| 395 | if (fAbsTime)
|
---|
| 396 | {
|
---|
| 397 | RTTIME Timestamp;
|
---|
| 398 | RTTimeToStringEx(RTTimeExplode(&Timestamp, &TimestampTS), &szTime[2], sizeof(szTime) - 2, 3);
|
---|
| 399 | }
|
---|
| 400 | else
|
---|
| 401 | {
|
---|
| 402 | RTTIMESPEC DurationTS = Now;
|
---|
| 403 | RTTimeFormatDurationEx(&szTime[2], sizeof(szTime) - 2, RTTimeSpecSub(&DurationTS, &TimestampTS), 3);
|
---|
| 404 | }
|
---|
| 405 | szTime[0] = '@';
|
---|
| 406 | szTime[1] = ' ';
|
---|
| 407 | }
|
---|
| 408 | else
|
---|
| 409 | szTime[0] = '\0';
|
---|
| 410 |
|
---|
| 411 | static RTUTF16 s_wszEmpty[] = { 0 };
|
---|
| 412 | PCRTUTF16 const pwszFlags = fFlags ? flags[i] : s_wszEmpty;
|
---|
| 413 |
|
---|
| 414 | int cchOut = RTPrintf("%-*ls = '%ls'", cwcMaxName, names[i], values[i]);
|
---|
| 415 | if (fTimestamp || *pwszFlags)
|
---|
| 416 | {
|
---|
| 417 | size_t const cwcWidth = cwcMaxName + cwcMaxValue + 6;
|
---|
| 418 | size_t const cwcValPadding = (unsigned)cchOut < cwcWidth ? cwcWidth - (unsigned)cchOut : 1;
|
---|
| 419 | RTPrintf("%*s%s%s%ls\n", cwcValPadding, "", szTime, *pwszFlags ? " " : "", pwszFlags);
|
---|
| 420 | }
|
---|
| 421 | else
|
---|
| 422 | RTPrintf("\n");
|
---|
| 423 | }
|
---|
| 424 | }
|
---|
| 425 | else
|
---|
| 426 | for (size_t iSorted = 0; iSorted < cEntries; ++iSorted)
|
---|
| 427 | {
|
---|
| 428 | size_t const i = paidxSorted[iSorted];
|
---|
[92372] | 429 | RTPrintf(GuestProp::tr("Name: %ls, value: %ls, timestamp: %lld, flags: %ls\n"),
|
---|
[11042] | 430 | names[i], values[i], timestamps[i], flags[i]);
|
---|
[96623] | 431 | }
|
---|
| 432 | RTMemFree(paidxSorted);
|
---|
[11042] | 433 | }
|
---|
[96623] | 434 |
|
---|
| 435 | return RTEXITCODE_SUCCESS;
|
---|
[11042] | 436 | }
|
---|
| 437 |
|
---|
[11031] | 438 | /**
|
---|
[14258] | 439 | * Enumerates the properties in the guest property store.
|
---|
| 440 | *
|
---|
| 441 | * @returns 0 on success, 1 on failure
|
---|
| 442 | * @note see the command line API description for parameters
|
---|
| 443 | */
|
---|
[56118] | 444 | static RTEXITCODE handleWaitGuestProperty(HandlerArg *a)
|
---|
[14258] | 445 | {
|
---|
[94208] | 446 | setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_WAIT);
|
---|
| 447 |
|
---|
[16724] | 448 | /*
|
---|
| 449 | * Handle arguments
|
---|
| 450 | */
|
---|
[22686] | 451 | bool fFailOnTimeout = false;
|
---|
| 452 | const char *pszPatterns = NULL;
|
---|
| 453 | uint32_t cMsTimeout = RT_INDEFINITE_WAIT;
|
---|
| 454 | bool usageOK = true;
|
---|
[16052] | 455 | if (a->argc < 2)
|
---|
[14258] | 456 | usageOK = false;
|
---|
| 457 | else
|
---|
[16052] | 458 | pszPatterns = a->argv[1];
|
---|
[14258] | 459 | ComPtr<IMachine> machine;
|
---|
[95140] | 460 | HRESULT hrc;
|
---|
[33294] | 461 | CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
|
---|
| 462 | machine.asOutParam()));
|
---|
[14258] | 463 | if (!machine)
|
---|
| 464 | usageOK = false;
|
---|
[16052] | 465 | for (int i = 2; usageOK && i < a->argc; ++i)
|
---|
[14258] | 466 | {
|
---|
[18772] | 467 | if ( !strcmp(a->argv[i], "--timeout")
|
---|
| 468 | || !strcmp(a->argv[i], "-timeout"))
|
---|
[14258] | 469 | {
|
---|
[16052] | 470 | if ( i + 1 >= a->argc
|
---|
[22686] | 471 | || RTStrToUInt32Full(a->argv[i + 1], 10, &cMsTimeout) != VINF_SUCCESS)
|
---|
[14258] | 472 | usageOK = false;
|
---|
| 473 | else
|
---|
| 474 | ++i;
|
---|
| 475 | }
|
---|
[22686] | 476 | else if (!strcmp(a->argv[i], "--fail-on-timeout"))
|
---|
| 477 | fFailOnTimeout = true;
|
---|
[14258] | 478 | else
|
---|
| 479 | usageOK = false;
|
---|
| 480 | }
|
---|
| 481 | if (!usageOK)
|
---|
[94208] | 482 | return errorSyntax(GuestProp::tr("Incorrect parameters"));
|
---|
[14258] | 483 |
|
---|
[16724] | 484 | /*
|
---|
[30599] | 485 | * Set up the event listener and wait until found match or timeout.
|
---|
[16724] | 486 | */
|
---|
[30599] | 487 | Bstr aMachStrGuid;
|
---|
| 488 | machine->COMGETTER(Id)(aMachStrGuid.asOutParam());
|
---|
| 489 | Guid aMachGuid(aMachStrGuid);
|
---|
| 490 | ComPtr<IEventSource> es;
|
---|
| 491 | CHECK_ERROR(a->virtualBox, COMGETTER(EventSource)(es.asOutParam()));
|
---|
| 492 | ComPtr<IEventListener> listener;
|
---|
| 493 | CHECK_ERROR(es, CreateListener(listener.asOutParam()));
|
---|
| 494 | com::SafeArray <VBoxEventType_T> eventTypes(1);
|
---|
[30871] | 495 | eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
|
---|
[30599] | 496 | CHECK_ERROR(es, RegisterListener(listener, ComSafeArrayAsInParam(eventTypes), false));
|
---|
| 497 |
|
---|
[22914] | 498 | uint64_t u64Started = RTTimeMilliTS();
|
---|
[30599] | 499 | bool fSignalled = false;
|
---|
[23083] | 500 | do
|
---|
| 501 | {
|
---|
| 502 | unsigned cMsWait;
|
---|
| 503 | if (cMsTimeout == RT_INDEFINITE_WAIT)
|
---|
| 504 | cMsWait = 1000;
|
---|
| 505 | else
|
---|
| 506 | {
|
---|
| 507 | uint64_t cMsElapsed = RTTimeMilliTS() - u64Started;
|
---|
| 508 | if (cMsElapsed >= cMsTimeout)
|
---|
| 509 | break; /* timed out */
|
---|
[23091] | 510 | cMsWait = RT_MIN(1000, cMsTimeout - (uint32_t)cMsElapsed);
|
---|
[23083] | 511 | }
|
---|
[30599] | 512 |
|
---|
| 513 | ComPtr<IEvent> ev;
|
---|
[95140] | 514 | hrc = es->GetEvent(listener, cMsWait, ev.asOutParam());
|
---|
| 515 | if (ev) /** @todo r=andy Why not using SUCCEEDED(hrc) here? */
|
---|
[23083] | 516 | {
|
---|
[30608] | 517 | VBoxEventType_T aType;
|
---|
[95140] | 518 | hrc = ev->COMGETTER(Type)(&aType);
|
---|
[30599] | 519 | switch (aType)
|
---|
| 520 | {
|
---|
[30871] | 521 | case VBoxEventType_OnGuestPropertyChanged:
|
---|
[30599] | 522 | {
|
---|
[30825] | 523 | ComPtr<IGuestPropertyChangedEvent> gpcev = ev;
|
---|
[30606] | 524 | Assert(gpcev);
|
---|
[30599] | 525 | Bstr aNextStrGuid;
|
---|
| 526 | gpcev->COMGETTER(MachineId)(aNextStrGuid.asOutParam());
|
---|
[30608] | 527 | if (aMachGuid != Guid(aNextStrGuid))
|
---|
[30599] | 528 | continue;
|
---|
| 529 | Bstr aNextName;
|
---|
| 530 | gpcev->COMGETTER(Name)(aNextName.asOutParam());
|
---|
| 531 | if (RTStrSimplePatternMultiMatch(pszPatterns, RTSTR_MAX,
|
---|
[31539] | 532 | Utf8Str(aNextName).c_str(), RTSTR_MAX, NULL))
|
---|
[30599] | 533 | {
|
---|
| 534 | Bstr aNextValue, aNextFlags;
|
---|
[94184] | 535 | BOOL aNextWasDeleted;
|
---|
[30599] | 536 | gpcev->COMGETTER(Value)(aNextValue.asOutParam());
|
---|
| 537 | gpcev->COMGETTER(Flags)(aNextFlags.asOutParam());
|
---|
[94184] | 538 | gpcev->COMGETTER(FWasDeleted)(&aNextWasDeleted);
|
---|
| 539 | if (aNextWasDeleted)
|
---|
| 540 | RTPrintf(GuestProp::tr("Property %ls was deleted\n"), aNextName.raw());
|
---|
| 541 | else
|
---|
| 542 | RTPrintf(GuestProp::tr("Name: %ls, value: %ls, flags: %ls\n"),
|
---|
| 543 | aNextName.raw(), aNextValue.raw(), aNextFlags.raw());
|
---|
[30599] | 544 | fSignalled = true;
|
---|
| 545 | }
|
---|
[30600] | 546 | break;
|
---|
[30599] | 547 | }
|
---|
| 548 | default:
|
---|
| 549 | AssertFailed();
|
---|
| 550 | }
|
---|
[23083] | 551 | }
|
---|
[30599] | 552 | } while (!fSignalled);
|
---|
[22686] | 553 |
|
---|
[30599] | 554 | es->UnregisterListener(listener);
|
---|
[22686] | 555 |
|
---|
[56118] | 556 | RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
|
---|
[30599] | 557 | if (!fSignalled)
|
---|
[22686] | 558 | {
|
---|
[92372] | 559 | RTMsgError(GuestProp::tr("Time out or interruption while waiting for a notification."));
|
---|
[22686] | 560 | if (fFailOnTimeout)
|
---|
[56118] | 561 | /* Hysterical rasins: We always returned 2 here, which now translates to syntax error... Which is bad. */
|
---|
| 562 | rcExit = RTEXITCODE_SYNTAX;
|
---|
[22686] | 563 | }
|
---|
[56118] | 564 | return rcExit;
|
---|
[14258] | 565 | }
|
---|
| 566 |
|
---|
| 567 | /**
|
---|
[11031] | 568 | * Access the guest property store.
|
---|
| 569 | *
|
---|
| 570 | * @returns 0 on success, 1 on failure
|
---|
| 571 | * @note see the command line API description for parameters
|
---|
| 572 | */
|
---|
[56118] | 573 | RTEXITCODE handleGuestProperty(HandlerArg *a)
|
---|
[11031] | 574 | {
|
---|
[96623] | 575 | if (a->argc == 0)
|
---|
| 576 | return errorNoSubcommand();
|
---|
[16052] | 577 |
|
---|
[35951] | 578 | /** @todo This command does not follow the syntax where the <uuid|vmname>
|
---|
| 579 | * comes between the command and subcommand. The commands controlvm,
|
---|
| 580 | * snapshot and debugvm puts it between.
|
---|
| 581 | */
|
---|
| 582 |
|
---|
[96623] | 583 | const char * const pszSubCmd = a->argv[0];
|
---|
| 584 | a->argc -= 1;
|
---|
| 585 | a->argv += 1;
|
---|
[16724] | 586 |
|
---|
| 587 | /* switch (cmd) */
|
---|
[96623] | 588 | if (strcmp(pszSubCmd, "get") == 0)
|
---|
| 589 | return handleGetGuestProperty(a);
|
---|
| 590 | if (strcmp(pszSubCmd, "set") == 0)
|
---|
| 591 | return handleSetGuestProperty(a);
|
---|
| 592 | if (strcmp(pszSubCmd, "delete") == 0 || strcmp(pszSubCmd, "unset") == 0)
|
---|
| 593 | return handleDeleteGuestProperty(a);
|
---|
| 594 | if (strcmp(pszSubCmd, "enumerate") == 0 || strcmp(pszSubCmd, "enum") == 0)
|
---|
| 595 | return handleEnumGuestProperty(a);
|
---|
| 596 | if (strcmp(pszSubCmd, "wait") == 0)
|
---|
| 597 | return handleWaitGuestProperty(a);
|
---|
[16724] | 598 |
|
---|
| 599 | /* default: */
|
---|
[96623] | 600 | return errorUnknownSubcommand(pszSubCmd);
|
---|
[11031] | 601 | }
|
---|