VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.cpp@ 59727

Last change on this file since 59727 was 59637, checked in by vboxsync, 9 years ago

BugReportTool(bugref:8169): Meaningful error messages, fallback to home dir if no write access, print where the output went

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.9 KB
Line 
1/* $Id: VBoxBugReport.cpp 59637 2016-02-11 13:10:28Z vboxsync $ */
2/** @file
3 * VBoxBugReport - VirtualBox command-line diagnostics tool, main file.
4 */
5
6/*
7 * Copyright (C) 2006-2016 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#include <VBox/com/com.h>
20#include <VBox/com/string.h>
21#include <VBox/com/array.h>
22//#include <VBox/com/Guid.h>
23#include <VBox/com/ErrorInfo.h>
24#include <VBox/com/errorprint.h>
25#include <VBox/com/VirtualBox.h>
26
27#include <VBox/version.h>
28
29#include <iprt/buildconfig.h>
30#include <iprt/env.h>
31#include <iprt/file.h>
32#include <iprt/getopt.h>
33#include <iprt/initterm.h>
34#include <iprt/path.h>
35#include <iprt/process.h>
36#include <iprt/zip.h>
37#include <iprt/cpp/exception.h>
38
39#include <list>
40
41#include "VBoxBugReport.h"
42
43/* Implementation - Base */
44
45#ifndef RT_OS_WINDOWS
46/* @todo Replace with platform-specific implementations. */
47void createBugReportOsSpecific(BugReport* report, const char *pszHome)
48{
49}
50#endif /* !RT_OS_WINDOWS */
51
52
53/* Globals */
54
55static char *g_pszVBoxManage = NULL;
56
57static const RTGETOPTDEF g_aOptions[] =
58{
59 { "-all", 'A', RTGETOPT_REQ_NOTHING },
60 { "--all", 'A', RTGETOPT_REQ_NOTHING },
61 { "-output", 'o', RTGETOPT_REQ_STRING },
62 { "--output", 'o', RTGETOPT_REQ_STRING },
63 { "-text", 't', RTGETOPT_REQ_NOTHING },
64 { "--text", 't', RTGETOPT_REQ_NOTHING }
65};
66
67static const char g_szUsage[] =
68 "Usage: %s [-h|-?|--help] [-A|--all|<vmname>...] [-o <file>|--output=<file>]\n"
69 " Several VM names can be specified at once to be included into single report.\n"
70 " If none is given then no machines will be included. Specifying -A overrides\n"
71 " any VM names provided and included all registered machines.\n"
72 "Options:\n"
73 " -h, -help, --help Print usage information\n"
74 " -A, -all, --all Include all registered machines\n"
75 " -o, -output, --output Specifies the name of the output file\n"
76 " -t, -text, --text Produce a single text file instead of compressed TAR\n"
77 " -V, -version, --version Print version number and exit\n"
78 "\n";
79
80
81/*
82 * This class stores machine-specific file paths that are obtained via
83 * VirtualBox API. In case API is not functioning properly these paths
84 * will be deduced on the best effort basis.
85 */
86class MachineInfo
87{
88public:
89 MachineInfo(const char *name, const char *logFolder, const char *settingsFile);
90 ~MachineInfo();
91 const char *getName() const { return m_name; };
92 const char *getLogPath() const { return m_logpath; };
93 const char *getSettingsFile() const { return m_settings; };
94private:
95 char *m_name;
96 char *m_logpath;
97 char *m_settings;
98};
99
100MachineInfo::MachineInfo(const char *name, const char *logFolder, const char *settingsFile)
101{
102 m_name = RTStrDup(name);
103 m_logpath = RTStrDup(logFolder);
104 m_settings = RTStrDup(settingsFile);
105}
106
107MachineInfo::~MachineInfo()
108{
109 RTStrFree(m_logpath);
110 RTStrFree(m_name);
111 RTStrFree(m_settings);
112 m_logpath = m_name = m_settings = 0;
113}
114
115typedef std::list<MachineInfo*> MachineInfoList;
116
117
118/*
119 * An abstract class serving as the root of the bug report item tree.
120 */
121BugReportItem::BugReportItem(const char *pszTitle)
122{
123 m_pszTitle = RTStrDup(pszTitle);
124}
125
126BugReportItem::~BugReportItem()
127{
128 RTStrFree(m_pszTitle);
129}
130
131const char * BugReportItem::getTitle(void)
132{
133 return m_pszTitle;
134}
135
136
137BugReport::BugReport(const char *pszFileName)
138{
139 m_pszFileName = RTStrDup(pszFileName);
140}
141
142BugReport::~BugReport()
143{
144 RTStrFree(m_pszFileName);
145}
146
147
148BugReportStream::BugReportStream(const char *pszTitle) : BugReportItem(pszTitle)
149{
150 handleRtError(RTPathTemp(m_szFileName, RTPATH_MAX),
151 "Failed to obtain path to temporary folder");
152 handleRtError(RTPathAppend(m_szFileName, RTPATH_MAX, "BugRepXXXXX.tmp"),
153 "Failed to append path");
154 handleRtError(RTFileCreateTemp(m_szFileName, 0600),
155 "Failed to create temporary file '%s'", m_szFileName);
156 handleRtError(RTStrmOpen(m_szFileName, "w", &m_Strm),
157 "Failed to open '%s'", m_szFileName);
158}
159
160BugReportStream::~BugReportStream()
161{
162 if (m_Strm)
163 RTStrmClose(m_Strm);
164 RTFileDelete(m_szFileName);
165}
166
167int BugReportStream::printf(const char *pszFmt, ...)
168{
169 va_list va;
170 va_start(va, pszFmt);
171 int cb = RTStrmPrintfV(m_Strm, pszFmt, va);
172 va_end(va);
173 return cb;
174}
175
176int BugReportStream::putStr(const char *pszString)
177{
178 return RTStrmPutStr(m_Strm, pszString);
179}
180
181PRTSTREAM BugReportStream::getStream(void)
182{
183 RTStrmClose(m_Strm);
184 handleRtError(RTStrmOpen(m_szFileName, "r", &m_Strm),
185 "Failed to open '%s'", m_szFileName);
186 return m_Strm;
187}
188
189
190/* Implementation - Generic */
191
192BugReportFile::BugReportFile(const char *pszPath, const char *pszShortName) : BugReportItem(pszShortName)
193{
194 m_Strm = 0;
195 m_pszPath = RTStrDup(pszPath);
196}
197
198BugReportFile::~BugReportFile()
199{
200 if (m_Strm)
201 RTStrmClose(m_Strm);
202 if (m_pszPath)
203 RTStrFree(m_pszPath);
204}
205
206PRTSTREAM BugReportFile::getStream(void)
207{
208 handleRtError(RTStrmOpen(m_pszPath, "rb", &m_Strm),
209 "Failed to open '%s'", m_pszPath);
210 return m_Strm;
211}
212
213
214BugReportCommand::BugReportCommand(const char *pszTitle, const char *pszExec, ...)
215 : BugReportItem(pszTitle), m_Strm(NULL)
216{
217 unsigned cArgs = 0;
218 m_papszArgs[cArgs++] = RTStrDup(pszExec);
219
220 const char *pszArg;
221 va_list va;
222 va_start(va, pszExec);
223 do
224 {
225 if (cArgs >= RT_ELEMENTS(m_papszArgs))
226 throw RTCError(com::Utf8StrFmt("Too many arguments (%u > %u)\n", cArgs+1, RT_ELEMENTS(m_papszArgs)));
227 pszArg = va_arg(va, const char *);
228 m_papszArgs[cArgs++] = pszArg ? RTStrDup(pszArg) : NULL;
229 } while (pszArg);
230 va_end(va);
231}
232
233BugReportCommand::~BugReportCommand()
234{
235 if (m_Strm)
236 RTStrmClose(m_Strm);
237 RTFileDelete(m_szFileName);
238 for (size_t i = 0; i < RT_ELEMENTS(m_papszArgs) && m_papszArgs[i]; ++i)
239 RTStrFree(m_papszArgs[i]);
240}
241
242PRTSTREAM BugReportCommand::getStream(void)
243{
244 handleRtError(RTPathTemp(m_szFileName, RTPATH_MAX),
245 "Failed to obtain path to temporary folder");
246 handleRtError(RTPathAppend(m_szFileName, RTPATH_MAX, "BugRepXXXXX.tmp"),
247 "Failed to append path");
248 handleRtError(RTFileCreateTemp(m_szFileName, 0600),
249 "Failed to create temporary file '%s'", m_szFileName);
250
251 RTHANDLE hStdOutErr;
252 hStdOutErr.enmType = RTHANDLETYPE_FILE;
253 handleRtError(RTFileOpen(&hStdOutErr.u.hFile, m_szFileName,
254 RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE),
255 "Failed to open temporary file '%s'", m_szFileName);
256
257 RTPROCESS hProcess;
258 handleRtError(RTProcCreateEx(m_papszArgs[0], m_papszArgs, RTENV_DEFAULT, 0,
259 NULL, &hStdOutErr, &hStdOutErr,
260 NULL, NULL, &hProcess),
261 "Failed to create process '%s'", m_papszArgs[0]);
262 RTPROCSTATUS status;
263 handleRtError(RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &status),
264 "Process wait failed");
265 //if (status.enmReason == RTPROCEXITREASON_NORMAL) {}
266 RTFileClose(hStdOutErr.u.hFile);
267
268 handleRtError(RTStrmOpen(m_szFileName, "r", &m_Strm),
269 "Failed to open '%s'", m_szFileName);
270 return m_Strm;
271}
272
273
274BugReportText::BugReportText(const char *pszFileName) : BugReport(pszFileName)
275{
276 handleRtError(RTStrmOpen(pszFileName, "w", &m_StrmTxt),
277 "Failed to open '%s'", pszFileName);
278}
279
280BugReportText::~BugReportText()
281{
282 if (m_StrmTxt)
283 RTStrmClose(m_StrmTxt);
284}
285
286int BugReportText::addItem(BugReportItem* item)
287{
288 if (!item)
289 return VERR_INVALID_PARAMETER;
290
291 int cb = RTStrmPrintf(m_StrmTxt, "[ %s ] -------------------------------------------\n", item->getTitle());
292 if (!cb)
293 throw RTCError(com::Utf8StrFmt("Write failure (cb=%d)\n", cb));
294
295 PRTSTREAM strmIn = NULL;
296 try
297 {
298 strmIn = item->getStream();
299 }
300 catch (RTCError &e)
301 {
302 strmIn = NULL;
303 RTStrmPutStr(m_StrmTxt, e.what());
304 }
305
306 int rc = VINF_SUCCESS;
307
308 if (strmIn)
309 {
310 char buf[64*1024];
311 size_t cbRead, cbWritten;
312 cbRead = cbWritten = 0;
313 while (RT_SUCCESS(rc = RTStrmReadEx(strmIn, buf, sizeof(buf), &cbRead)) && cbRead)
314 {
315 rc = RTStrmWriteEx(m_StrmTxt, buf, cbRead, &cbWritten);
316 if (RT_FAILURE(rc) || cbRead != cbWritten)
317 throw RTCError(com::Utf8StrFmt("Write failure (rc=%d, cbRead=%lu, cbWritten=%lu)\n",
318 rc, cbRead, cbWritten));
319 }
320 }
321
322 handleRtError(RTStrmPutCh(m_StrmTxt, '\n'), "Write failure");
323
324 delete item;
325
326 return rc;
327}
328
329
330BugReportTarGzip::BugReportTarGzip(const char *pszFileName)
331 : BugReport(pszFileName), m_hTar(NIL_RTTAR), m_hTarFile(NIL_RTTARFILE)
332{
333 VfsIoStreamHandle hVfsOut;
334 handleRtError(RTVfsIoStrmOpenNormal(pszFileName, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE,
335 hVfsOut.getPtr()),
336 "Failed to create output file '%s'", pszFileName);
337 handleRtError(RTZipGzipCompressIoStream(hVfsOut.get(), 0, 6, m_hVfsGzip.getPtr()),
338 "Failed to create compressed stream for '%s'", pszFileName);
339
340 handleRtError(RTPathTemp(m_szTarName, RTPATH_MAX),
341 "Failed to obtain path to temporary folder");
342 handleRtError(RTPathAppend(m_szTarName, RTPATH_MAX, "BugRepXXXXX.tar"),
343 "Failed to append path");
344 handleRtError(RTFileCreateTemp(m_szTarName, 0600),
345 "Failed to create temporary file '%s'", m_szTarName);
346 handleRtError(RTFileDelete(m_szTarName),
347 "Failed to delete temporary file '%s'", m_szTarName);
348 handleRtError(RTTarOpen(&m_hTar, m_szTarName, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL),
349 "Failed to create TAR file '%s'", m_szTarName);
350
351}
352
353BugReportTarGzip::~BugReportTarGzip()
354{
355 if (m_hTarFile != NIL_RTTARFILE)
356 RTTarFileClose(m_hTarFile);
357 if (m_hTar != NIL_RTTAR)
358 RTTarClose(m_hTar);
359}
360
361int BugReportTarGzip::addItem(BugReportItem* item)
362{
363 if (!item)
364 return VERR_INVALID_PARAMETER;
365
366 handleRtError(RTTarFileOpen(m_hTar, &m_hTarFile, item->getTitle(),
367 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE),
368 "Failed to open '%s' in TAR", item->getTitle());
369
370 PRTSTREAM strmIn = NULL;
371 try
372 {
373 strmIn = item->getStream();
374 }
375 catch (RTCError &e)
376 {
377 strmIn = NULL;
378 handleRtError(RTTarFileWriteAt(m_hTarFile, 0, e.what(), RTStrNLen(e.what(), 1024), NULL),
379 "Failed to write %u bytes to TAR", RTStrNLen(e.what(), 1024));
380 }
381
382 int rc = VINF_SUCCESS;
383
384 if (strmIn)
385 {
386 char buf[64*1024];
387 size_t cbRead = 0;
388 for (uint64_t offset = 0;
389 RT_SUCCESS(rc = RTStrmReadEx(strmIn, buf, sizeof(buf), &cbRead)) && cbRead;
390 offset += cbRead)
391 {
392 handleRtError(RTTarFileWriteAt(m_hTarFile, offset, buf, cbRead, NULL),
393 "Failed to write %u bytes to TAR", cbRead);
394 }
395 }
396
397 if (m_hTarFile)
398 {
399 RTTarFileClose(m_hTarFile);
400 m_hTarFile = NIL_RTTARFILE;
401 }
402
403 delete item;
404
405 return rc;
406}
407
408void BugReportTarGzip::complete(void)
409{
410 if (m_hTarFile != NIL_RTTARFILE)
411 {
412 RTTarFileClose(m_hTarFile);
413 m_hTarFile = NIL_RTTARFILE;
414 }
415 if (m_hTar != NIL_RTTAR)
416 {
417 RTTarClose(m_hTar);
418 m_hTar = NIL_RTTAR;
419 }
420
421 VfsIoStreamHandle hVfsIn;
422 handleRtError(RTVfsIoStrmOpenNormal(m_szTarName, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
423 hVfsIn.getPtr()),
424 "Failed to open TAR file '%s'", m_szTarName);
425
426 int rc;
427 char buf[_64K];
428 size_t cbRead = 0;
429 while (RT_SUCCESS(rc = RTVfsIoStrmRead(hVfsIn.get(), buf, sizeof(buf), true, &cbRead)) && cbRead)
430 handleRtError(RTVfsIoStrmWrite(m_hVfsGzip.get(), buf, cbRead, true, NULL),
431 "Failed to write into compressed stream");
432 handleRtError(rc, "Failed to read from TAR stream");
433 handleRtError(RTVfsIoStrmFlush(m_hVfsGzip.get()), "Failed to flush output stream");
434 m_hVfsGzip.release();
435}
436
437
438/* Implementation - Main */
439
440void createBugReport(BugReport* report, const char *pszHome, MachineInfoList& machines)
441{
442 report->addItem(new BugReportFile(PathJoin(pszHome, "VBoxSVC.log"), "VBoxSVC.log"));
443 report->addItem(new BugReportFile(PathJoin(pszHome, "VBoxSVC.log.1"), "VBoxSVC.log.1"));
444 report->addItem(new BugReportFile(PathJoin(pszHome, "VirtualBox.xml"), "VirtualBox.xml"));
445 for (MachineInfoList::iterator it = machines.begin(); it != machines.end(); ++it)
446 {
447 report->addItem(new BugReportFile(PathJoin((*it)->getLogPath(), "VBox.log"),
448 PathJoin((*it)->getName(), "VBox.log")));
449 report->addItem(new BugReportFile((*it)->getSettingsFile(),
450 PathJoin((*it)->getName(), RTPathFilename((*it)->getSettingsFile()))));
451 report->addItem(new BugReportCommand(PathJoin((*it)->getName(), "GuestProperties"),
452 g_pszVBoxManage, "guestproperty", "enumerate",
453 (*it)->getName(), NULL));
454 }
455
456 createBugReportOsSpecific(report, pszHome);
457}
458
459void addMachine(MachineInfoList& list, ComPtr<IMachine> machine)
460{
461 com::Bstr name, logFolder, settingsFile;
462 handleComError(machine->COMGETTER(Name)(name.asOutParam()),
463 "Failed to get VM name");
464 handleComError(machine->COMGETTER(LogFolder)(logFolder.asOutParam()),
465 "Failed to get VM log folder");
466 handleComError(machine->COMGETTER(SettingsFilePath)(settingsFile.asOutParam()),
467 "Failed to get VM settings file path");
468 list.push_back(new MachineInfo(com::Utf8Str(name).c_str(),
469 com::Utf8Str(logFolder).c_str(),
470 com::Utf8Str(settingsFile).c_str()));
471}
472
473
474static void printHeader(void)
475{
476 RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " Bug Report Tool " VBOX_VERSION_STRING "\n"
477 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
478 "All rights reserved.\n\n");
479}
480
481int main(int argc, char *argv[])
482{
483 /*
484 * Initialize the VBox runtime without loading
485 * the support driver.
486 */
487 RTR3InitExe(argc, &argv, 0);
488
489 bool fAllMachines = false;
490 bool fTextOutput = false;
491 const char *pszOutputFile = NULL;
492 std::list<const char *> nameList;
493 RTGETOPTUNION ValueUnion;
494 RTGETOPTSTATE GetState;
495 int ret = RTGetOptInit(&GetState, argc, argv,
496 g_aOptions, RT_ELEMENTS(g_aOptions),
497 1 /* First */, 0 /*fFlags*/);
498 if (RT_FAILURE(ret))
499 return ret;
500 int ch;
501 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
502 {
503 switch(ch)
504 {
505 case 'h':
506 printHeader();
507 RTStrmPrintf(g_pStdErr, g_szUsage, argv[0]);
508 return 0;
509 case 'A':
510 fAllMachines = true;
511 break;
512 case 'o':
513 pszOutputFile = ValueUnion.psz;
514 break;
515 case 't':
516 fTextOutput = true;
517 break;
518 case 'V':
519 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
520 return 0;
521 case VINF_GETOPT_NOT_OPTION:
522 nameList.push_back(ValueUnion.psz);
523 break;
524 default:
525 return RTGetOptPrintError(ch, &ValueUnion);
526 }
527 }
528
529 printHeader();
530
531 HRESULT hr = S_OK;
532 char homeDir[RTPATH_MAX];
533 com::GetVBoxUserHomeDirectory(homeDir, sizeof(homeDir));
534
535 try
536 {
537 /* Figure out full path to VBoxManage */
538 char *pszVBoxBin = RTStrDup(argv[0]);
539 if (!pszVBoxBin)
540 throw RTCError("Out of memory\n");
541 RTPathStripFilename(pszVBoxBin);
542 g_pszVBoxManage = RTPathJoinA(pszVBoxBin, VBOXMANAGE);
543 if (!g_pszVBoxManage)
544 throw RTCError("Out of memory\n");
545 RTStrFree(pszVBoxBin);
546
547 handleComError(com::Initialize(), "Failed to initialize COM");
548
549 MachineInfoList list;
550
551 do
552 {
553 ComPtr<IVirtualBox> virtualBox;
554 ComPtr<ISession> session;
555
556 hr = virtualBox.createLocalObject(CLSID_VirtualBox);
557 if (FAILED(hr))
558 RTStrmPrintf(g_pStdErr, "WARNING: Failed to create the VirtualBox object (hr=0x%x)\n", hr);
559 else
560 {
561 hr = session.createInprocObject(CLSID_Session);
562 if (FAILED(hr))
563 RTStrmPrintf(g_pStdErr, "WARNING: Failed to create a session object (hr=0x%x)\n", hr);
564 }
565
566 if (SUCCEEDED(hr))
567 {
568 if (fAllMachines)
569 {
570 com::SafeIfaceArray<IMachine> machines;
571 hr = virtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines));
572 if (SUCCEEDED(hr))
573 {
574 for (size_t i = 0; i < machines.size(); ++i)
575 {
576 if (machines[i])
577 addMachine(list, machines[i]);
578 }
579 }
580 }
581 else
582 {
583 for ( std::list<const char *>::iterator it = nameList.begin(); it != nameList.end(); ++it)
584 {
585 ComPtr<IMachine> machine;
586 handleComError(virtualBox->FindMachine(com::Bstr(*it).raw(), machine.asOutParam()),
587 "No such machine '%s'", *it);
588 addMachine(list, machine);
589 }
590 }
591 }
592
593 }
594 while(0);
595
596 RTTIMESPEC TimeSpec;
597 RTTIME Time;
598 RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
599 RTCStringFmt strOutFile("%04d-%02d-%02d-%02d-%02d-%02d-bugreport.%s",
600 Time.i32Year, Time.u8Month, Time.u8MonthDay,
601 Time.u8Hour, Time.u8Minute, Time.u8Second,
602 fTextOutput ? "txt" : "tgz");
603 RTCString strFallbackOutFile;
604 if (!pszOutputFile)
605 {
606 RTFILE tmp;
607 pszOutputFile = strOutFile.c_str();
608 int rc = RTFileOpen(&tmp, pszOutputFile, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
609 if (rc == VERR_ACCESS_DENIED)
610 {
611 char szUserHome[RTPATH_MAX];
612 handleRtError(RTPathUserHome(szUserHome, sizeof(szUserHome)), "Failed to obtain home directory");
613 strFallbackOutFile.printf("%s/%s", szUserHome, strOutFile.c_str());
614 pszOutputFile = strFallbackOutFile.c_str();
615 }
616 else if (RT_SUCCESS(rc))
617 {
618 RTFileClose(tmp);
619 RTFileDelete(pszOutputFile);
620 }
621 }
622 BugReport *pReport;
623 if (fTextOutput)
624 pReport = new BugReportText(pszOutputFile);
625 else
626 pReport = new BugReportTarGzip(pszOutputFile);
627 createBugReport(pReport, homeDir, list);
628 pReport->complete();
629 RTPrintf("Report was written to '%s'\n", pszOutputFile);
630 delete pReport;
631 }
632 catch (RTCError &e)
633 {
634 RTStrmPrintf(g_pStdErr, "ERROR: %s\n", e.what());
635 }
636
637 com::Shutdown();
638
639 if (g_pszVBoxManage)
640 RTStrFree(g_pszVBoxManage);
641
642 return SUCCEEDED(hr) ? 0 : 1;
643}
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