VirtualBox

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

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

make VBoxBugReport public

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