summaryrefslogtreecommitdiffstats
path: root/src/VBox/Frontends/VBoxBugReport
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Frontends/VBoxBugReport
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Frontends/VBoxBugReport')
-rw-r--r--src/VBox/Frontends/VBoxBugReport/Makefile.kmk48
-rw-r--r--src/VBox/Frontends/VBoxBugReport/VBoxBugReport.cpp845
-rw-r--r--src/VBox/Frontends/VBoxBugReport/VBoxBugReport.h287
-rw-r--r--src/VBox/Frontends/VBoxBugReport/VBoxBugReportWin.cpp786
4 files changed, 1966 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxBugReport/Makefile.kmk b/src/VBox/Frontends/VBoxBugReport/Makefile.kmk
new file mode 100644
index 00000000..df69518d
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBugReport/Makefile.kmk
@@ -0,0 +1,48 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxBugReport
+#
+# VBoxBugReport is a tool for collecting useful information on the host.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+PROGRAMS += VBoxBugReport
+VBoxBugReport_TEMPLATE = VBoxMainClientExe
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ # VBoxBugReport_DEFS = VBOX_WATCHDOG_GLOBAL_PERFCOL VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ # VBoxBugReport_DEFS = VBOX_WATCHDOG_GLOBAL_PERFCOL VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxBugReport_DEFS.win = _WIN32_WINNT=0x0501 PSAPI_VERSION=1
+VBoxBugReport_LIBS.win = psapi.lib
+VBoxBugReport_SOURCES = \
+ VBoxBugReport.cpp
+VBoxBugReport_SOURCES.win = \
+ VBoxBugReportWin.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.cpp b/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.cpp
new file mode 100644
index 00000000..5da89ed2
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.cpp
@@ -0,0 +1,845 @@
+/* $Id: VBoxBugReport.cpp $ */
+/** @file
+ * VBoxBugReport - VirtualBox command-line diagnostics tool, main file.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+#include <VBox/com/array.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/com/VirtualBox.h>
+
+#include <VBox/version.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/exception.h>
+
+#include <list>
+
+#include "VBoxBugReport.h"
+
+/* Implementation - Base */
+
+#ifndef RT_OS_WINDOWS
+/** @todo Replace with platform-specific implementations. */
+void createBugReportOsSpecific(BugReport *pReport, const char *pszHome)
+{
+ RT_NOREF(pReport, pszHome);
+}
+#endif /* !RT_OS_WINDOWS */
+
+
+/* Globals */
+
+static char *g_pszVBoxManage = NULL;
+
+static const RTGETOPTDEF g_aOptions[] =
+{
+ { "-all", 'A', RTGETOPT_REQ_NOTHING },
+ { "--all", 'A', RTGETOPT_REQ_NOTHING },
+ { "-output", 'o', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "-text", 't', RTGETOPT_REQ_NOTHING },
+ { "--text", 't', RTGETOPT_REQ_NOTHING }
+};
+
+static const char g_szUsage[] =
+ "Usage: %s [-h|-?|--help] [-A|--all|<vmname>...] [-o <file>|--output=<file>]\n"
+ " Several VM names can be specified at once to be included into single report.\n"
+ " If none is given then no machines will be included. Specifying -A overrides\n"
+ " any VM names provided and includes all registered machines.\n"
+ "Options:\n"
+ " -h, -help, --help Print usage information\n"
+ " -A, -all, --all Include all registered machines\n"
+ " -o, -output, --output Specifies the name of the output file\n"
+ " -t, -text, --text Produce a single text file instead of compressed TAR\n"
+ " -V, -version, --version Print version information\n"
+ "\n";
+
+
+/*
+ * This class stores machine-specific file paths that are obtained via
+ * VirtualBox API. In case API is not functioning properly these paths
+ * will be deduced on the best effort basis.
+ */
+class MachineInfo
+{
+public:
+ MachineInfo(const char *name, const char *logFolder, const char *settingsFile);
+ ~MachineInfo();
+ const char *getName() const { return m_name; };
+ const char *getLogPath() const { return m_logpath; };
+ const char *getSettingsFile() const { return m_settings; };
+private:
+ char *m_name;
+ char *m_logpath;
+ char *m_settings;
+};
+
+MachineInfo::MachineInfo(const char *name, const char *logFolder, const char *settingsFile)
+{
+ m_name = RTStrDup(name);
+ m_logpath = RTStrDup(logFolder);
+ m_settings = RTStrDup(settingsFile);
+}
+
+MachineInfo::~MachineInfo()
+{
+ RTStrFree(m_logpath);
+ RTStrFree(m_name);
+ RTStrFree(m_settings);
+ m_logpath = m_name = m_settings = 0;
+}
+
+typedef std::list<MachineInfo*> MachineInfoList;
+
+
+class VBRDir
+{
+public:
+ VBRDir(const char *pcszPath) : m_hDir(NIL_RTDIR)
+ {
+ int rc = RTDirOpenFiltered(&m_hDir, pcszPath, RTDIRFILTER_WINNT, 0);
+ if (RT_FAILURE(rc) && rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
+ throw RTCError(com::Utf8StrFmt("Failed to open directory '%s'\n", pcszPath));
+ };
+ ~VBRDir()
+ {
+ if (RT_VALID_PTR(m_hDir))
+ {
+ int rc = RTDirClose(m_hDir);
+ AssertRC(rc);
+ }
+ };
+ const char *next(void)
+ {
+ if (!RT_VALID_PTR(m_hDir))
+ return NULL;
+
+ int rc = RTDirRead(m_hDir, &m_DirEntry, NULL);
+ if (RT_SUCCESS(rc))
+ return m_DirEntry.szName;
+ if (rc == VERR_NO_MORE_FILES)
+ return NULL;
+ throw RTCError("Failed to read directory element\n");
+ };
+
+private:
+ RTDIR m_hDir;
+ RTDIRENTRY m_DirEntry;
+};
+
+
+BugReportFilter::BugReportFilter() : m_pvBuffer(0), m_cbBuffer(0)
+{
+}
+
+BugReportFilter::~BugReportFilter()
+{
+ if (m_pvBuffer)
+ RTMemFree(m_pvBuffer);
+}
+
+void *BugReportFilter::allocateBuffer(size_t cbNeeded)
+{
+ if (m_pvBuffer)
+ {
+ if (cbNeeded > m_cbBuffer)
+ RTMemFree(m_pvBuffer);
+ else
+ return m_pvBuffer;
+ }
+ m_pvBuffer = RTMemAlloc(cbNeeded);
+ if (!m_pvBuffer)
+ throw RTCError(com::Utf8StrFmt("Failed to allocate %ld bytes\n", cbNeeded));
+ m_cbBuffer = cbNeeded;
+ return m_pvBuffer;
+}
+
+
+/*
+ * An abstract class serving as the root of the bug report item tree.
+ */
+BugReportItem::BugReportItem(const char *pszTitle)
+{
+ m_pszTitle = RTStrDup(pszTitle);
+ m_filter = 0;
+}
+
+BugReportItem::~BugReportItem()
+{
+ if (m_filter)
+ delete m_filter;
+ RTStrFree(m_pszTitle);
+}
+
+void BugReportItem::addFilter(BugReportFilter *filter)
+{
+ m_filter = filter;
+}
+
+void *BugReportItem::applyFilter(void *pvSource, size_t *pcbInOut)
+{
+ if (m_filter)
+ return m_filter->apply(pvSource, pcbInOut);
+ return pvSource;
+}
+
+const char * BugReportItem::getTitle(void)
+{
+ return m_pszTitle;
+}
+
+
+BugReport::BugReport(const char *pszFileName)
+{
+ m_pszFileName = RTStrDup(pszFileName);
+}
+
+BugReport::~BugReport()
+{
+ for (unsigned i = 0; i < m_Items.size(); ++i)
+ {
+ delete m_Items[i];
+ }
+ RTStrFree(m_pszFileName);
+}
+
+int BugReport::getItemCount(void)
+{
+ return (int)m_Items.size();
+}
+
+void BugReport::addItem(BugReportItem* item, BugReportFilter *filter)
+{
+ if (filter)
+ item->addFilter(filter);
+ if (item)
+ m_Items.append(item);
+}
+
+void BugReport::process(void)
+{
+ for (unsigned i = 0; i < m_Items.size(); ++i)
+ {
+ BugReportItem *pItem = m_Items[i];
+ RTPrintf("%3u%% - collecting %s...\n", i * 100 / m_Items.size(), pItem->getTitle());
+ processItem(pItem);
+ }
+ RTPrintf("100%% - compressing...\n\n");
+}
+
+void *BugReport::applyFilters(BugReportItem* item, void *pvSource, size_t *pcbInOut)
+{
+ return item->applyFilter(pvSource, pcbInOut);
+}
+
+
+BugReportStream::BugReportStream(const char *pszTitle) : BugReportItem(pszTitle)
+{
+ m_hVfsIos = NIL_RTVFSIOSTREAM;
+ handleRtError(RTPathTemp(m_szFileName, RTPATH_MAX),
+ "Failed to obtain path to temporary folder");
+ handleRtError(RTPathAppend(m_szFileName, RTPATH_MAX, "BugRepXXXXX.tmp"),
+ "Failed to append path");
+ handleRtError(RTFileCreateTemp(m_szFileName, 0600),
+ "Failed to create temporary file '%s'", m_szFileName);
+ handleRtError(RTVfsIoStrmOpenNormal(m_szFileName, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_szFileName);
+}
+
+BugReportStream::~BugReportStream()
+{
+ if (m_hVfsIos != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(m_hVfsIos);
+ RTFileDelete(m_szFileName);
+}
+
+int BugReportStream::printf(const char *pszFmt, ...)
+{
+ va_list va;
+ va_start(va, pszFmt);
+ int cb = RTVfsIoStrmPrintfV(m_hVfsIos, pszFmt, va);
+ va_end(va);
+ return cb;
+}
+
+int BugReportStream::putStr(const char *pszString)
+{
+ return RTVfsIoStrmPrintf(m_hVfsIos, "%s", pszString);
+}
+
+RTVFSIOSTREAM BugReportStream::getStream(void)
+{
+ RTVfsIoStrmRelease(m_hVfsIos);
+ handleRtError(RTVfsIoStrmOpenNormal(m_szFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_szFileName);
+ return m_hVfsIos;
+}
+
+
+/* Implementation - Generic */
+
+BugReportFile::BugReportFile(const char *pszPath, const char *pszShortName) : BugReportItem(pszShortName)
+{
+ m_hVfsIos = NIL_RTVFSIOSTREAM;
+ m_pszPath = RTStrDup(pszPath);
+}
+
+BugReportFile::~BugReportFile()
+{
+ if (m_hVfsIos != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(m_hVfsIos);
+ if (m_pszPath)
+ RTStrFree(m_pszPath);
+}
+
+RTVFSIOSTREAM BugReportFile::getStream(void)
+{
+ handleRtError(RTVfsIoStrmOpenNormal(m_pszPath, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_pszPath);
+ return m_hVfsIos;
+}
+
+
+BugReportCommand::BugReportCommand(const char *pszTitle, const char *pszExec, ...)
+ : BugReportItem(pszTitle), m_hVfsIos(NIL_RTVFSIOSTREAM)
+{
+ unsigned cArgs = 0;
+ m_papszArgs[cArgs++] = RTStrDup(pszExec);
+
+ const char *pszArg;
+ va_list va;
+ va_start(va, pszExec);
+ do
+ {
+ if (cArgs >= RT_ELEMENTS(m_papszArgs))
+ {
+ va_end(va);
+ throw RTCError(com::Utf8StrFmt("Too many arguments (%u > %u)\n", cArgs+1, RT_ELEMENTS(m_papszArgs)));
+ }
+ pszArg = va_arg(va, const char *);
+ m_papszArgs[cArgs++] = pszArg ? RTStrDup(pszArg) : NULL;
+ } while (pszArg);
+ va_end(va);
+}
+
+BugReportCommand::~BugReportCommand()
+{
+ if (m_hVfsIos != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(m_hVfsIos);
+ RTFileDelete(m_szFileName);
+ for (size_t i = 0; i < RT_ELEMENTS(m_papszArgs) && m_papszArgs[i]; ++i)
+ RTStrFree(m_papszArgs[i]);
+}
+
+RTVFSIOSTREAM BugReportCommand::getStream(void)
+{
+ handleRtError(RTPathTemp(m_szFileName, RTPATH_MAX),
+ "Failed to obtain path to temporary folder");
+ handleRtError(RTPathAppend(m_szFileName, RTPATH_MAX, "BugRepXXXXX.tmp"),
+ "Failed to append path");
+ handleRtError(RTFileCreateTemp(m_szFileName, 0600),
+ "Failed to create temporary file '%s'", m_szFileName);
+
+ RTHANDLE hStdOutErr;
+ hStdOutErr.enmType = RTHANDLETYPE_FILE;
+ handleRtError(RTFileOpen(&hStdOutErr.u.hFile, m_szFileName,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE),
+ "Failed to open temporary file '%s'", m_szFileName);
+
+ RTPROCESS hProcess;
+ handleRtError(RTProcCreateEx(m_papszArgs[0], m_papszArgs, RTENV_DEFAULT, 0,
+ NULL, &hStdOutErr, &hStdOutErr,
+ NULL, NULL, NULL, &hProcess),
+ "Failed to create process '%s'", m_papszArgs[0]);
+ RTPROCSTATUS status;
+ handleRtError(RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &status),
+ "Process wait failed");
+ //if (status.enmReason == RTPROCEXITREASON_NORMAL) {}
+ RTFileClose(hStdOutErr.u.hFile);
+
+ handleRtError(RTVfsIoStrmOpenNormal(m_szFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_szFileName);
+ return m_hVfsIos;
+}
+
+
+BugReportCommandTemp::BugReportCommandTemp(const char *pszTitle, const char *pszExec, ...)
+ : BugReportItem(pszTitle), m_hVfsIos(NIL_RTVFSIOSTREAM)
+{
+ handleRtError(RTPathTemp(m_szFileName, RTPATH_MAX),
+ "Failed to obtain path to temporary folder");
+ handleRtError(RTPathAppend(m_szFileName, RTPATH_MAX, "BugRepXXXXX.tmp"),
+ "Failed to append path");
+ handleRtError(RTFileCreateTemp(m_szFileName, 0600),
+ "Failed to create temporary file '%s'", m_szFileName);
+
+ unsigned cArgs = 0;
+ m_papszArgs[cArgs++] = RTStrDup(pszExec);
+
+ const char *pszArg;
+ va_list va;
+ va_start(va, pszExec);
+ do
+ {
+ if (cArgs >= RT_ELEMENTS(m_papszArgs) - 1)
+ {
+ va_end(va);
+ throw RTCError(com::Utf8StrFmt("Too many arguments (%u > %u)\n", cArgs+1, RT_ELEMENTS(m_papszArgs)));
+ }
+ pszArg = va_arg(va, const char *);
+ m_papszArgs[cArgs++] = RTStrDup(pszArg ? pszArg : m_szFileName);
+ } while (pszArg);
+ va_end(va);
+
+ m_papszArgs[cArgs++] = NULL;
+}
+
+BugReportCommandTemp::~BugReportCommandTemp()
+{
+ if (m_hVfsIos != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(m_hVfsIos);
+ RTFileDelete(m_szErrFileName);
+ RTFileDelete(m_szFileName);
+ for (size_t i = 0; i < RT_ELEMENTS(m_papszArgs) && m_papszArgs[i]; ++i)
+ RTStrFree(m_papszArgs[i]);
+}
+
+RTVFSIOSTREAM BugReportCommandTemp::getStream(void)
+{
+ handleRtError(RTPathTemp(m_szErrFileName, RTPATH_MAX),
+ "Failed to obtain path to temporary folder");
+ handleRtError(RTPathAppend(m_szErrFileName, RTPATH_MAX, "BugRepErrXXXXX.tmp"),
+ "Failed to append path");
+ handleRtError(RTFileCreateTemp(m_szErrFileName, 0600),
+ "Failed to create temporary file '%s'", m_szErrFileName);
+
+ RTHANDLE hStdOutErr;
+ hStdOutErr.enmType = RTHANDLETYPE_FILE;
+ handleRtError(RTFileOpen(&hStdOutErr.u.hFile, m_szErrFileName,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE),
+ "Failed to open temporary file '%s'", m_szErrFileName);
+
+ /* Remove the output file to prevent errors or confirmation prompts */
+ handleRtError(RTFileDelete(m_szFileName),
+ "Failed to delete temporary file '%s'", m_szFileName);
+
+ RTPROCESS hProcess;
+ handleRtError(RTProcCreateEx(m_papszArgs[0], m_papszArgs, RTENV_DEFAULT, 0,
+ NULL, &hStdOutErr, &hStdOutErr,
+ NULL, NULL, NULL, &hProcess),
+ "Failed to create process '%s'", m_papszArgs[0]);
+ RTPROCSTATUS status;
+ handleRtError(RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &status),
+ "Process wait failed");
+ RTFileClose(hStdOutErr.u.hFile);
+
+ if (status.enmReason == RTPROCEXITREASON_NORMAL && status.iStatus == 0)
+ handleRtError(RTVfsIoStrmOpenNormal(m_szFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_szFileName);
+ else
+ handleRtError(RTVfsIoStrmOpenNormal(m_szErrFileName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE, &m_hVfsIos),
+ "Failed to open '%s'", m_szErrFileName);
+ return m_hVfsIos;
+}
+
+
+BugReportText::BugReportText(const char *pszFileName) : BugReport(pszFileName)
+{
+ handleRtError(RTStrmOpen(pszFileName, "w", &m_StrmTxt),
+ "Failed to open '%s'", pszFileName);
+}
+
+BugReportText::~BugReportText()
+{
+ if (m_StrmTxt)
+ RTStrmClose(m_StrmTxt);
+}
+
+void BugReportText::processItem(BugReportItem* item)
+{
+ int cb = RTStrmPrintf(m_StrmTxt, "[ %s ] -------------------------------------------\n", item->getTitle());
+ if (!cb)
+ throw RTCError(com::Utf8StrFmt("Write failure (cb=%d)\n", cb));
+
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ try
+ {
+ hVfsIos = item->getStream();
+ }
+ catch (RTCError &e)
+ {
+ hVfsIos = NIL_RTVFSIOSTREAM;
+ RTStrmPutStr(m_StrmTxt, e.what());
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ char buf[64*1024];
+ size_t cbRead, cbWritten;
+ cbRead = cbWritten = 0;
+ while (RT_SUCCESS(rc = RTVfsIoStrmRead(hVfsIos, buf, sizeof(buf), true /*fBlocking*/, &cbRead)) && cbRead)
+ {
+ rc = RTStrmWriteEx(m_StrmTxt, applyFilters(item, buf, &cbRead), cbRead, &cbWritten);
+ if (RT_FAILURE(rc) || cbRead != cbWritten)
+ throw RTCError(com::Utf8StrFmt("Write failure (rc=%d, cbRead=%lu, cbWritten=%lu)\n",
+ rc, cbRead, cbWritten));
+ }
+ }
+
+ handleRtError(RTStrmPutCh(m_StrmTxt, '\n'), "Write failure");
+}
+
+
+BugReportTarGzip::BugReportTarGzip(const char *pszFileName)
+ : BugReport(pszFileName), m_hTarFss(NIL_RTVFSFSSTREAM)
+{
+ VfsIoStreamHandle hVfsOut;
+ handleRtError(RTVfsIoStrmOpenNormal(pszFileName, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE,
+ hVfsOut.getPtr()),
+ "Failed to create output file '%s'", pszFileName);
+ handleRtError(RTZipGzipCompressIoStream(hVfsOut.get(), 0, 6, m_hVfsGzip.getPtr()),
+ "Failed to create compressed stream for '%s'", pszFileName);
+
+ int rc = RTZipTarFsStreamToIoStream(m_hVfsGzip.get(), RTZIPTARFORMAT_DEFAULT, RTZIPTAR_C_SPARSE, &m_hTarFss);
+ handleRtError(rc, "Failed to create TAR file '%s'", m_szTarName);
+}
+
+BugReportTarGzip::~BugReportTarGzip()
+{
+ if (m_hTarFss != NIL_RTVFSFSSTREAM)
+ RTVfsFsStrmRelease(m_hTarFss);
+}
+
+void BugReportTarGzip::dumpExceptionToArchive(RTCString &strTarFile, RTCError &e)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, _1K/*cbEstimate*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWrite(hVfsFile, e.what(), RTStrNLen(e.what(), 1024), NULL /*pcbWritten*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileSeek(hVfsFile, 0 /*offSeek*/, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFile);
+ rc = RTVfsFsStrmAdd(m_hTarFss, strTarFile.c_str(), hVfsObj, 0 /*fFlags*/);
+ RTVfsObjRelease(hVfsObj);
+ }
+ }
+ RTVfsFileRelease(hVfsFile);
+ }
+ handleRtError(rc, "Failed to add exception text to TAR archive '%s'", m_szTarName);
+}
+
+void BugReportTarGzip::processItem(BugReportItem* item)
+{
+ /*
+ * @todo Our TAR implementation does not support names larger than 100 characters.
+ * We truncate the title to make sure it will fit into 100-character field of TAR header.
+ */
+ RTCString strTarFile = RTCString(item->getTitle()).substr(0, RTStrNLen(item->getTitle(), 99));
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ try
+ {
+ hVfsIos = item->getStream();
+ }
+ catch (RTCError &e)
+ {
+ hVfsIos = NIL_RTVFSIOSTREAM;
+ dumpExceptionToArchive(strTarFile, e);
+ }
+
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ RTVFSOBJ hVfsObjIos = RTVfsObjFromIoStream(hVfsIos);
+ int rc = RTVfsFsStrmAdd(m_hTarFss, strTarFile.c_str(), hVfsObjIos, 0 /*fFlags*/);
+ RTVfsObjRelease(hVfsObjIos);
+ handleRtError(rc, "Failed to add file to TAR archive '%s'", m_szTarName);
+ }
+}
+
+void BugReportTarGzip::complete(void)
+{
+ if (m_hTarFss != NIL_RTVFSFSSTREAM)
+ {
+ RTVfsFsStrmRelease(m_hTarFss);
+ m_hTarFss = NIL_RTVFSFSSTREAM;
+ }
+ handleRtError(RTVfsIoStrmFlush(m_hVfsGzip.get()), "Failed to flush output stream");
+ m_hVfsGzip.release();
+}
+
+
+/* Implementation - Main */
+
+void createBugReport(BugReport* report, const char *pszHome, MachineInfoList& machines)
+{
+ /* Collect all log files from VBoxSVC */
+ VBRDir HomeDir(PathJoin(pszHome, "VBoxSVC.log*"));
+ const char *pcszSvcLogFile = HomeDir.next();
+ while (pcszSvcLogFile)
+ {
+ report->addItem(new BugReportFile(PathJoin(pszHome, pcszSvcLogFile), pcszSvcLogFile));
+ pcszSvcLogFile = HomeDir.next();
+ }
+
+ report->addItem(new BugReportFile(PathJoin(pszHome, "VirtualBox.xml"), "VirtualBox.xml"));
+ report->addItem(new BugReportCommand("HostUsbDevices", g_pszVBoxManage, "list", "usbhost", NULL));
+ report->addItem(new BugReportCommand("HostUsbFilters", g_pszVBoxManage, "list", "usbfilters", NULL));
+
+ for (MachineInfoList::iterator it = machines.begin(); it != machines.end(); ++it)
+ {
+ static const char * const s_apszLogFilePatterns[] = { "VBox.log*", "VBoxHardening.log" };
+ for (size_t iPat = 0; iPat < RT_ELEMENTS(s_apszLogFilePatterns); iPat++)
+ {
+ VBRDir VmLogFiles(PathJoin((*it)->getLogPath(), s_apszLogFilePatterns[iPat]));
+ const char *pcszVmLogFile = VmLogFiles.next();
+ while (pcszVmLogFile)
+ {
+ report->addItem(new BugReportFile(PathJoin((*it)->getLogPath(), pcszVmLogFile),
+ PathJoin((*it)->getName(), pcszVmLogFile)));
+ pcszVmLogFile = VmLogFiles.next();
+ }
+ }
+ report->addItem(new BugReportFile((*it)->getSettingsFile(),
+ PathJoin((*it)->getName(), RTPathFilename((*it)->getSettingsFile()))));
+ report->addItem(new BugReportCommand(PathJoin((*it)->getName(), "GuestProperties"),
+ g_pszVBoxManage, "guestproperty", "enumerate",
+ (*it)->getName(), NULL));
+ }
+
+ createBugReportOsSpecific(report, pszHome);
+}
+
+void addMachine(MachineInfoList& list, ComPtr<IMachine> machine)
+{
+ BOOL fAccessible = FALSE;
+ HRESULT hrc = machine->COMGETTER(Accessible)(&fAccessible);
+ if (SUCCEEDED(hrc) && !fAccessible)
+ return
+ handleComError(hrc, "Failed to get accessible status of VM");
+
+ com::Bstr name, logFolder, settingsFile;
+ handleComError(machine->COMGETTER(Name)(name.asOutParam()),
+ "Failed to get VM name");
+ handleComError(machine->COMGETTER(LogFolder)(logFolder.asOutParam()),
+ "Failed to get VM log folder");
+ handleComError(machine->COMGETTER(SettingsFilePath)(settingsFile.asOutParam()),
+ "Failed to get VM settings file path");
+ list.push_back(new MachineInfo(com::Utf8Str(name).c_str(),
+ com::Utf8Str(logFolder).c_str(),
+ com::Utf8Str(settingsFile).c_str()));
+}
+
+
+static void printHeader(void)
+{
+ RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " Bug Report Tool " VBOX_VERSION_STRING "\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Initialize the VBox runtime without loading
+ * the support driver.
+ */
+ RTR3InitExe(argc, &argv, 0);
+
+ bool fAllMachines = false;
+ bool fTextOutput = false;
+ const char *pszOutputFile = NULL;
+ std::list<const char *> nameList;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int ret = RTGetOptInit(&GetState, argc, argv,
+ g_aOptions, RT_ELEMENTS(g_aOptions),
+ 1 /* First */, 0 /*fFlags*/);
+ if (RT_FAILURE(ret))
+ return ret;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch(ch)
+ {
+ case 'h':
+ printHeader();
+ RTStrmPrintf(g_pStdErr, g_szUsage, argv[0]);
+ return 0;
+ case 'A':
+ fAllMachines = true;
+ break;
+ case 'o':
+ pszOutputFile = ValueUnion.psz;
+ break;
+ case 't':
+ fTextOutput = true;
+ break;
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return 0;
+ case VINF_GETOPT_NOT_OPTION:
+ nameList.push_back(ValueUnion.psz);
+ break;
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ printHeader();
+
+ HRESULT hr = S_OK;
+ char homeDir[RTPATH_MAX];
+ com::GetVBoxUserHomeDirectory(homeDir, sizeof(homeDir));
+
+ try
+ {
+ /* Figure out the full path to VBoxManage */
+ char szVBoxBin[RTPATH_MAX];
+ if (!RTProcGetExecutablePath(szVBoxBin, sizeof(szVBoxBin)))
+ throw RTCError("RTProcGetExecutablePath failed\n");
+ RTPathStripFilename(szVBoxBin);
+ g_pszVBoxManage = RTPathJoinA(szVBoxBin, VBOXMANAGE);
+ if (!g_pszVBoxManage)
+ throw RTCError("Out of memory\n");
+
+ handleComError(com::Initialize(VBOX_COM_INIT_F_DEFAULT | VBOX_COM_INIT_F_NO_COM_PATCHING), "Failed to initialize COM");
+
+ MachineInfoList list;
+
+ do
+ {
+ ComPtr<IVirtualBoxClient> virtualBoxClient;
+ ComPtr<IVirtualBox> virtualBox;
+ ComPtr<ISession> session;
+
+ hr = virtualBoxClient.createLocalObject(CLSID_VirtualBoxClient);
+ if (SUCCEEDED(hr))
+ hr = virtualBoxClient->COMGETTER(VirtualBox)(virtualBox.asOutParam());
+ else
+ hr = virtualBox.createLocalObject(CLSID_VirtualBox);
+ if (FAILED(hr))
+ RTStrmPrintf(g_pStdErr, "WARNING: Failed to create the VirtualBox object (hr=0x%x)\n", hr);
+ else
+ {
+ hr = session.createInprocObject(CLSID_Session);
+ if (FAILED(hr))
+ RTStrmPrintf(g_pStdErr, "WARNING: Failed to create a session object (hr=0x%x)\n", hr);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (fAllMachines)
+ {
+ com::SafeIfaceArray<IMachine> machines;
+ hr = virtualBox->COMGETTER(Machines)(ComSafeArrayAsOutParam(machines));
+ if (SUCCEEDED(hr))
+ {
+ for (size_t i = 0; i < machines.size(); ++i)
+ {
+ if (machines[i])
+ addMachine(list, machines[i]);
+ }
+ }
+ }
+ else
+ {
+ for ( std::list<const char *>::iterator it = nameList.begin(); it != nameList.end(); ++it)
+ {
+ ComPtr<IMachine> machine;
+ handleComError(virtualBox->FindMachine(com::Bstr(*it).raw(), machine.asOutParam()),
+ "No such machine '%s'", *it);
+ addMachine(list, machine);
+ }
+ }
+ }
+
+ }
+ while(0);
+
+ RTTIMESPEC TimeSpec;
+ RTTIME Time;
+ RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
+ RTCStringFmt strOutFile("%04d-%02d-%02d-%02d-%02d-%02d-bugreport.%s",
+ Time.i32Year, Time.u8Month, Time.u8MonthDay,
+ Time.u8Hour, Time.u8Minute, Time.u8Second,
+ fTextOutput ? "txt" : "tgz");
+ RTCString strFallbackOutFile;
+ if (!pszOutputFile)
+ {
+ RTFILE tmp;
+ pszOutputFile = strOutFile.c_str();
+ int rc = RTFileOpen(&tmp, pszOutputFile, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
+ if (rc == VERR_ACCESS_DENIED)
+ {
+ char szUserHome[RTPATH_MAX];
+ handleRtError(RTPathUserHome(szUserHome, sizeof(szUserHome)), "Failed to obtain home directory");
+ strFallbackOutFile.printf("%s/%s", szUserHome, strOutFile.c_str());
+ pszOutputFile = strFallbackOutFile.c_str();
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ RTFileClose(tmp);
+ RTFileDelete(pszOutputFile);
+ }
+ }
+ BugReport *pReport;
+ if (fTextOutput)
+ pReport = new BugReportText(pszOutputFile);
+ else
+ pReport = new BugReportTarGzip(pszOutputFile);
+ createBugReport(pReport, homeDir, list);
+ pReport->process();
+ pReport->complete();
+ RTPrintf("Report was written to '%s'\n", pszOutputFile);
+ delete pReport;
+ }
+ catch (RTCError &e)
+ {
+ RTStrmPrintf(g_pStdErr, "ERROR: %s\n", e.what());
+ }
+
+ com::Shutdown();
+
+ if (g_pszVBoxManage)
+ RTStrFree(g_pszVBoxManage);
+
+ return SUCCEEDED(hr) ? 0 : 1;
+}
diff --git a/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.h b/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.h
new file mode 100644
index 00000000..c81aaf34
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBugReport/VBoxBugReport.h
@@ -0,0 +1,287 @@
+/* $Id: VBoxBugReport.h $ */
+/** @file
+ * VBoxBugReport - VirtualBox command-line diagnostics tool, internal header file.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_VBoxBugReport_VBoxBugReport_h
+#define VBOX_INCLUDED_SRC_VBoxBugReport_VBoxBugReport_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/*
+ * Introduction.
+ *
+ * In the most general sense a bug report is a collection of data obtained from
+ * the user's host system. It may include files common for all VMs, like the
+ * VBoxSVC.log file, as well as files related to particular machines. It may
+ * also contain the output of commands executed on the host, as well as data
+ * collected via OS APIs.
+ */
+
+/** @todo not sure if using a separate namespace would be beneficial */
+
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/list.h>
+
+#ifdef RT_OS_WINDOWS
+#define VBOXMANAGE "VBoxManage.exe"
+#else /* !RT_OS_WINDOWS */
+#define VBOXMANAGE "VBoxManage"
+#endif /* !RT_OS_WINDOWS */
+
+/* Base */
+
+DECL_INLINE_THROW(void) handleRtError(int rc, const char *pszMsgFmt, ...)
+{
+ if (RT_FAILURE(rc))
+ {
+ va_list va;
+ va_start(va, pszMsgFmt);
+ RTCString strMsg(pszMsgFmt, va);
+ va_end(va);
+ strMsg.appendPrintfNoThrow(". %Rrf\n", rc);
+ throw RTCError(strMsg);
+ }
+}
+
+DECL_INLINE_THROW(void) handleComError(HRESULT hr, const char *pszMsgFmt, ...)
+{
+ if (FAILED(hr))
+ {
+ va_list va;
+ va_start(va, pszMsgFmt);
+ RTCString strMsg(pszMsgFmt, va);
+ va_end(va);
+ strMsg.appendPrintfNoThrow(". (hr=0x%x %Rhrc)\n", hr, hr);
+ throw RTCError(strMsg);
+ }
+}
+
+/*
+ * An auxiliary class to facilitate in-place path joins.
+ */
+class PathJoin
+{
+public:
+ PathJoin(const char *folder, const char *file) { m_path = RTPathJoinA(folder, file); }
+ ~PathJoin() { RTStrFree(m_path); };
+ operator char*() const { return m_path; };
+private:
+ char *m_path;
+};
+
+
+/*
+ * An abstract class serving as the root of the bug report filter tree.
+ * A child provides an implementation of the 'apply' method. A child
+ * should modify the input buffer (provided via pvSource) in place, or
+ * allocate a new buffer via 'allocateBuffer'. Allocated buffers are
+ * released automatically when another buffer is allocated, which means
+ * that NEXT CALL TO 'APPLY' INVALIDATES BUFFERS RETURNED IN PREVIOUS
+ * CALLS!
+ */
+class BugReportFilter
+{
+public:
+ BugReportFilter();
+ virtual ~BugReportFilter();
+ virtual void *apply(void *pvSource, size_t *pcbInOut) = 0;
+protected:
+ void *allocateBuffer(size_t cbNeeded);
+private:
+ void *m_pvBuffer;
+ size_t m_cbBuffer;
+};
+
+
+/*
+ * An abstract class serving as the root of the bug report item tree.
+ */
+class BugReportItem
+{
+public:
+ BugReportItem(const char *pszTitle);
+ virtual ~BugReportItem();
+ virtual const char *getTitle(void);
+ virtual RTVFSIOSTREAM getStream(void) = 0;
+ void addFilter(BugReportFilter *filter);
+ void *applyFilter(void *pvSource, size_t *pcbInOut);
+private:
+ char *m_pszTitle;
+ BugReportFilter *m_filter;
+};
+
+/*
+ * An abstract class to serve as a base class for all report types.
+ */
+class BugReport
+{
+public:
+ BugReport(const char *pszFileName);
+ virtual ~BugReport();
+
+ void addItem(BugReportItem* item, BugReportFilter *filter = 0);
+ int getItemCount(void);
+ void process();
+ void *applyFilters(BugReportItem* item, void *pvSource, size_t *pcbInOut);
+
+ virtual void processItem(BugReportItem* item) = 0;
+ virtual void complete(void) = 0;
+
+protected:
+ char *m_pszFileName;
+ RTCList<BugReportItem*> m_Items;
+};
+
+/*
+ * An auxiliary class providing formatted output into a temporary file for item
+ * classes that obtain data via host OS APIs.
+ */
+class BugReportStream : public BugReportItem
+{
+public:
+ BugReportStream(const char *pszTitle);
+ virtual ~BugReportStream();
+ virtual RTVFSIOSTREAM getStream(void);
+protected:
+ int printf(const char *pszFmt, ...);
+ int putStr(const char *pszString);
+private:
+ RTVFSIOSTREAM m_hVfsIos;
+ char m_szFileName[RTPATH_MAX];
+};
+
+
+/* Generic */
+
+/*
+ * This class reports everything into a single text file.
+ */
+class BugReportText : public BugReport
+{
+public:
+ BugReportText(const char *pszFileName);
+ virtual ~BugReportText();
+ virtual void processItem(BugReportItem* item);
+ virtual void complete(void) {};
+private:
+ PRTSTREAM m_StrmTxt;
+};
+
+/*
+ * This class reports items as individual files archived into a single compressed TAR file.
+ */
+class BugReportTarGzip : public BugReport
+{
+public:
+ BugReportTarGzip(const char *pszFileName);
+ virtual ~BugReportTarGzip();
+ virtual void processItem(BugReportItem* item);
+ virtual void complete(void);
+private:
+ void dumpExceptionToArchive(RTCString &strTarFile, RTCError &e);
+
+ /*
+ * Helper class to release handles going out of scope.
+ */
+ class VfsIoStreamHandle
+ {
+ public:
+ VfsIoStreamHandle() : m_hVfsStream(NIL_RTVFSIOSTREAM) {};
+ ~VfsIoStreamHandle() { release(); }
+ PRTVFSIOSTREAM getPtr(void) { return &m_hVfsStream; };
+ RTVFSIOSTREAM get(void) { return m_hVfsStream; };
+ void release(void)
+ {
+ if (m_hVfsStream != NIL_RTVFSIOSTREAM)
+ RTVfsIoStrmRelease(m_hVfsStream);
+ m_hVfsStream = NIL_RTVFSIOSTREAM;
+ };
+ private:
+ RTVFSIOSTREAM m_hVfsStream;
+ };
+
+ VfsIoStreamHandle m_hVfsGzip;
+
+ RTVFSFSSTREAM m_hTarFss;
+ char m_szTarName[RTPATH_MAX];
+};
+
+
+/*
+ * BugReportFile adds a file as an item to a report.
+ */
+class BugReportFile : public BugReportItem
+{
+public:
+ BugReportFile(const char *pszPath, const char *pcszName);
+ virtual ~BugReportFile();
+ virtual RTVFSIOSTREAM getStream(void);
+
+private:
+ char *m_pszPath;
+ RTVFSIOSTREAM m_hVfsIos;
+};
+
+/*
+ * A base class for item classes that collect CLI output.
+ */
+class BugReportCommand : public BugReportItem
+{
+public:
+ BugReportCommand(const char *pszTitle, const char *pszExec, ...);
+ virtual ~BugReportCommand();
+ virtual RTVFSIOSTREAM getStream(void);
+private:
+ RTVFSIOSTREAM m_hVfsIos;
+ char m_szFileName[RTPATH_MAX];
+ char *m_papszArgs[32];
+};
+
+/*
+ * A base class for item classes that provide temp output file to a command.
+ */
+class BugReportCommandTemp : public BugReportItem
+{
+public:
+ BugReportCommandTemp(const char *pszTitle, const char *pszExec, ...);
+ virtual ~BugReportCommandTemp();
+ virtual RTVFSIOSTREAM getStream(void);
+private:
+ RTVFSIOSTREAM m_hVfsIos;
+ char m_szFileName[RTPATH_MAX];
+ char m_szErrFileName[RTPATH_MAX];
+ char *m_papszArgs[32];
+};
+
+/* Platform-specific */
+
+void createBugReportOsSpecific(BugReport* report, const char *pszHome);
+
+#endif /* !VBOX_INCLUDED_SRC_VBoxBugReport_VBoxBugReport_h */
diff --git a/src/VBox/Frontends/VBoxBugReport/VBoxBugReportWin.cpp b/src/VBox/Frontends/VBoxBugReport/VBoxBugReportWin.cpp
new file mode 100644
index 00000000..28a26794
--- /dev/null
+++ b/src/VBox/Frontends/VBoxBugReport/VBoxBugReportWin.cpp
@@ -0,0 +1,786 @@
+/* $Id: VBoxBugReportWin.cpp $ */
+/** @file
+ * VBoxBugReportWin - VirtualBox command-line diagnostics tool, Windows-specific part.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/com/com.h>
+#include <VBox/com/string.h>
+
+#include <iprt/cpp/exception.h>
+
+#include "VBoxBugReport.h"
+
+#include <netcfgx.h>
+#include <iprt/win/setupapi.h>
+#include <initguid.h>
+#include <devguid.h>
+#include <usbiodef.h>
+#include <usbioctl.h>
+#include <psapi.h>
+
+#define ReleaseAndReset(obj) \
+ if (obj) \
+ obj->Release(); \
+ obj = NULL;
+
+
+class BugReportNetworkAdaptersWin : public BugReportStream
+{
+public:
+ BugReportNetworkAdaptersWin() : BugReportStream("NetworkAdapters") {};
+ virtual ~BugReportNetworkAdaptersWin() {};
+ virtual RTVFSIOSTREAM getStream(void) { collect(); return BugReportStream::getStream(); };
+private:
+ struct CharacteristicsName
+ {
+ DWORD dwChar;
+ const char *szName;
+ };
+ void printCharteristics(DWORD dwChars);
+ void collect();
+ void collectNetCfgComponentInfo(int ident, bool fEnabled, INetCfgComponent *pComponent);
+};
+
+
+
+void BugReportNetworkAdaptersWin::printCharteristics(DWORD dwChars)
+{
+ static CharacteristicsName cMap[] =
+ {
+ { NCF_VIRTUAL, "virtual" },
+ { NCF_SOFTWARE_ENUMERATED, "software_enumerated" },
+ { NCF_PHYSICAL, "physical" },
+ { NCF_HIDDEN, "hidden" },
+ { NCF_NO_SERVICE, "no_service" },
+ { NCF_NOT_USER_REMOVABLE, "not_user_removable" },
+ { NCF_MULTIPORT_INSTANCED_ADAPTER, "multiport_instanced_adapter" },
+ { NCF_HAS_UI, "has_ui" },
+ { NCF_SINGLE_INSTANCE, "single_instance" },
+ { NCF_FILTER, "filter" },
+ { NCF_DONTEXPOSELOWER, "dontexposelower" },
+ { NCF_HIDE_BINDING, "hide_binding" },
+ { NCF_NDIS_PROTOCOL, "ndis_protocol" },
+ { NCF_FIXED_BINDING, "fixed_binding" },
+ { NCF_LW_FILTER, "lw_filter" }
+ };
+ bool fPrintDelim = false;
+
+ for (int i = 0; i < RT_ELEMENTS(cMap); ++i)
+ {
+ if (dwChars & cMap[i].dwChar)
+ {
+ if (fPrintDelim)
+ {
+ putStr(", ");
+ fPrintDelim = false;
+ }
+ putStr(cMap[i].szName);
+ fPrintDelim = true;
+ }
+ }
+}
+
+void BugReportNetworkAdaptersWin::collectNetCfgComponentInfo(int ident, bool fEnabled, INetCfgComponent *pComponent)
+{
+ LPWSTR pwszName = NULL;
+ HRESULT hr = pComponent->GetDisplayName(&pwszName);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to get component display name, hr=0x%x.\n", hr));
+ printf("%s%c %ls [", RTCString(ident, ' ').c_str(), fEnabled ? '+' : '-', pwszName);
+ if (pwszName)
+ CoTaskMemFree(pwszName);
+
+ DWORD dwChars = 0;
+ hr = pComponent->GetCharacteristics(&dwChars);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to get component characteristics, hr=0x%x.\n", hr));
+ printCharteristics(dwChars);
+ putStr("]\n");
+}
+
+void BugReportNetworkAdaptersWin::collect(void)
+{
+ INetCfg *pNetCfg = NULL;
+ IEnumNetCfgComponent *pEnumAdapters = NULL;
+ INetCfgComponent *pNetCfgAdapter = NULL;
+ INetCfgComponentBindings *pAdapterBindings = NULL;
+ IEnumNetCfgBindingPath *pEnumBp = NULL;
+ INetCfgBindingPath *pBp = NULL;
+ IEnumNetCfgBindingInterface *pEnumBi = NULL;
+ INetCfgBindingInterface *pBi = NULL;
+ INetCfgComponent *pUpperComponent = NULL;
+
+ try
+ {
+ HRESULT hr = CoCreateInstance(CLSID_CNetCfg, NULL, CLSCTX_INPROC_SERVER, IID_INetCfg, (PVOID*)&pNetCfg);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to create instance of INetCfg, hr=0x%x.\n", hr));
+ hr = pNetCfg->Initialize(NULL);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to initialize instance of INetCfg, hr=0x%x.\n", hr));
+
+ hr = pNetCfg->EnumComponents(&GUID_DEVCLASS_NET, &pEnumAdapters);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed enumerate network adapters, hr=0x%x.\n", hr));
+
+ hr = pEnumAdapters->Reset();
+ Assert(SUCCEEDED(hr));
+ do
+ {
+ hr = pEnumAdapters->Next(1, &pNetCfgAdapter, NULL);
+ if (hr == S_FALSE)
+ break;
+ if (hr != S_OK)
+ throw RTCError(com::Utf8StrFmt("Failed to get next network adapter, hr=0x%x.\n", hr));
+ hr = pNetCfgAdapter->QueryInterface(IID_INetCfgComponentBindings, (PVOID*)&pAdapterBindings);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to query INetCfgComponentBindings, hr=0x%x.\n", hr));
+ hr = pAdapterBindings->EnumBindingPaths(EBP_ABOVE, &pEnumBp);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to enumerate binding paths, hr=0x%x.\n", hr));
+ hr = pEnumBp->Reset();
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to reset enumeration of binding paths (0x%x)\n", hr));
+ do
+ {
+ hr = pEnumBp->Next(1, &pBp, NULL);
+ if (hr == S_FALSE)
+ break;
+ if (hr != S_OK)
+ throw RTCError(com::Utf8StrFmt("Failed to get next network adapter, hr=0x%x.\n", hr));
+ bool fBpEnabled;
+ hr = pBp->IsEnabled();
+ if (hr == S_FALSE)
+ fBpEnabled = false;
+ else if (hr != S_OK)
+ throw RTCError(com::Utf8StrFmt("Failed to check if bind path is enabled, hr=0x%x.\n", hr));
+ else
+ fBpEnabled = true;
+ hr = pBp->EnumBindingInterfaces(&pEnumBi);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to enumerate binding interfaces (0x%x)\n", hr));
+ hr = pEnumBi->Reset();
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to reset enumeration of binding interfaces (0x%x)\n", hr));
+ int ident;
+ for (ident = 0;; ++ident)
+ {
+ hr = pEnumBi->Next(1, &pBi, NULL);
+ if (hr == S_FALSE)
+ break;
+ if (hr != S_OK)
+ throw RTCError(com::Utf8StrFmt("Failed to get next binding interface, hr=0x%x.\n", hr));
+ hr = pBi->GetUpperComponent(&pUpperComponent);
+ if (FAILED(hr))
+ throw RTCError(com::Utf8StrFmt("Failed to get upper component, hr=0x%x.\n", hr));
+ collectNetCfgComponentInfo(ident, fBpEnabled, pUpperComponent);
+ ReleaseAndReset(pUpperComponent);
+ ReleaseAndReset(pBi);
+ }
+ collectNetCfgComponentInfo(ident, fBpEnabled, pNetCfgAdapter);
+ ReleaseAndReset(pEnumBi);
+ ReleaseAndReset(pBp);
+ } while (true);
+
+ ReleaseAndReset(pEnumBp);
+ ReleaseAndReset(pAdapterBindings);
+ ReleaseAndReset(pNetCfgAdapter);
+ } while (true);
+ ReleaseAndReset(pEnumAdapters);
+ ReleaseAndReset(pNetCfg);
+ }
+
+ catch (RTCError &e)
+ {
+ ReleaseAndReset(pUpperComponent);
+ ReleaseAndReset(pBi);
+ ReleaseAndReset(pEnumBi);
+ ReleaseAndReset(pBp);
+ ReleaseAndReset(pEnumBp);
+ ReleaseAndReset(pAdapterBindings);
+ ReleaseAndReset(pNetCfgAdapter);
+ ReleaseAndReset(pEnumAdapters);
+ ReleaseAndReset(pNetCfg);
+ RTPrintf("ERROR in osCollect: %s\n", e.what());
+ throw;
+ }
+
+}
+
+
+class ErrorHandler
+{
+public:
+ ErrorHandler(const char *pszFunction, int iLine)
+ : m_function(pszFunction), m_line(iLine)
+ { }
+
+ void handleWinError(DWORD uError, const char *pszMsgFmt, ...)
+ {
+ if (uError != ERROR_SUCCESS)
+ {
+ va_list va;
+ va_start(va, pszMsgFmt);
+ RTCString msgArgs(pszMsgFmt, va);
+ va_end(va);
+
+ LPSTR pBuf = NULL;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, uError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&pBuf, 0, NULL);
+ RTCStringFmt msg("%s at %s(%d): err=%u %s", msgArgs.c_str(), m_function, m_line, uError, pBuf);
+ LocalFree(pBuf);
+ throw RTCError(msg.c_str());
+ }
+ }
+
+private:
+ const char *m_function;
+ int m_line;
+};
+#define handleWinError ErrorHandler(__FUNCTION__, __LINE__).handleWinError
+
+
+class BugReportUsbTreeWin : public BugReportStream
+{
+public:
+ BugReportUsbTreeWin();
+ virtual ~BugReportUsbTreeWin();
+ virtual RTVFSIOSTREAM getStream(void) { enumerate(); return BugReportStream::getStream(); }
+private:
+ class AutoHandle {
+ public:
+ AutoHandle(HANDLE h) { m_h = h; }
+ ~AutoHandle() { close(); }
+ bool isValid() { return m_h != INVALID_HANDLE_VALUE; }
+ operator HANDLE() { return m_h; }
+ void close(void) { if (isValid()) { CloseHandle(m_h); m_h = INVALID_HANDLE_VALUE; } }
+ private:
+ HANDLE m_h;
+ };
+ void enumerate();
+
+ void enumerateController(PSP_DEVINFO_DATA pInfoData, PSP_DEVICE_INTERFACE_DATA pInterfaceData);
+ void enumerateHub(RTCString strFullName, RTCString strPrefix);
+ void enumeratePorts(HANDLE hHub, unsigned cPorts, RTCString strPrefix);
+ PBYTE getDeviceRegistryProperty(HDEVINFO hDev, PSP_DEVINFO_DATA pInfoData, DWORD uProperty,
+ DWORD uExpectedType, PDWORD puSize);
+ RTCString getDeviceRegistryPropertyString(HDEVINFO hDev, PSP_DEVINFO_DATA pInfoData, DWORD uProperty);
+
+ RTCString getDeviceDescByDriverName(RTCString strDrvName);
+ RTCString getDriverKeyName(HANDLE hHub, int iPort);
+ RTCString getExternalHubName(HANDLE hHub, int iPort);
+
+ HDEVINFO m_hDevInfo;
+ PSP_DEVICE_INTERFACE_DETAIL_DATA m_pDetailData;
+ HANDLE m_hHostCtrlDev;
+};
+
+BugReportUsbTreeWin::BugReportUsbTreeWin() : BugReportStream("HostUsbTree")
+{
+ m_hDevInfo = INVALID_HANDLE_VALUE;
+ m_pDetailData = NULL;
+ m_hHostCtrlDev = INVALID_HANDLE_VALUE;
+}
+
+BugReportUsbTreeWin::~BugReportUsbTreeWin()
+{
+ if (m_hHostCtrlDev != INVALID_HANDLE_VALUE)
+ CloseHandle(m_hHostCtrlDev);
+ if (m_pDetailData)
+ RTMemFree(m_pDetailData);
+ if (m_hDevInfo != INVALID_HANDLE_VALUE)
+ SetupDiDestroyDeviceInfoList(m_hDevInfo);
+}
+
+
+PBYTE BugReportUsbTreeWin::getDeviceRegistryProperty(HDEVINFO hDev,
+ PSP_DEVINFO_DATA pInfoData,
+ DWORD uProperty,
+ DWORD uExpectedType,
+ PDWORD puSize)
+{
+ DWORD uActualType, cbNeeded = 0;
+ if (!SetupDiGetDeviceRegistryProperty(hDev, pInfoData, uProperty, &uActualType,
+ NULL, 0, &cbNeeded)
+ && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ if (GetLastError() == ERROR_INVALID_DATA)
+ return NULL;
+ handleWinError(GetLastError(), "SetupDiGetDeviceRegistryProperty(0x%x) failed", uProperty);
+ }
+ if (uExpectedType != REG_NONE && uActualType != uExpectedType)
+ throw RTCError(RTCStringFmt("SetupDiGetDeviceRegistryProperty(0x%x) returned type %d instead of %d",
+ uActualType, uExpectedType).c_str());
+ PBYTE pBuffer = (PBYTE)RTMemAlloc(cbNeeded);
+ if (!pBuffer)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbNeeded).c_str());
+ if (!SetupDiGetDeviceRegistryProperty(hDev, pInfoData, uProperty, NULL,
+ pBuffer, cbNeeded, &cbNeeded))
+ {
+ DWORD dwErr = GetLastError();
+ RTMemFree(pBuffer);
+ pBuffer = NULL;
+ handleWinError(dwErr, "SetupDiGetDeviceRegistryProperty(0x%x) failed", uProperty);
+ }
+ if (puSize)
+ *puSize = cbNeeded;
+
+ return pBuffer;
+}
+
+RTCString BugReportUsbTreeWin::getDeviceRegistryPropertyString(HDEVINFO hDev, PSP_DEVINFO_DATA pInfoData, DWORD uProperty)
+{
+ PWSTR pUnicodeString = (PWSTR)getDeviceRegistryProperty(hDev, pInfoData, uProperty, REG_SZ, NULL);
+
+ if (!pUnicodeString)
+ return RTCString();
+
+ RTCStringFmt utf8string("%ls", pUnicodeString);
+ RTMemFree(pUnicodeString);
+ return utf8string;
+}
+
+
+RTCString BugReportUsbTreeWin::getDeviceDescByDriverName(RTCString strDrvName)
+{
+ DWORD dwErr;
+ SP_DEVINFO_DATA devInfoData;
+ HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
+
+ if (hDevInfo == INVALID_HANDLE_VALUE)
+ handleWinError(GetLastError(), "SetupDiGetClassDevs failed");
+
+ bool fFound = false;
+ devInfoData.cbSize = sizeof(devInfoData);
+ for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); ++i)
+ {
+ if (getDeviceRegistryPropertyString(hDevInfo, &devInfoData, SPDRP_DRIVER).equals(strDrvName))
+ {
+ fFound = true;
+ break;
+ }
+ }
+ if (!fFound)
+ {
+ dwErr = GetLastError();
+ SetupDiDestroyDeviceInfoList(hDevInfo);
+ handleWinError(dwErr, "SetupDiEnumDeviceInfo failed");
+ }
+
+ RTCString strDesc = getDeviceRegistryPropertyString(hDevInfo, &devInfoData, SPDRP_DEVICEDESC);
+ SetupDiDestroyDeviceInfoList(hDevInfo);
+ return strDesc;
+}
+
+
+RTCString BugReportUsbTreeWin::getDriverKeyName(HANDLE hHub, int iPort)
+{
+ USB_NODE_CONNECTION_DRIVERKEY_NAME name;
+ ULONG cbNeeded = 0;
+
+ name.ConnectionIndex = iPort;
+ if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
+ &name, sizeof(name), &name, sizeof(name), &cbNeeded, NULL))
+ handleWinError(GetLastError(), "DeviceIoControl(IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME) failed");
+ cbNeeded = name.ActualLength;
+ PUSB_NODE_CONNECTION_DRIVERKEY_NAME pName = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)RTMemAlloc(cbNeeded);
+ if (!pName)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbNeeded).c_str());
+ pName->ConnectionIndex = iPort;
+ if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
+ pName, cbNeeded, pName, cbNeeded, &cbNeeded, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ RTMemFree(pName);
+ handleWinError(dwErr, "DeviceIoControl(IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME) failed");
+ }
+ RTCStringFmt strName("%ls", pName->DriverKeyName);
+ RTMemFree(pName);
+ return strName;
+}
+
+
+RTCString BugReportUsbTreeWin::getExternalHubName(HANDLE hHub, int iPort)
+{
+ USB_NODE_CONNECTION_NAME name;
+ ULONG cbNeeded = 0;
+
+ name.ConnectionIndex = iPort;
+ if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME,
+ &name, sizeof(name), &name, sizeof(name), &cbNeeded, NULL))
+ handleWinError(GetLastError(), "DeviceIoControl(IOCTL_USB_GET_NODE_CONNECTION_NAME) failed");
+ cbNeeded = name.ActualLength;
+ PUSB_NODE_CONNECTION_NAME pName = (PUSB_NODE_CONNECTION_NAME)RTMemAlloc(cbNeeded);
+ if (!pName)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbNeeded).c_str());
+ pName->ConnectionIndex = iPort;
+ if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME,
+ pName, cbNeeded, pName, cbNeeded, &cbNeeded, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ RTMemFree(pName);
+ handleWinError(dwErr, "DeviceIoControl(IOCTL_USB_GET_NODE_CONNECTION_NAME) failed");
+ }
+ RTCStringFmt strName("%ls", pName->NodeName);
+ RTMemFree(pName);
+ return strName;
+}
+
+
+void BugReportUsbTreeWin::enumeratePorts(HANDLE hHub, unsigned cPorts, RTCString strPrefix)
+{
+ DWORD cbInfo = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + 30 * sizeof(USB_PIPE_INFO);
+ PUSB_NODE_CONNECTION_INFORMATION_EX pInfo = (PUSB_NODE_CONNECTION_INFORMATION_EX)RTMemAlloc(cbInfo);
+ if (!pInfo)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbInfo).c_str());
+ for (unsigned i = 1; i <= cPorts; ++i)
+ {
+ pInfo->ConnectionIndex = i;
+ if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
+ pInfo, cbInfo, pInfo, cbInfo, &cbInfo, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ RTMemFree(pInfo);
+ handleWinError(dwErr, "DeviceIoControl(IOCTL_USB_GET_NODE_CONNECTION_INFORMATION) failed");
+ }
+ if (pInfo->ConnectionStatus == NoDeviceConnected)
+ printf("%s[Port %d]\n", strPrefix.c_str(), i);
+ else
+ {
+ RTCString strName = getDeviceDescByDriverName(getDriverKeyName(hHub, i));
+ printf("%s[Port %d] %s\n", strPrefix.c_str(), i, strName.c_str());
+ if (pInfo->DeviceIsHub)
+ enumerateHub(getExternalHubName(hHub, i), strPrefix + " ");
+ }
+ }
+ RTMemFree(pInfo);
+}
+
+void BugReportUsbTreeWin::enumerateHub(RTCString strFullName, RTCString strPrefix)
+{
+ AutoHandle hHubDev(CreateFileA(RTCString("\\\\.\\").append(strFullName).c_str(),
+ GENERIC_WRITE, FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL));
+ if (!hHubDev.isValid())
+ handleWinError(GetLastError(), "CreateFile(%s) failed", strFullName.c_str());
+ ULONG cb;
+ USB_NODE_INFORMATION hubInfo;
+ if (!DeviceIoControl(hHubDev,
+ IOCTL_USB_GET_NODE_INFORMATION,
+ &hubInfo,
+ sizeof(USB_NODE_INFORMATION),
+ &hubInfo,
+ sizeof(USB_NODE_INFORMATION),
+ &cb,
+ NULL))
+ handleWinError(GetLastError(), "DeviceIoControl(IOCTL_USB_GET_NODE_INFORMATION) failed");
+ enumeratePorts(hHubDev, hubInfo.u.HubInformation.HubDescriptor.bNumberOfPorts, strPrefix);
+}
+
+void BugReportUsbTreeWin::enumerateController(PSP_DEVINFO_DATA pInfoData, PSP_DEVICE_INTERFACE_DATA pInterfaceData)
+{
+ RT_NOREF(pInterfaceData);
+ RTCString strCtrlDesc = getDeviceRegistryPropertyString(m_hDevInfo, pInfoData, SPDRP_DEVICEDESC);
+ printf("%s\n", strCtrlDesc.c_str());
+
+ ULONG cbNeeded;
+ USB_ROOT_HUB_NAME rootHub;
+ /* Find out the name length first */
+ if (!DeviceIoControl(m_hHostCtrlDev, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0,
+ &rootHub, sizeof(rootHub),
+ &cbNeeded, NULL))
+ handleWinError(GetLastError(), "DeviceIoControl(IOCTL_USB_GET_ROOT_HUB_NAME) failed");
+ cbNeeded = rootHub.ActualLength;
+ PUSB_ROOT_HUB_NAME pUnicodeName = (PUSB_ROOT_HUB_NAME)RTMemAlloc(cbNeeded);
+ if (!pUnicodeName)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbNeeded).c_str());
+
+ if (!DeviceIoControl(m_hHostCtrlDev, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0,
+ pUnicodeName, cbNeeded,
+ &cbNeeded, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ RTMemFree(pUnicodeName);
+ handleWinError(dwErr, "DeviceIoControl(IOCTL_USB_GET_ROOT_HUB_NAME) failed");
+ }
+
+ RTCStringFmt strRootHubName("%ls", pUnicodeName->RootHubName);
+ RTMemFree(pUnicodeName);
+ printf(" Root Hub\n");
+ enumerateHub(strRootHubName, " ");
+}
+
+void BugReportUsbTreeWin::enumerate()
+{
+ m_hDevInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, NULL, NULL,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (m_hDevInfo == INVALID_HANDLE_VALUE)
+ handleWinError(GetLastError(), "SetupDiGetClassDevs(GUID_DEVINTERFACE_USB_HOST_CONTROLLER) failed");
+
+ SP_DEVINFO_DATA deviceInfoData;
+ deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
+ for (int i = 0; SetupDiEnumDeviceInfo(m_hDevInfo, i, &deviceInfoData); ++i)
+ {
+ if (m_hHostCtrlDev != INVALID_HANDLE_VALUE)
+ CloseHandle(m_hHostCtrlDev);
+ if (m_pDetailData)
+ RTMemFree(m_pDetailData);
+
+ SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
+ deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+ if (!SetupDiEnumDeviceInterfaces(m_hDevInfo, 0, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
+ i, &deviceInterfaceData))
+ handleWinError(GetLastError(), "SetupDiEnumDeviceInterfaces(GUID_DEVINTERFACE_USB_HOST_CONTROLLER) failed");
+
+ ULONG cbNeeded = 0;
+ if (!SetupDiGetDeviceInterfaceDetail(m_hDevInfo, &deviceInterfaceData, NULL, 0, &cbNeeded, NULL)
+ && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ handleWinError(GetLastError(), "SetupDiGetDeviceInterfaceDetail failed");
+
+ m_pDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAlloc(cbNeeded);
+ if (!m_pDetailData)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", cbNeeded).c_str());
+
+ m_pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+ if (!SetupDiGetDeviceInterfaceDetail(m_hDevInfo, &deviceInterfaceData, m_pDetailData, cbNeeded, &cbNeeded, NULL))
+ handleWinError(GetLastError(), "SetupDiGetDeviceInterfaceDetail failed");
+
+ m_hHostCtrlDev = CreateFile(m_pDetailData->DevicePath, GENERIC_WRITE, FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (m_hHostCtrlDev == INVALID_HANDLE_VALUE)
+ handleWinError(GetLastError(), "CreateFile(%ls) failed", m_pDetailData);
+
+ enumerateController(&deviceInfoData, &deviceInterfaceData);
+ }
+}
+
+class BugReportDriversWin : public BugReportStream
+{
+public:
+ BugReportDriversWin();
+ virtual ~BugReportDriversWin();
+ virtual RTVFSIOSTREAM getStream(void) { enumerateDrivers(); return BugReportStream::getStream(); }
+private:
+ void enumerateDrivers(void);
+
+ WCHAR *m_pwszSystemRoot;
+ UINT m_cSystemRoot;
+ LPVOID *m_pDrivers;
+ DWORD m_cDrivers;
+ LPVOID m_pVerInfo;
+ DWORD m_cbVerInfo;
+};
+
+BugReportDriversWin::BugReportDriversWin() : BugReportStream("DriverVersions")
+{
+ m_cSystemRoot = MAX_PATH;
+ m_pwszSystemRoot = new WCHAR[MAX_PATH];
+ m_cDrivers = 1024;
+ m_pDrivers = new LPVOID[m_cDrivers];
+ m_pVerInfo = NULL;
+ m_cbVerInfo = 0;
+}
+
+BugReportDriversWin::~BugReportDriversWin()
+{
+ if (m_pVerInfo)
+ RTMemTmpFree(m_pVerInfo);
+ delete[] m_pDrivers;
+ delete[] m_pwszSystemRoot;
+}
+
+void BugReportDriversWin::enumerateDrivers()
+{
+ UINT cNeeded = GetWindowsDirectory(m_pwszSystemRoot, m_cSystemRoot);
+ if (cNeeded > m_cSystemRoot)
+ {
+ /* Re-allocate and try again */
+ m_cSystemRoot = cNeeded;
+ delete[] m_pwszSystemRoot;
+ m_pwszSystemRoot = new WCHAR[m_cSystemRoot];
+ cNeeded = GetWindowsDirectory(m_pwszSystemRoot, m_cSystemRoot);
+ }
+ if (cNeeded == 0)
+ handleWinError(GetLastError(), "GetWindowsDirectory failed");
+
+ DWORD cbNeeded = 0;
+ if ( !EnumDeviceDrivers(m_pDrivers, m_cDrivers * sizeof(m_pDrivers[0]), &cbNeeded)
+ || cbNeeded > m_cDrivers * sizeof(m_pDrivers[0]))
+ {
+ /* Re-allocate and try again */
+ m_cDrivers = cbNeeded / sizeof(m_pDrivers[0]);
+ delete[] m_pDrivers;
+ m_pDrivers = new LPVOID[m_cDrivers];
+ if (!EnumDeviceDrivers(m_pDrivers, cbNeeded, &cbNeeded))
+ handleWinError(GetLastError(), "EnumDeviceDrivers failed (%p, %u)", m_pDrivers, cbNeeded);
+ }
+
+ WCHAR wszDriver[1024];
+ for (unsigned i = 0; i < m_cDrivers; i++)
+ {
+ if (GetDeviceDriverBaseName(m_pDrivers[i], wszDriver, RT_ELEMENTS(wszDriver)))
+ {
+ if (_wcsnicmp(L"vbox", wszDriver, 4))
+ continue;
+ }
+ else
+ continue;
+ if (GetDeviceDriverFileName(m_pDrivers[i], wszDriver, RT_ELEMENTS(wszDriver)))
+ {
+ WCHAR wszTmpDrv[1024];
+ WCHAR *pwszDrv = wszDriver;
+ if (!wcsncmp(L"\\SystemRoot", wszDriver, 11))
+ {
+ wcsncpy_s(wszTmpDrv, m_pwszSystemRoot, m_cSystemRoot);
+ wcsncat_s(wszTmpDrv, wszDriver + 11, RT_ELEMENTS(wszTmpDrv) - m_cSystemRoot);
+ pwszDrv = wszTmpDrv;
+ }
+ else if (!wcsncmp(L"\\??\\", wszDriver, 4))
+ pwszDrv = wszDriver + 4;
+
+
+ /* Allocate a buffer for version info. Reuse if large enough. */
+ DWORD cbNewVerInfo = GetFileVersionInfoSize(pwszDrv, NULL);
+ if (cbNewVerInfo > m_cbVerInfo)
+ {
+ if (m_pVerInfo)
+ RTMemTmpFree(m_pVerInfo);
+ m_cbVerInfo = cbNewVerInfo;
+ m_pVerInfo = RTMemTmpAlloc(m_cbVerInfo);
+ if (!m_pVerInfo)
+ throw RTCError(RTCStringFmt("Failed to allocate %u bytes", m_cbVerInfo).c_str());
+ }
+
+ if (GetFileVersionInfo(pwszDrv, NULL, m_cbVerInfo, m_pVerInfo))
+ {
+ UINT cbSize = 0;
+ LPBYTE lpBuffer = NULL;
+ if (VerQueryValue(m_pVerInfo, L"\\", (VOID FAR* FAR*)&lpBuffer, &cbSize))
+ {
+ if (cbSize)
+ {
+ VS_FIXEDFILEINFO *pFileInfo = (VS_FIXEDFILEINFO *)lpBuffer;
+ if (pFileInfo->dwSignature == 0xfeef04bd)
+ {
+ printf("%ls (Version: %d.%d.%d.%d)\n", pwszDrv,
+ (pFileInfo->dwFileVersionMS >> 16) & 0xffff,
+ (pFileInfo->dwFileVersionMS >> 0) & 0xffff,
+ (pFileInfo->dwFileVersionLS >> 16) & 0xffff,
+ (pFileInfo->dwFileVersionLS >> 0) & 0xffff);
+ }
+ else
+ printf("%ls - invalid signature\n", pwszDrv);
+ }
+ else
+ printf("%ls - version info size is 0\n", pwszDrv);
+ }
+ else
+ printf("%ls - failed to query version info size\n", pwszDrv);
+ }
+ else
+ printf("%ls - failed to get version info with 0x%x\n", pwszDrv, GetLastError());
+ }
+ else
+ printf("%ls - GetDeviceDriverFileName failed with 0x%x\n", wszDriver, GetLastError());
+ }
+}
+
+
+class BugReportFilterRegistryWin : public BugReportFilter
+{
+public:
+ BugReportFilterRegistryWin() {};
+ virtual ~BugReportFilterRegistryWin() {};
+ virtual void *apply(void *pvSource, size_t *pcbInOut);
+};
+
+void *BugReportFilterRegistryWin::apply(void *pvSource, size_t *pcbInOut)
+{
+ /*
+ * The following implementation is not optimal by any means. It serves to
+ * illustrate and test the case when filter's output is longer than its
+ * input.
+ */
+ RT_NOREF(pcbInOut);
+ /* Registry export files are encoded in UTF-16 (little endian on Intel x86). */
+ void *pvDest = pvSource;
+ uint16_t *pwsSource = (uint16_t *)pvSource;
+ if (*pwsSource++ == 0xFEFF && *pcbInOut > 48)
+ {
+ if (!memcmp(pwsSource, L"Windows Registry Editor", 46))
+ {
+ *pcbInOut += 2;
+ pvDest = allocateBuffer(*pcbInOut);
+ uint16_t *pwsDest = (uint16_t *)pvDest;
+ *pwsDest++ = 0xFEFF;
+ *pwsDest++ = '#';
+ /* Leave space for 0xFEFF and '#' */
+ memcpy(pwsDest, pwsSource, *pcbInOut - 4);
+ }
+ }
+ return pvDest;
+}
+
+
+void createBugReportOsSpecific(BugReport* report, const char *pszHome)
+{
+ RT_NOREF(pszHome);
+ WCHAR szWinDir[MAX_PATH];
+
+ int cbNeeded = GetWindowsDirectory(szWinDir, RT_ELEMENTS(szWinDir));
+ if (cbNeeded == 0)
+ throw RTCError(RTCStringFmt("Failed to get Windows directory (err=%d)\n", GetLastError()));
+ if (cbNeeded > MAX_PATH)
+ throw RTCError(RTCStringFmt("Failed to get Windows directory (needed %d-byte buffer)\n", cbNeeded));
+ RTCStringFmt WinInfDir("%ls/inf", szWinDir);
+ report->addItem(new BugReportFile(PathJoin(WinInfDir.c_str(), "setupapi.app.log"), "setupapi.app.log"));
+ report->addItem(new BugReportFile(PathJoin(WinInfDir.c_str(), "setupapi.dev.log"), "setupapi.dev.log"));
+ report->addItem(new BugReportNetworkAdaptersWin);
+ RTCStringFmt WinSysDir("%ls/System32", szWinDir);
+ report->addItem(new BugReportCommand("IpConfig", PathJoin(WinSysDir.c_str(), "ipconfig.exe"), "/all", NULL));
+ report->addItem(new BugReportCommand("RouteTable", PathJoin(WinSysDir.c_str(), "netstat.exe"), "-rn", NULL));
+ report->addItem(new BugReportCommand("SystemEvents", PathJoin(WinSysDir.c_str(), "wevtutil.exe"),
+ "qe", "System",
+ "/q:*[System[Provider[@Name='VBoxUSBMon' or @Name='VBoxNetLwf']]]", NULL));
+ report->addItem(new BugReportCommand("UpdateHistory", PathJoin(WinSysDir.c_str(), "wbem/wmic.exe"),
+ "qfe", "list", "brief", NULL));
+ report->addItem(new BugReportCommand("DriverServices", PathJoin(WinSysDir.c_str(), "sc.exe"),
+ "query", "type=", "driver", "state=", "all", NULL));
+ report->addItem(new BugReportCommand("DriverStore", PathJoin(WinSysDir.c_str(), "pnputil.exe"), "-e", NULL));
+ report->addItem(new BugReportCommandTemp("RegDevKeys", PathJoin(WinSysDir.c_str(), "reg.exe"), "export",
+ "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\Root\\NET", NULL),
+ new BugReportFilterRegistryWin());
+ report->addItem(new BugReportCommandTemp("RegDrvKeys", PathJoin(WinSysDir.c_str(), "reg.exe"), "export",
+ "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", NULL),
+ new BugReportFilterRegistryWin());
+ report->addItem(new BugReportCommandTemp("RegNetwork", PathJoin(WinSysDir.c_str(), "reg.exe"), "export",
+ "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Network", NULL),
+ new BugReportFilterRegistryWin());
+ report->addItem(new BugReportCommandTemp("RegNetFltNobj", PathJoin(WinSysDir.c_str(), "reg.exe"), "export",
+ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\CLSID\\{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}", NULL),
+ new BugReportFilterRegistryWin());
+ report->addItem(new BugReportUsbTreeWin);
+ report->addItem(new BugReportDriversWin);
+}