VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageGuestProp.cpp@ 103068

Last change on this file since 103068 was 98103, checked in by vboxsync, 18 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 20.9 KB
Line 
1/* $Id: VBoxManageGuestProp.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBoxManage - Implementation of guestproperty command.
4 */
5
6/*
7 * Copyright (C) 2006-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 "VBoxManage.h"
33
34#include <VBox/com/com.h>
35#include <VBox/com/string.h>
36#include <VBox/com/array.h>
37#include <VBox/com/ErrorInfo.h>
38#include <VBox/com/errorprint.h>
39#include <VBox/com/VirtualBox.h>
40
41#include <VBox/log.h>
42#include <iprt/asm.h>
43#include <iprt/stream.h>
44#include <iprt/string.h>
45#include <iprt/time.h>
46#include <iprt/thread.h>
47
48#ifdef USE_XPCOM_QUEUE
49# include <sys/select.h>
50# include <errno.h>
51#endif
52
53#ifdef RT_OS_DARWIN
54# include <CoreFoundation/CFRunLoop.h>
55#endif
56
57using namespace com;
58
59DECLARE_TRANSLATION_CONTEXT(GuestProp);
60
61
62static RTEXITCODE handleGetGuestProperty(HandlerArg *a)
63{
64 HRESULT hrc = S_OK;
65
66 setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_GET);
67
68 bool verbose = false;
69 if ( a->argc == 3
70 && ( !strcmp(a->argv[2], "--verbose")
71 || !strcmp(a->argv[2], "-verbose")))
72 verbose = true;
73 else if (a->argc != 2)
74 return errorSyntax(GuestProp::tr("Incorrect parameters"));
75
76 ComPtr<IMachine> machine;
77 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
78 machine.asOutParam()));
79 if (machine)
80 {
81 /* open a session for the VM - new or existing */
82 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
83
84 /* get the mutable session machine */
85 a->session->COMGETTER(Machine)(machine.asOutParam());
86
87 Bstr value;
88 LONG64 i64Timestamp;
89 Bstr flags;
90 CHECK_ERROR(machine, GetGuestProperty(Bstr(a->argv[1]).raw(),
91 value.asOutParam(),
92 &i64Timestamp, flags.asOutParam()));
93 if (value.isEmpty())
94 RTPrintf(GuestProp::tr("No value set!\n"));
95 else
96 RTPrintf(GuestProp::tr("Value: %ls\n"), value.raw());
97 if (!value.isEmpty() && verbose)
98 {
99 RTPrintf(GuestProp::tr("Timestamp: %lld\n"), i64Timestamp);
100 RTPrintf(GuestProp::tr("Flags: %ls\n"), flags.raw());
101 }
102 }
103 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
104}
105
106static RTEXITCODE handleSetGuestProperty(HandlerArg *a)
107{
108 HRESULT hrc = S_OK;
109
110 setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_SET);
111
112 /*
113 * Check the syntax. We can deduce the correct syntax from the number of
114 * arguments.
115 */
116 bool usageOK = true;
117 const char *pszName = NULL;
118 const char *pszValue = NULL;
119 const char *pszFlags = NULL;
120 if (a->argc == 3)
121 pszValue = a->argv[2];
122 else if (a->argc == 4)
123 usageOK = false;
124 else if (a->argc == 5)
125 {
126 pszValue = a->argv[2];
127 if ( strcmp(a->argv[3], "--flags")
128 && strcmp(a->argv[3], "-flags"))
129 usageOK = false;
130 pszFlags = a->argv[4];
131 }
132 else if (a->argc != 2)
133 usageOK = false;
134 if (!usageOK)
135 return errorSyntax(GuestProp::tr("Incorrect parameters"));
136 /* This is always needed. */
137 pszName = a->argv[1];
138
139 ComPtr<IMachine> machine;
140 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
141 machine.asOutParam()));
142 if (machine)
143 {
144 /* open a session for the VM - new or existing */
145 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
146
147 /* get the mutable session machine */
148 a->session->COMGETTER(Machine)(machine.asOutParam());
149
150 if (!pszFlags)
151 CHECK_ERROR(machine, SetGuestPropertyValue(Bstr(pszName).raw(),
152 Bstr(pszValue).raw()));
153 else
154 CHECK_ERROR(machine, SetGuestProperty(Bstr(pszName).raw(),
155 Bstr(pszValue).raw(),
156 Bstr(pszFlags).raw()));
157
158 if (SUCCEEDED(hrc))
159 CHECK_ERROR(machine, SaveSettings());
160
161 a->session->UnlockMachine();
162 }
163 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
164}
165
166static RTEXITCODE handleDeleteGuestProperty(HandlerArg *a)
167{
168 HRESULT hrc = S_OK;
169
170 setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_UNSET);
171
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)
181 return errorSyntax(GuestProp::tr("Incorrect parameters"));
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 */
191 CHECK_ERROR_RET(machine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE);
192
193 /* get the mutable session machine */
194 a->session->COMGETTER(Machine)(machine.asOutParam());
195
196 CHECK_ERROR(machine, DeleteGuestProperty(Bstr(pszName).raw()));
197
198 if (SUCCEEDED(hrc))
199 CHECK_ERROR(machine, SaveSettings());
200
201 a->session->UnlockMachine();
202 }
203 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
204}
205
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 */
212static RTEXITCODE handleEnumGuestProperty(HandlerArg *a)
213{
214 setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_ENUMERATE);
215
216 /*
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.
224 */
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 };
244
245 int ch;
246 RTGETOPTUNION ValueUnion;
247 RTGETOPTSTATE GetState;
248 RTGetOptInit(&GetState, a->argc, a->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
249
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
321 /*
322 * Make the actual call to Main.
323 */
324 ComPtr<IMachine> machine;
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
348 {
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;
355
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 }
366
367 if (fNewStyle)
368 {
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];
429 RTPrintf(GuestProp::tr("Name: %ls, value: %ls, timestamp: %lld, flags: %ls\n"),
430 names[i], values[i], timestamps[i], flags[i]);
431 }
432 RTMemFree(paidxSorted);
433 }
434
435 return RTEXITCODE_SUCCESS;
436}
437
438/**
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 */
444static RTEXITCODE handleWaitGuestProperty(HandlerArg *a)
445{
446 setCurrentSubcommand(HELP_SCOPE_GUESTPROPERTY_WAIT);
447
448 /*
449 * Handle arguments
450 */
451 bool fFailOnTimeout = false;
452 const char *pszPatterns = NULL;
453 uint32_t cMsTimeout = RT_INDEFINITE_WAIT;
454 bool usageOK = true;
455 if (a->argc < 2)
456 usageOK = false;
457 else
458 pszPatterns = a->argv[1];
459 ComPtr<IMachine> machine;
460 HRESULT hrc;
461 CHECK_ERROR(a->virtualBox, FindMachine(Bstr(a->argv[0]).raw(),
462 machine.asOutParam()));
463 if (!machine)
464 usageOK = false;
465 for (int i = 2; usageOK && i < a->argc; ++i)
466 {
467 if ( !strcmp(a->argv[i], "--timeout")
468 || !strcmp(a->argv[i], "-timeout"))
469 {
470 if ( i + 1 >= a->argc
471 || RTStrToUInt32Full(a->argv[i + 1], 10, &cMsTimeout) != VINF_SUCCESS)
472 usageOK = false;
473 else
474 ++i;
475 }
476 else if (!strcmp(a->argv[i], "--fail-on-timeout"))
477 fFailOnTimeout = true;
478 else
479 usageOK = false;
480 }
481 if (!usageOK)
482 return errorSyntax(GuestProp::tr("Incorrect parameters"));
483
484 /*
485 * Set up the event listener and wait until found match or timeout.
486 */
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);
495 eventTypes.push_back(VBoxEventType_OnGuestPropertyChanged);
496 CHECK_ERROR(es, RegisterListener(listener, ComSafeArrayAsInParam(eventTypes), false));
497
498 uint64_t u64Started = RTTimeMilliTS();
499 bool fSignalled = false;
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 */
510 cMsWait = RT_MIN(1000, cMsTimeout - (uint32_t)cMsElapsed);
511 }
512
513 ComPtr<IEvent> ev;
514 hrc = es->GetEvent(listener, cMsWait, ev.asOutParam());
515 if (ev) /** @todo r=andy Why not using SUCCEEDED(hrc) here? */
516 {
517 VBoxEventType_T aType;
518 hrc = ev->COMGETTER(Type)(&aType);
519 switch (aType)
520 {
521 case VBoxEventType_OnGuestPropertyChanged:
522 {
523 ComPtr<IGuestPropertyChangedEvent> gpcev = ev;
524 Assert(gpcev);
525 Bstr aNextStrGuid;
526 gpcev->COMGETTER(MachineId)(aNextStrGuid.asOutParam());
527 if (aMachGuid != Guid(aNextStrGuid))
528 continue;
529 Bstr aNextName;
530 gpcev->COMGETTER(Name)(aNextName.asOutParam());
531 if (RTStrSimplePatternMultiMatch(pszPatterns, RTSTR_MAX,
532 Utf8Str(aNextName).c_str(), RTSTR_MAX, NULL))
533 {
534 Bstr aNextValue, aNextFlags;
535 BOOL aNextWasDeleted;
536 gpcev->COMGETTER(Value)(aNextValue.asOutParam());
537 gpcev->COMGETTER(Flags)(aNextFlags.asOutParam());
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());
544 fSignalled = true;
545 }
546 break;
547 }
548 default:
549 AssertFailed();
550 }
551 }
552 } while (!fSignalled);
553
554 es->UnregisterListener(listener);
555
556 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
557 if (!fSignalled)
558 {
559 RTMsgError(GuestProp::tr("Time out or interruption while waiting for a notification."));
560 if (fFailOnTimeout)
561 /* Hysterical rasins: We always returned 2 here, which now translates to syntax error... Which is bad. */
562 rcExit = RTEXITCODE_SYNTAX;
563 }
564 return rcExit;
565}
566
567/**
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 */
573RTEXITCODE handleGuestProperty(HandlerArg *a)
574{
575 if (a->argc == 0)
576 return errorNoSubcommand();
577
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
583 const char * const pszSubCmd = a->argv[0];
584 a->argc -= 1;
585 a->argv += 1;
586
587 /* switch (cmd) */
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);
598
599 /* default: */
600 return errorUnknownSubcommand(pszSubCmd);
601}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use