VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp@ 56103

Last change on this file since 56103 was 55977, checked in by vboxsync, 10 years ago

Main/Snapshot: add a parameter to IMachine.takeSnapshot, returning the snapshot UUID, which is useful for finding the snapshot reliably (using the non-unique name is error prone), plus the necessary code adaptions everywhere.
Frontends/VBoxManage: add a feature for creating unique snapshot names (by appending a number or timestamp).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.8 KB
Line 
1/* $Id: VBoxManageSnapshot.cpp 55977 2015-05-20 16:52:25Z vboxsync $ */
2/** @file
3 * VBoxManage - The 'snapshot' command.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#include <VBox/com/com.h>
22#include <VBox/com/string.h>
23#include <VBox/com/array.h>
24#include <VBox/com/ErrorInfo.h>
25#include <VBox/com/errorprint.h>
26
27#include <VBox/com/VirtualBox.h>
28
29#include <iprt/stream.h>
30#include <iprt/getopt.h>
31#include <iprt/time.h>
32
33#include "VBoxManage.h"
34using namespace com;
35
36/**
37 * Helper function used with "VBoxManage snapshot ... dump". Gets called to find the
38 * snapshot in the machine's snapshot tree that uses a particular diff image child of
39 * a medium.
40 * Horribly inefficient since we keep re-querying the snapshots tree for each image,
41 * but this is for quick debugging only.
42 * @param pMedium
43 * @param pThisSnapshot
44 * @param pCurrentSnapshot
45 * @param uMediumLevel
46 * @param uSnapshotLevel
47 * @return
48 */
49bool FindAndPrintSnapshotUsingMedium(ComPtr<IMedium> &pMedium,
50 ComPtr<ISnapshot> &pThisSnapshot,
51 ComPtr<ISnapshot> &pCurrentSnapshot,
52 uint32_t uMediumLevel,
53 uint32_t uSnapshotLevel)
54{
55 HRESULT rc;
56
57 do
58 {
59 // get snapshot machine so we can figure out which diff image this created
60 ComPtr<IMachine> pSnapshotMachine;
61 CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Machine)(pSnapshotMachine.asOutParam()));
62
63 // get media attachments
64 SafeIfaceArray<IMediumAttachment> aAttachments;
65 CHECK_ERROR_BREAK(pSnapshotMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments)));
66
67 for (uint32_t i = 0;
68 i < aAttachments.size();
69 ++i)
70 {
71 ComPtr<IMediumAttachment> pAttach(aAttachments[i]);
72 DeviceType_T type;
73 CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type));
74 if (type == DeviceType_HardDisk)
75 {
76 ComPtr<IMedium> pMediumInSnapshot;
77 CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pMediumInSnapshot.asOutParam()));
78
79 if (pMediumInSnapshot == pMedium)
80 {
81 // get snapshot name
82 Bstr bstrSnapshotName;
83 CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Name)(bstrSnapshotName.asOutParam()));
84
85 RTPrintf("%*s \"%ls\"%s\n",
86 50 + uSnapshotLevel * 2, "", // indent
87 bstrSnapshotName.raw(),
88 (pThisSnapshot == pCurrentSnapshot) ? " (CURSNAP)" : "");
89 return true; // found
90 }
91 }
92 }
93
94 // not found: then recurse into child snapshots
95 SafeIfaceArray<ISnapshot> aSnapshots;
96 CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Children)(ComSafeArrayAsOutParam(aSnapshots)));
97
98 for (uint32_t i = 0;
99 i < aSnapshots.size();
100 ++i)
101 {
102 ComPtr<ISnapshot> pChild(aSnapshots[i]);
103 if (FindAndPrintSnapshotUsingMedium(pMedium,
104 pChild,
105 pCurrentSnapshot,
106 uMediumLevel,
107 uSnapshotLevel + 1))
108 // found:
109 break;
110 }
111 } while (0);
112
113 return false;
114}
115
116/**
117 * Helper function used with "VBoxManage snapshot ... dump". Called from DumpSnapshot()
118 * for each hard disk attachment found in a virtual machine. This then writes out the
119 * root (base) medium for that hard disk attachment and recurses into the children
120 * tree of that medium, correlating it with the snapshots of the machine.
121 * @param pCurrentStateMedium constant, the medium listed in the current machine data (latest diff image).
122 * @param pMedium variant, initially the base medium, then a child of the base medium when recursing.
123 * @param pRootSnapshot constant, the root snapshot of the machine, if any; this then looks into the child snapshots.
124 * @param pCurrentSnapshot constant, the machine's current snapshot (so we can mark it in the output).
125 * @param uLevel variant, the recursion level for output indentation.
126 */
127void DumpMediumWithChildren(ComPtr<IMedium> &pCurrentStateMedium,
128 ComPtr<IMedium> &pMedium,
129 ComPtr<ISnapshot> &pRootSnapshot,
130 ComPtr<ISnapshot> &pCurrentSnapshot,
131 uint32_t uLevel)
132{
133 HRESULT rc;
134 do
135 {
136 // print this medium
137 Bstr bstrMediumName;
138 CHECK_ERROR_BREAK(pMedium, COMGETTER(Name)(bstrMediumName.asOutParam()));
139 RTPrintf("%*s \"%ls\"%s\n",
140 uLevel * 2, "", // indent
141 bstrMediumName.raw(),
142 (pCurrentStateMedium == pMedium) ? " (CURSTATE)" : "");
143
144 // find and print the snapshot that uses this particular medium (diff image)
145 FindAndPrintSnapshotUsingMedium(pMedium, pRootSnapshot, pCurrentSnapshot, uLevel, 0);
146
147 // recurse into children
148 SafeIfaceArray<IMedium> aChildren;
149 CHECK_ERROR_BREAK(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(aChildren)));
150 for (uint32_t i = 0;
151 i < aChildren.size();
152 ++i)
153 {
154 ComPtr<IMedium> pChild(aChildren[i]);
155 DumpMediumWithChildren(pCurrentStateMedium, pChild, pRootSnapshot, pCurrentSnapshot, uLevel + 1);
156 }
157 } while (0);
158}
159
160
161/**
162 * Handles the 'snapshot myvm list' sub-command.
163 * @returns Exit code.
164 * @param pArgs The handler argument package.
165 * @param pMachine Reference to the VM (locked) we're operating on.
166 */
167static RTEXITCODE handleSnapshotList(HandlerArg *pArgs, ComPtr<IMachine> &pMachine)
168{
169 static const RTGETOPTDEF g_aOptions[] =
170 {
171 { "--details", 'D', RTGETOPT_REQ_NOTHING },
172 { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING },
173 };
174
175 VMINFO_DETAILS enmDetails = VMINFO_STANDARD;
176
177 int c;
178 RTGETOPTUNION ValueUnion;
179 RTGETOPTSTATE GetState;
180 RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, g_aOptions, RT_ELEMENTS(g_aOptions), 2 /*iArg*/, 0 /*fFlags*/);
181 while ((c = RTGetOpt(&GetState, &ValueUnion)))
182 {
183 switch (c)
184 {
185 case 'D': enmDetails = VMINFO_FULL; break;
186 case 'M': enmDetails = VMINFO_MACHINEREADABLE; break;
187 default: return errorGetOpt(USAGE_SNAPSHOT, c, &ValueUnion);
188 }
189 }
190
191 ComPtr<ISnapshot> pSnapshot;
192 HRESULT hrc = pMachine->FindSnapshot(Bstr().raw(), pSnapshot.asOutParam());
193 if (FAILED(hrc))
194 {
195 RTPrintf("This machine does not have any snapshots\n");
196 return RTEXITCODE_FAILURE;
197 }
198 if (pSnapshot)
199 {
200 ComPtr<ISnapshot> pCurrentSnapshot;
201 CHECK_ERROR2_RET(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()), RTEXITCODE_FAILURE);
202 hrc = showSnapshots(pSnapshot, pCurrentSnapshot, enmDetails);
203 if (FAILED(hrc))
204 return RTEXITCODE_FAILURE;
205 }
206 return RTEXITCODE_SUCCESS;
207}
208
209/**
210 * Implementation for "VBoxManage snapshot ... dump". This goes thru the machine's
211 * medium attachments and calls DumpMediumWithChildren() for each hard disk medium found,
212 * which then dumps the parent/child tree of that medium together with the corresponding
213 * snapshots.
214 * @param pMachine Machine to dump snapshots for.
215 */
216void DumpSnapshot(ComPtr<IMachine> &pMachine)
217{
218 HRESULT rc;
219
220 do
221 {
222 // get root snapshot
223 ComPtr<ISnapshot> pSnapshot;
224 CHECK_ERROR_BREAK(pMachine, FindSnapshot(Bstr("").raw(), pSnapshot.asOutParam()));
225
226 // get current snapshot
227 ComPtr<ISnapshot> pCurrentSnapshot;
228 CHECK_ERROR_BREAK(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()));
229
230 // get media attachments
231 SafeIfaceArray<IMediumAttachment> aAttachments;
232 CHECK_ERROR_BREAK(pMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments)));
233 for (uint32_t i = 0;
234 i < aAttachments.size();
235 ++i)
236 {
237 ComPtr<IMediumAttachment> pAttach(aAttachments[i]);
238 DeviceType_T type;
239 CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type));
240 if (type == DeviceType_HardDisk)
241 {
242 ComPtr<IMedium> pCurrentStateMedium;
243 CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pCurrentStateMedium.asOutParam()));
244
245 ComPtr<IMedium> pBaseMedium;
246 CHECK_ERROR_BREAK(pCurrentStateMedium, COMGETTER(Base)(pBaseMedium.asOutParam()));
247
248 Bstr bstrBaseMediumName;
249 CHECK_ERROR_BREAK(pBaseMedium, COMGETTER(Name)(bstrBaseMediumName.asOutParam()));
250
251 RTPrintf("[%RI32] Images and snapshots for medium \"%ls\"\n", i, bstrBaseMediumName.raw());
252
253 DumpMediumWithChildren(pCurrentStateMedium,
254 pBaseMedium,
255 pSnapshot,
256 pCurrentSnapshot,
257 0);
258 }
259 }
260 } while (0);
261}
262
263typedef enum SnapshotUniqueFlags
264{
265 SnapshotUniqueFlags_Null = 0,
266 SnapshotUniqueFlags_Number = RT_BIT(1),
267 SnapshotUniqueFlags_Timestamp = RT_BIT(2),
268 SnapshotUniqueFlags_Space = RT_BIT(16),
269 SnapshotUniqueFlags_Force = RT_BIT(30)
270} SnapshotUniqueFlags;
271
272static int parseSnapshotUniqueFlags(const char *psz, SnapshotUniqueFlags *pUnique)
273{
274 int rc = VINF_SUCCESS;
275 unsigned uUnique = 0;
276 while (psz && *psz && RT_SUCCESS(rc))
277 {
278 size_t len;
279 const char *pszComma = strchr(psz, ',');
280 if (pszComma)
281 len = pszComma - psz;
282 else
283 len = strlen(psz);
284 if (len > 0)
285 {
286 if (!RTStrNICmp(psz, "number", len))
287 uUnique |= SnapshotUniqueFlags_Number;
288 else if (!RTStrNICmp(psz, "timestamp", len))
289 uUnique |= SnapshotUniqueFlags_Timestamp;
290 else if (!RTStrNICmp(psz, "space", len))
291 uUnique |= SnapshotUniqueFlags_Space;
292 else if (!RTStrNICmp(psz, "force", len))
293 uUnique |= SnapshotUniqueFlags_Force;
294 else
295 rc = VERR_PARSE_ERROR;
296 }
297 if (pszComma)
298 psz += len + 1;
299 else
300 psz += len;
301 }
302
303 if (RT_SUCCESS(rc))
304 *pUnique = (SnapshotUniqueFlags)uUnique;
305 return rc;
306}
307
308/**
309 * Implementation for all VBoxManage snapshot ... subcommands.
310 * @param a
311 * @return
312 */
313int handleSnapshot(HandlerArg *a)
314{
315 HRESULT rc;
316
317 /* we need at least a VM and a command */
318 if (a->argc < 2)
319 return errorSyntax(USAGE_SNAPSHOT, "Not enough parameters");
320
321 /* the first argument must be the VM */
322 Bstr bstrMachine(a->argv[0]);
323 ComPtr<IMachine> pMachine;
324 CHECK_ERROR(a->virtualBox, FindMachine(bstrMachine.raw(),
325 pMachine.asOutParam()));
326 if (!pMachine)
327 return 1;
328
329 /* we have to open a session for this task (new or shared) */
330 CHECK_ERROR_RET(pMachine, LockMachine(a->session, LockType_Shared), 1);
331 do
332 {
333 /* replace the (read-only) IMachine object by a writable one */
334 ComPtr<IMachine> sessionMachine;
335 CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
336
337 /* switch based on the command */
338 bool fDelete = false,
339 fRestore = false,
340 fRestoreCurrent = false;
341
342 if (!strcmp(a->argv[1], "take"))
343 {
344 /* there must be a name */
345 if (a->argc < 3)
346 {
347 errorSyntax(USAGE_SNAPSHOT, "Missing snapshot name");
348 rc = E_FAIL;
349 break;
350 }
351 Bstr name(a->argv[2]);
352
353 /* parse the optional arguments */
354 Bstr desc;
355 bool fPause = true; /* default is NO live snapshot */
356 SnapshotUniqueFlags enmUnique = SnapshotUniqueFlags_Null;
357 static const RTGETOPTDEF s_aTakeOptions[] =
358 {
359 { "--description", 'd', RTGETOPT_REQ_STRING },
360 { "-description", 'd', RTGETOPT_REQ_STRING },
361 { "-desc", 'd', RTGETOPT_REQ_STRING },
362 { "--pause", 'p', RTGETOPT_REQ_NOTHING },
363 { "--live", 'l', RTGETOPT_REQ_NOTHING },
364 { "--uniquename", 'u', RTGETOPT_REQ_STRING }
365 };
366 RTGETOPTSTATE GetOptState;
367 RTGetOptInit(&GetOptState, a->argc, a->argv, s_aTakeOptions, RT_ELEMENTS(s_aTakeOptions),
368 3, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
369 int ch;
370 RTGETOPTUNION Value;
371 int vrc;
372 while ( SUCCEEDED(rc)
373 && (ch = RTGetOpt(&GetOptState, &Value)))
374 {
375 switch (ch)
376 {
377 case 'p':
378 fPause = true;
379 break;
380
381 case 'l':
382 fPause = false;
383 break;
384
385 case 'd':
386 desc = Value.psz;
387 break;
388
389 case 'u':
390 vrc = parseSnapshotUniqueFlags(Value.psz, &enmUnique);
391 if (RT_FAILURE(vrc))
392 return errorArgument("Invalid unique name description '%s'", Value.psz);
393 break;
394
395 default:
396 errorGetOpt(USAGE_SNAPSHOT, ch, &Value);
397 rc = E_FAIL;
398 break;
399 }
400 }
401 if (FAILED(rc))
402 break;
403
404 if (enmUnique & (SnapshotUniqueFlags_Number | SnapshotUniqueFlags_Timestamp))
405 {
406 ComPtr<ISnapshot> pSnapshot;
407 rc = sessionMachine->FindSnapshot(name.raw(),
408 pSnapshot.asOutParam());
409 if (SUCCEEDED(rc) || (enmUnique & SnapshotUniqueFlags_Force))
410 {
411 /* there is a duplicate, need to create a unique name */
412 uint32_t count = 0;
413 RTTIMESPEC now;
414
415 if (enmUnique & SnapshotUniqueFlags_Number)
416 {
417 if (enmUnique & SnapshotUniqueFlags_Force)
418 count = 1;
419 else
420 count = 2;
421 }
422 else
423 RTTimeNow(&now);
424
425 while (count < 500)
426 {
427 Utf8Str suffix;
428 if (enmUnique & SnapshotUniqueFlags_Number)
429 suffix = Utf8StrFmt("%u", count);
430 else
431 {
432 RTTIMESPEC nowplus = now;
433 RTTimeSpecAddSeconds(&nowplus, count);
434 RTTIME stamp;
435 RTTimeExplode(&stamp, &nowplus);
436 suffix = Utf8StrFmt("%04u-%02u-%02uT%02u:%02u:%02uZ", stamp.i32Year, stamp.u8Month, stamp.u8MonthDay, stamp.u8Hour, stamp.u8Minute, stamp.u8Second);
437 }
438 Bstr tryName = name;
439 if (enmUnique & SnapshotUniqueFlags_Space)
440 tryName = BstrFmt("%ls %s", name.raw(), suffix.c_str());
441 else
442 tryName = BstrFmt("%ls%s", name.raw(), suffix.c_str());
443 count++;
444 rc = sessionMachine->FindSnapshot(tryName.raw(),
445 pSnapshot.asOutParam());
446 if (FAILED(rc))
447 {
448 name = tryName;
449 break;
450 }
451 }
452 if (SUCCEEDED(rc))
453 {
454 errorArgument("Failed to generate a unique snapshot name");
455 rc = E_FAIL;
456 break;
457 }
458 }
459 rc = S_OK;
460 }
461
462 ComPtr<IProgress> progress;
463 Bstr snapId;
464 CHECK_ERROR_BREAK(sessionMachine, TakeSnapshot(name.raw(), desc.raw(),
465 fPause, snapId.asOutParam(),
466 progress.asOutParam()));
467
468 rc = showProgress(progress);
469 if (SUCCEEDED(rc))
470 RTPrintf("Snapshot taken. UUID: %ls\n", snapId.raw());
471 else
472 CHECK_PROGRESS_ERROR(progress, ("Failed to take snapshot"));
473 }
474 else if ( (fDelete = !strcmp(a->argv[1], "delete"))
475 || (fRestore = !strcmp(a->argv[1], "restore"))
476 || (fRestoreCurrent = !strcmp(a->argv[1], "restorecurrent"))
477 )
478 {
479 if (fRestoreCurrent)
480 {
481 if (a->argc > 2)
482 {
483 errorSyntax(USAGE_SNAPSHOT, "Too many arguments");
484 rc = E_FAIL;
485 break;
486 }
487 }
488 /* exactly one parameter: snapshot name */
489 else if (a->argc != 3)
490 {
491 errorSyntax(USAGE_SNAPSHOT, "Expecting snapshot name only");
492 rc = E_FAIL;
493 break;
494 }
495
496 ComPtr<ISnapshot> pSnapshot;
497 ComPtr<IProgress> pProgress;
498 Bstr bstrSnapGuid;
499
500 if (fRestoreCurrent)
501 {
502 CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam()));
503 }
504 else
505 {
506 // restore or delete snapshot: then resolve cmd line argument to snapshot instance
507 CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(),
508 pSnapshot.asOutParam()));
509 }
510
511 CHECK_ERROR_BREAK(pSnapshot, COMGETTER(Id)(bstrSnapGuid.asOutParam()));
512
513 if (fDelete)
514 {
515 CHECK_ERROR_BREAK(sessionMachine, DeleteSnapshot(bstrSnapGuid.raw(),
516 pProgress.asOutParam()));
517 }
518 else
519 {
520 // restore or restore current
521 RTPrintf("Restoring snapshot %ls\n", bstrSnapGuid.raw());
522 CHECK_ERROR_BREAK(sessionMachine, RestoreSnapshot(pSnapshot, pProgress.asOutParam()));
523 }
524
525 rc = showProgress(pProgress);
526 CHECK_PROGRESS_ERROR(pProgress, ("Snapshot operation failed"));
527 }
528 else if (!strcmp(a->argv[1], "edit"))
529 {
530 if (a->argc < 3)
531 {
532 errorSyntax(USAGE_SNAPSHOT, "Missing snapshot name");
533 rc = E_FAIL;
534 break;
535 }
536
537 ComPtr<ISnapshot> pSnapshot;
538
539 if ( !strcmp(a->argv[2], "--current")
540 || !strcmp(a->argv[2], "-current"))
541 {
542 CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam()));
543 }
544 else
545 {
546 CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(),
547 pSnapshot.asOutParam()));
548 }
549
550 /* parse options */
551 for (int i = 3; i < a->argc; i++)
552 {
553 if ( !strcmp(a->argv[i], "--name")
554 || !strcmp(a->argv[i], "-name")
555 || !strcmp(a->argv[i], "-newname"))
556 {
557 if (a->argc <= i + 1)
558 {
559 errorArgument("Missing argument to '%s'", a->argv[i]);
560 rc = E_FAIL;
561 break;
562 }
563 i++;
564 pSnapshot->COMSETTER(Name)(Bstr(a->argv[i]).raw());
565 }
566 else if ( !strcmp(a->argv[i], "--description")
567 || !strcmp(a->argv[i], "-description")
568 || !strcmp(a->argv[i], "-newdesc"))
569 {
570 if (a->argc <= i + 1)
571 {
572 errorArgument("Missing argument to '%s'", a->argv[i]);
573 rc = E_FAIL;
574 break;
575 }
576 i++;
577 pSnapshot->COMSETTER(Description)(Bstr(a->argv[i]).raw());
578 }
579 else
580 {
581 errorSyntax(USAGE_SNAPSHOT, "Invalid parameter '%s'", Utf8Str(a->argv[i]).c_str());
582 rc = E_FAIL;
583 break;
584 }
585 }
586
587 }
588 else if (!strcmp(a->argv[1], "showvminfo"))
589 {
590 /* exactly one parameter: snapshot name */
591 if (a->argc != 3)
592 {
593 errorSyntax(USAGE_SNAPSHOT, "Expecting snapshot name only");
594 rc = E_FAIL;
595 break;
596 }
597
598 ComPtr<ISnapshot> pSnapshot;
599
600 CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(),
601 pSnapshot.asOutParam()));
602
603 /* get the machine of the given snapshot */
604 ComPtr<IMachine> pMachine2;
605 pSnapshot->COMGETTER(Machine)(pMachine2.asOutParam());
606 showVMInfo(a->virtualBox, pMachine2, NULL, VMINFO_NONE);
607 }
608 else if (!strcmp(a->argv[1], "list"))
609 rc = handleSnapshotList(a, sessionMachine) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL;
610 else if (!strcmp(a->argv[1], "dump")) // undocumented parameter to debug snapshot info
611 DumpSnapshot(sessionMachine);
612 else
613 {
614 errorSyntax(USAGE_SNAPSHOT, "Invalid parameter '%s'", Utf8Str(a->argv[1]).c_str());
615 rc = E_FAIL;
616 }
617 } while (0);
618
619 a->session->UnlockMachine();
620
621 return SUCCEEDED(rc) ? 0 : 1;
622}
623
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette