summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/tools')
-rw-r--r--src/VBox/Runtime/tools/Makefile.kmk239
-rw-r--r--src/VBox/Runtime/tools/RTCat.cpp320
-rw-r--r--src/VBox/Runtime/tools/RTChMod.cpp425
-rw-r--r--src/VBox/Runtime/tools/RTCp.cpp351
-rw-r--r--src/VBox/Runtime/tools/RTDbgSymCache.cpp1345
-rw-r--r--src/VBox/Runtime/tools/RTEfiFatExtract.cpp253
-rw-r--r--src/VBox/Runtime/tools/RTFTPServer.cpp659
-rw-r--r--src/VBox/Runtime/tools/RTFuzzClient.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTFuzzMaster.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTGzip.cpp45
-rw-r--r--src/VBox/Runtime/tools/RTHttp.cpp178
-rw-r--r--src/VBox/Runtime/tools/RTIsoMaker.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTKrnlModInfo.cpp91
-rw-r--r--src/VBox/Runtime/tools/RTLdrCheckImports.cpp697
-rw-r--r--src/VBox/Runtime/tools/RTLdrFlt.cpp445
-rw-r--r--src/VBox/Runtime/tools/RTLs.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTManifest.cpp394
-rw-r--r--src/VBox/Runtime/tools/RTMkDir.cpp369
-rw-r--r--src/VBox/Runtime/tools/RTNtDbgHelp.cpp383
-rw-r--r--src/VBox/Runtime/tools/RTRm.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTRmDir.cpp359
-rw-r--r--src/VBox/Runtime/tools/RTShutdown.cpp104
-rw-r--r--src/VBox/Runtime/tools/RTSignTool.cpp2765
-rw-r--r--src/VBox/Runtime/tools/RTTar.cpp44
-rw-r--r--src/VBox/Runtime/tools/RTTraceLogTool.cpp330
-rw-r--r--src/VBox/Runtime/tools/RTUnzip.cpp44
26 files changed, 10060 insertions, 0 deletions
diff --git a/src/VBox/Runtime/tools/Makefile.kmk b/src/VBox/Runtime/tools/Makefile.kmk
new file mode 100644
index 00000000..d09f743f
--- /dev/null
+++ b/src/VBox/Runtime/tools/Makefile.kmk
@@ -0,0 +1,239 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the IPRT tools.
+#
+
+#
+# Copyright (C) 2006-2020 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+# VirtualBox OSE distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+if !defined(VBOX_ONLY_EXTPACKS) && !defined(VBOX_ONLY_DOCS)
+ # RTIsoMaker - ISO image maker - build version.
+ ifeq ($(KBUILD_TARGET), win) # Needed for repacking guest additions.
+ PROGRAMS += bldRTIsoMaker
+ bldRTIsoMaker_INSTTYPE = stage
+ else
+ BLDPROGS += bldRTIsoMaker
+ endif
+ bldRTIsoMaker_TEMPLATE = VBoxAdvBldProg
+ bldRTIsoMaker_SOURCES = \
+ RTIsoMaker.cpp \
+ ../common/misc/buildconfig.cpp
+ bldRTIsoMaker_DEFS = \
+ IPRT_BLDCFG_SCM_REV=$(if $(VBOX_SVN_REV_FALLBACK),$(VBOX_SVN_REV_FALLBACK),$(VBOX_SVN_REV)) \
+ IPRT_BLDCFG_VERSION_MAJOR=$(VBOX_VERSION_MAJOR) \
+ IPRT_BLDCFG_VERSION_MINOR=$(VBOX_VERSION_MINOR) \
+ IPRT_BLDCFG_VERSION_BUILD=$(VBOX_VERSION_BUILD)
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ bldRTIsoMaker_DEFS += \
+ IPRT_BLDCFG_VERSION_STRING="$(VBOX_VERSION_STRING)" \
+ IPRT_BLDCFG_TARGET="$(KBUILD_TARGET)" \
+ IPRT_BLDCFG_TARGET_ARCH="$(KBUILD_TARGET_ARCH)" \
+ IPRT_BLDCFG_TYPE="$(KBUILD_TYPE)"
+ else
+ bldRTIsoMaker_DEFS += \
+ IPRT_BLDCFG_VERSION_STRING=\"$(VBOX_VERSION_STRING)\" \
+ IPRT_BLDCFG_TARGET=\"$(KBUILD_TARGET)\" \
+ IPRT_BLDCFG_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" \
+ IPRT_BLDCFG_TYPE=\"$(KBUILD_TYPE)\"
+ endif
+ bldRTIsoMaker_INCS = ../include
+endif
+
+
+if !defined(VBOX_ONLY_DOCS)
+
+ # RTManifest is a tool for creating and verifying manifest files - build version.
+ BLDPROGS += bldRTManifest
+ bldRTManifest_TEMPLATE = VBoxAdvBldProg
+ bldRTManifest_SOURCES = RTManifest.cpp
+
+
+ if !defined(VBOX_ONLY_EXTPACKS) || "$(KBUILD_TARGET)" == "win" || "$(KBUILD_TARGET)" == "darwin"
+ # RTSignTool - Signing utility - build version. Signed on windows so we can get the certificate from it.
+ BLDPROGS += bldRTSignTool
+ bldRTSignTool_TEMPLATE = VBoxAdvBldProg
+ bldRTSignTool_SOURCES = RTSignTool.cpp
+ bldRTSignTool_DEFS = IPRT_IN_BUILD_TOOL
+ bldRTSignTool_POST_CMDS.win = $(VBOX_SIGN_IMAGE_CMDS)
+ if defined(VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION) && defined(VBOX_SIGNING_MODE)
+ bldRTSignTool_POST_CMDS.darwin = $(call VBOX_SIGN_MACHO_FN,$(out),org.virtualbox.org.bldtool.$(target))
+ endif
+ endif
+
+ if !defined(VBOX_ONLY_EXTPACKS)
+ # RTLdrCheckImports - import checker.
+ PROGRAMS += bldRTLdrCheckImports
+ bldRTLdrCheckImports_TEMPLATE = VBoxAdvBldProg
+ bldRTLdrCheckImports_DEFS = IPRT_IN_BUILD_TOOL
+ bldRTLdrCheckImports_SOURCES = RTLdrCheckImports.cpp
+ endif
+endif
+
+if !defined(VBOX_ONLY_BUILD)
+
+ # RTCat is a tool for displaying files.
+ PROGRAMS += RTCat
+ RTCat_TEMPLATE = VBoxR3Tool
+ RTCat_SOURCES = RTCat.cpp
+ RTCat_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTChMod - our chmod clone.
+ PROGRAMS += RTChMod
+ RTChMod_TEMPLATE = VBoxR3Tool
+ RTChMod_SOURCES = RTChMod.cpp
+
+ # RTCp - our cp clone.
+ PROGRAMS += RTCp
+ RTCp_TEMPLATE = VBoxR3Tool
+ RTCp_SOURCES = RTCp.cpp
+ RTCp_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTIsoMaker - ISO image maker - build version.
+ PROGRAMS += RTIsoMaker
+ RTIsoMaker_TEMPLATE = VBoxR3Tool
+ RTIsoMaker_SOURCES = RTIsoMaker.cpp
+ RTIsoMaker_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTLs is a tool for listing file information.
+ PROGRAMS += RTLs
+ RTLs_TEMPLATE = VBoxR3Tool
+ RTLs_SOURCES = RTLs.cpp
+ RTLs_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTRm is a tool for removing files and directories.
+ PROGRAMS += RTRm
+ RTRm_TEMPLATE = VBoxR3Tool
+ RTRm_SOURCES = RTRm.cpp
+ RTRm_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTManifest is a tool for creating and verifying manifest files.
+ PROGRAMS += RTManifest
+ RTManifest_TEMPLATE = VBoxR3Tool
+ RTManifest_SOURCES = RTManifest.cpp
+ RTManifest_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTLdrFlt is similar to c++filt, except that it's for VMMR0.r0 stacks.
+ PROGRAMS += RTLdrFlt
+ RTLdrFlt_TEMPLATE = VBoxR3Tool
+ RTLdrFlt_SOURCES = RTLdrFlt.cpp
+
+ # RTFTPServer implements a simple FTP server.
+ PROGRAMS += RTFTPServer
+ RTFTPServer_TEMPLATE = VBoxR3Tool
+ RTFTPServer_SOURCES = RTFTPServer.cpp
+
+ # RTGzip - our gzip clone (for testing the gzip/gunzip streaming code)
+ PROGRAMS += RTGzip
+ RTGzip_TEMPLATE = VBoxR3Tool
+ RTGzip_SOURCES = RTGzip.cpp
+ RTGzip_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ ifdef VBOX_WITH_LIBCURL
+ # RTHttp - our http/https fetcher (for testing the http client API).
+ PROGRAMS += RTHttp
+ RTHttp_TEMPLATE = VBoxR3Tool
+ RTHttp_SOURCES = RTHttp.cpp
+ endif
+
+ # RTLdrCheckImports - import checker.
+ PROGRAMS += RTLdrCheckImports
+ RTLdrCheckImports_TEMPLATE = VBoxR3Tool
+ RTLdrCheckImports_SOURCES = RTLdrCheckImports.cpp
+ RTLdrCheckImports_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTMkDir - our mkdir clone.
+ PROGRAMS += RTMkDir
+ RTMkDir_TEMPLATE = VBoxR3Tool
+ RTMkDir_SOURCES = RTMkDir.cpp
+
+ # RTRmDir - our mkdir clone.
+ PROGRAMS += RTRmDir
+ RTRmDir_TEMPLATE = VBoxR3Tool
+ RTRmDir_SOURCES = RTRmDir.cpp
+
+ # RTShutdown - similar (but not identical) to a typical unix shutdown command.
+ PROGRAMS += RTShutdown
+ RTShutdown_TEMPLATE = VBoxR3Tool
+ RTShutdown_SOURCES = RTShutdown.cpp
+
+ # RTTar - our tar clone (for testing the tar/gzip/gunzip streaming code)
+ PROGRAMS += RTTar
+ RTTar_TEMPLATE = VBoxR3Tool
+ RTTar_SOURCES = RTTar.cpp
+ RTTar_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTUnzip - our unzip clone (for testing the unzip streaming code)
+ PROGRAMS += RTUnzip
+ RTUnzip_TEMPLATE = VBoxR3Tool
+ RTUnzip_SOURCES = RTUnzip.cpp
+ RTUnzip_SOURCES += ../VBox/LoadVBoxDDU.cpp
+
+ # RTNtDbgHelp - our tar clone (for testing the tar/gzip/gunzip streaming code)
+ PROGRAMS.win += RTNtDbgHelp
+ RTNtDbgHelp_TEMPLATE = VBoxR3Tool
+ RTNtDbgHelp_SOURCES = RTNtDbgHelp.cpp
+
+ # RTDbgSymCache - Symbol cache manager.
+ PROGRAMS += RTDbgSymCache
+ RTDbgSymCache_TEMPLATE = VBoxR3Tool
+ RTDbgSymCache_SOURCES = RTDbgSymCache.cpp
+
+ # RTSignTool - Signing utility.
+ PROGRAMS += RTSignTool
+ RTSignTool_TEMPLATE = VBoxR3Tool
+ RTSignTool_SOURCES = RTSignTool.cpp
+ RTSignTool_LIBS = $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB)
+
+ # RTTraceLogTool - Trace log collection and dissection tool.
+ PROGRAMS += RTTraceLogTool
+ RTTraceLogTool_TEMPLATE = VBoxR3Tool
+ RTTraceLogTool_SOURCES = RTTraceLogTool.cpp
+
+ # RTFuzzMaster - Fuzzing master tool.
+ PROGRAMS += RTFuzzMaster
+ RTFuzzMaster_TEMPLATE = VBoxR3Tool
+ RTFuzzMaster_SOURCES = RTFuzzMaster.cpp
+
+ # RTFuzzClient - Fuzzing client tool.
+ PROGRAMS += RTFuzzClient
+ RTFuzzClient_TEMPLATE = VBoxR3Tool
+ RTFuzzClient_SOURCES = RTFuzzClient.cpp
+
+ # RTEfiFatExtract - Extracting single files from a fat EFI binary.
+ PROGRAMS += RTEfiFatExtract
+ RTEfiFatExtract_TEMPLATE = VBoxR3Tool
+ RTEfiFatExtract_SOURCES = RTEfiFatExtract.cpp
+
+ if1of ($(KBUILD_TARGET), darwin linux solaris win)
+ # RTKrnlModInfo - our lsmod/kextstat clone (for testing the RTKrnlMod code).
+ PROGRAMS += RTKrnlModInfo
+ RTKrnlModInfo_TEMPLATE = VBoxR3Tool
+ RTKrnlModInfo_SOURCES = RTKrnlModInfo.cpp
+ endif
+
+endif # !VBOX_ONLY_BUILD
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Runtime/tools/RTCat.cpp b/src/VBox/Runtime/tools/RTCat.cpp
new file mode 100644
index 00000000..a0aaa7b0
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTCat.cpp
@@ -0,0 +1,320 @@
+/* $Id: RTCat.cpp $ */
+/** @file
+ * IPRT - cat like utility.
+ */
+
+/*
+ * Copyright (C) 2017-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * CAT command options.
+ */
+typedef struct RTCMDCATOPTS
+{
+ bool fShowEnds; /**< -E */
+ bool fShowNonPrinting; /**< -v */
+ bool fShowTabs; /**< -T */
+ bool fSqueezeBlankLines; /**< -s */
+ bool fNumberLines; /**< -n */
+ bool fNumberNonBlankLines; /**< -b */
+ bool fAdvisoryOutputLock; /**< -l */
+ bool fUnbufferedOutput; /**< -u */
+} RTCMDCATOPTS;
+/** Pointer to const CAT options. */
+typedef RTCMDCATOPTS const *PCRTCMDCATOPTS;
+
+
+
+/**
+ * Outputs the source raw.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ * @param hVfsOutput The output I/O stream.
+ * @param hVfsSrc The input I/O stream.
+ * @param pszSrc The input name.
+ */
+static RTEXITCODE rtCmdCatShowRaw(RTVFSIOSTREAM hVfsOutput, RTVFSIOSTREAM hVfsSrc, const char *pszSrc)
+{
+ int rc = RTVfsUtilPumpIoStreams(hVfsSrc, hVfsOutput, 0 /*cbBufHint*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ return RTMsgErrorExitFailure("Error catting '%s': %Rrc", pszSrc, rc);
+}
+
+
+/**
+ * Outputs the source with complicated formatting.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ * @param hVfsOutput The output I/O stream.
+ * @param hVfsSrc The input I/O stream.
+ * @param pszSrc The input name.
+ */
+static RTEXITCODE rtCmdCatShowComplicated(RTVFSIOSTREAM hVfsOutput, RTVFSIOSTREAM hVfsSrc, const char *pszSrc,
+ PCRTCMDCATOPTS pOpts)
+{
+ if (pOpts->fShowEnds)
+ RTMsgWarning("--show-ends is not implemented\n");
+ if (pOpts->fShowTabs)
+ RTMsgWarning("--show-tabs is not implemented\n");
+ if (pOpts->fShowNonPrinting)
+ RTMsgWarning("--show-nonprinting is not implemented\n");
+ if (pOpts->fSqueezeBlankLines)
+ RTMsgWarning("--squeeze-blank is not implemented\n");
+ if (pOpts->fNumberLines)
+ RTMsgWarning("--number is not implemented\n");
+ if (pOpts->fNumberNonBlankLines)
+ RTMsgWarning("--number-nonblank is not implemented\n");
+ return rtCmdCatShowRaw(hVfsOutput, hVfsSrc, pszSrc);
+}
+
+
+/**
+ * Opens the input file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param phVfsIos Where to return the input stream handle.
+ */
+static RTEXITCODE rtCmdCatOpenInput(const char *pszFile, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+
+ if (!strcmp(pszFile, "-"))
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Error opening standard input: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ phVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszFile, rc, offError, &ErrInfo.Core);
+ }
+
+ return RTEXITCODE_SUCCESS;
+
+}
+
+
+/**
+ * A /bin/cat clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTEXITCODE RTCmdCat(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--show-all", 'A', RTGETOPT_REQ_NOTHING },
+ { "--number-nonblanks", 'b', RTGETOPT_REQ_NOTHING },
+ { "--show-ends-and-nonprinting", 'e', RTGETOPT_REQ_NOTHING },
+ { "--show-ends", 'E', RTGETOPT_REQ_NOTHING },
+ { "--advisory-output-lock", 'l', RTGETOPT_REQ_NOTHING },
+ { "--number", 'n', RTGETOPT_REQ_NOTHING },
+ { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING },
+ { "--show-tabs-and-nonprinting", 't', RTGETOPT_REQ_NOTHING },
+ { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING },
+ { "--unbuffered-output", 'u', RTGETOPT_REQ_NOTHING },
+ { "--show-nonprinting", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDCATOPTS Opts;
+ Opts.fShowEnds = false;
+ Opts.fShowNonPrinting = false;
+ Opts.fShowTabs = false;
+ Opts.fSqueezeBlankLines = false;
+ Opts.fNumberLines = false;
+ Opts.fNumberNonBlankLines = false;
+ Opts.fAdvisoryOutputLock = false;
+ Opts.fUnbufferedOutput = false;
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ RTVFSIOSTREAM hVfsOutput = NIL_RTVFSIOSTREAM;
+ int rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/, &hVfsOutput);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsIoStrmFromStdHandle: %Rrc", rc);
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ bool fContinue = true;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /*
+ * If we've processed any files we're done. Otherwise take
+ * input from stdin and write the output to stdout.
+ */
+ if (cProcessed > 0)
+ {
+ fContinue = false;
+ break;
+ }
+ ValueUnion.psz = "-";
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ RTVFSIOSTREAM hVfsSrc;
+ RTEXITCODE rcExit2 = rtCmdCatOpenInput(ValueUnion.psz, &hVfsSrc);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if ( Opts.fShowEnds
+ || Opts.fShowTabs
+ || Opts.fShowNonPrinting
+ || Opts.fSqueezeBlankLines
+ || Opts.fNumberLines
+ || Opts.fNumberNonBlankLines)
+ rcExit2 = rtCmdCatShowComplicated(hVfsOutput, hVfsSrc, ValueUnion.psz, &Opts);
+ else
+ rcExit2 = rtCmdCatShowRaw(hVfsOutput, hVfsSrc, ValueUnion.psz);
+ RTVfsIoStrmRelease(hVfsSrc);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ cProcessed++;
+ break;
+ }
+
+ case 'A':
+ Opts.fShowNonPrinting = true;
+ Opts.fShowEnds = true;
+ Opts.fShowTabs = true;
+ break;
+
+ case 'b':
+ Opts.fNumberNonBlankLines = true;
+ break;
+
+ case 'e':
+ Opts.fShowNonPrinting = true;
+ RT_FALL_THRU();
+ case 'E':
+ Opts.fShowEnds = true;
+ break;
+
+ case 'l':
+ Opts.fAdvisoryOutputLock = true;
+ break;
+
+ case 'n':
+ Opts.fNumberLines = true;
+ Opts.fNumberNonBlankLines = false;
+ break;
+
+ case 's':
+ Opts.fSqueezeBlankLines = true;
+ break;
+
+ case 't':
+ Opts.fShowNonPrinting = true;
+ RT_FALL_THRU();
+ case 'T':
+ Opts.fShowTabs = true;
+ break;
+
+ case 'u': /* currently ignored */
+ Opts.fUnbufferedOutput = true;
+ break;
+
+ case 'v':
+ Opts.fShowNonPrinting = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ RTVfsIoStrmRelease(hVfsOutput);
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdCat(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTChMod.cpp b/src/VBox/Runtime/tools/RTChMod.cpp
new file mode 100644
index 00000000..e68d2cd1
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTChMod.cpp
@@ -0,0 +1,425 @@
+/* $Id: RTChMod.cpp $ */
+/** @file
+ * IPRT - Changes the mode/attributes of a file system object.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** What to clear we all bits are being set. */
+#define RTCHMOD_SET_ALL_MASK (~( RTFS_TYPE_MASK \
+ | RTFS_DOS_NT_ENCRYPTED \
+ | RTFS_DOS_NT_COMPRESSED \
+ | RTFS_DOS_NT_REPARSE_POINT \
+ | RTFS_DOS_NT_SPARSE_FILE \
+ | RTFS_DOS_NT_DEVICE \
+ | RTFS_DOS_DIRECTORY))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum RTCMDCHMODNOISE
+{
+ kRTCmdChModNoise_Quiet,
+ kRTCmdChModNoise_Default,
+ kRTCmdChModNoise_Changes,
+ kRTCmdChModNoise_Verbose
+} RTCMDCHMODNOISE;
+
+
+typedef struct RTCMDCHMODOPTS
+{
+ /** The noise level. */
+ RTCMDCHMODNOISE enmNoiseLevel;
+ /** -R, --recursive */
+ bool fRecursive;
+ /** --preserve-root / --no-preserve-root (don't allow recursion from root). */
+ bool fPreserveRoot;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+ /** Which mode bits to set. */
+ RTFMODE fModeSet;
+ /** Which mode bits to clear. */
+ RTFMODE fModeClear;
+} RTCMDCHMODOPTS;
+
+
+
+/**
+ * Calculates the new file mode.
+ *
+ * @returns New mode mask.
+ * @param pOpts The chmod options.
+ * @param fMode The current file mode.
+ */
+static RTFMODE rtCmdMkModCalcNewMode(RTCMDCHMODOPTS const *pOpts, RTFMODE fMode)
+{
+ fMode &= ~pOpts->fModeClear;
+ fMode |= pOpts->fModeSet;
+ /** @todo do 'X' */
+ return fMode;
+}
+
+
+/**
+ * Changes the file mode of one file system object.
+ *
+ * @returns exit code
+ * @param pOpts The chmod options.
+ * @param pszPath The path to the file system object to change the
+ * file mode of.
+ */
+static RTEXITCODE rtCmdChModOne(RTCMDCHMODOPTS const *pOpts, const char *pszPath)
+{
+ int rc;
+ RTFSOBJINFO ObjInfo;
+ bool fChanges = false;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszPath) )
+ {
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ fChanges = fNewMode != ObjInfo.Attr.fMode;
+ if (fChanges)
+ {
+ rc = RTPathSetMode(pszPath, fNewMode);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTPathSetMode failed on '%s' with fNewMode=%#x: %Rrc", pszPath, fNewMode, rc);
+ }
+ }
+ else
+ RTMsgError("RTPathQueryInfoEx failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ RTVFSOBJ hVfsObj;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenObj(pszPath, RTFILE_O_ACCESS_ATTR_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING | RTPATH_F_FOLLOW_LINK,
+ &hVfsObj, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ fChanges = fNewMode != ObjInfo.Attr.fMode;
+ if (fChanges)
+ {
+ rc = RTVfsObjSetMode(hVfsObj, fNewMode, RTCHMOD_SET_ALL_MASK);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTVfsObjSetMode failed on '%s' with fNewMode=%#x: %Rrc", pszPath, fNewMode, rc);
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsObjQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ RTVfsObjRelease(hVfsObj);
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenObject", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->enmNoiseLevel >= (fChanges ? kRTCmdChModNoise_Changes : kRTCmdChModNoise_Verbose))
+ RTPrintf("%s\n", pszPath);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Recursively changes the file mode.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszPath The path to start changing the mode of.
+ */
+static int rtCmdChModRecursive(RTCMDCHMODOPTS const *pOpts, const char *pszPath)
+{
+ /*
+ * Check if it's a directory first. If not, join the non-recursive code.
+ */
+ int rc;
+ uint32_t offError;
+ RTFSOBJINFO ObjInfo;
+ RTERRINFOSTATIC ErrInfo;
+ bool const fUseChainApi = pOpts->fAlwaysUseChainApi || RTVfsChainIsSpec(pszPath);
+ if (!fUseChainApi)
+ {
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTPathQueryInfoEx failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ rc = RTVfsChainQueryInfo(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ /*
+ * Don't bother redoing the above work if its not necessary.
+ */
+ RTFMODE fNewMode = rtCmdMkModCalcNewMode(pOpts, ObjInfo.Attr.fMode);
+ if (fNewMode != ObjInfo.Attr.fMode)
+ return rtCmdChModOne(pOpts, pszPath);
+ if (pOpts->enmNoiseLevel >= kRTCmdChModNoise_Verbose)
+ RTPrintf("%s\n", pszPath);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /*
+ * For recursion we always use the VFS layer.
+ */
+ RTVFSDIR hVfsDir;
+ if (!fUseChainApi)
+ {
+ rc = RTVfsDirOpenNormal(pszPath, 0 /** @todo write attrib flag*/, &hVfsDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsDirOpenNormal failed on '%s': %Rrc", pszPath, rc);
+ }
+ else
+ {
+ rc = RTVfsChainOpenDir(pszPath, 0 /** @todo write attrib flag*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszPath, rc, offError, &ErrInfo.Core);
+ }
+
+ RTMsgError("Recursion is not yet implemented\n");
+ RTVfsDirRelease(hVfsDir);
+ rc = VERR_NOT_IMPLEMENTED;
+
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+static RTEXITCODE RTCmdChMod(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--preserve-root", 'x', RTGETOPT_REQ_NOTHING },
+ { "--no-preserve-root", 'X', RTGETOPT_REQ_NOTHING },
+ { "--changes", 'c', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'f', RTGETOPT_REQ_NOTHING },
+ { "--silent", 'f', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--reference", 'Z', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDCHMODOPTS Opts;
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Default;
+ Opts.fPreserveRoot = false;
+ Opts.fRecursive = false;
+ Opts.fAlwaysUseChainApi = false;
+ Opts.fModeClear = 0;
+ Opts.fModeSet = 0;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+
+ case 'x':
+ Opts.fPreserveRoot = true;
+ break;
+ case 'X':
+ Opts.fPreserveRoot = false;
+ break;
+
+ case 'f':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Quiet;
+ break;
+ case 'c':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Changes;
+ break;
+ case 'v':
+ Opts.enmNoiseLevel = kRTCmdChModNoise_Verbose;
+ break;
+
+ case 'Z':
+ {
+ RTFSOBJINFO ObjInfo;
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ rc = RTVfsChainQueryInfo(ValueUnion.psz, &ObjInfo,RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", ValueUnion.psz, rc, offError, &ErrInfo.Core);
+ Opts.fModeClear = RTCHMOD_SET_ALL_MASK;
+ Opts.fModeSet = ObjInfo.Attr.fMode & RTCHMOD_SET_ALL_MASK;
+ break;
+ }
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <mode> <file> [..]\n"
+ "\n"
+ "Options:\n"
+ " -f, --silent, --quiet\n"
+ " -c, --changes\n"
+ " -v, --verbose\n"
+ " Noise level selection.\n"
+ " -R, --recursive\n"
+ " Recurse into directories.\n"
+ " --preserve-root, --no-preserve-root\n"
+ " Whether to allow recursion from the root (default: yes).\n"
+ " --reference <file>\n"
+ " Take mode mask to use from <file> instead of <mode>.\n"
+ "\n"
+ "The <mode> part isn't fully implemented, so only numerical octal notation\n"
+ "works. Prefix the number(s) with 0x to use hexadecimal. There are two forms\n"
+ "of the numerical notation: <SET> and <SET>:<CLEAR>\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * The MODE.
+ */
+ if ( Opts.fModeClear == 0
+ && Opts.fModeSet == 0)
+ {
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No mode change specified.\n");
+
+ char *pszNext;
+ if ( ValueUnion.psz[0] == '0'
+ && (ValueUnion.psz[1] == 'x' || ValueUnion.psz[1] == 'X'))
+ rc = RTStrToUInt32Ex(ValueUnion.psz, &pszNext, 16, &Opts.fModeSet);
+ else
+ rc = RTStrToUInt32Ex(ValueUnion.psz, &pszNext, 8, &Opts.fModeSet);
+ if ( rc != VINF_SUCCESS
+ && (rc != VWRN_TRAILING_CHARS || *pszNext != ':'))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to parse mode mask: %s\n", ValueUnion.psz);
+ Opts.fModeSet &= RTCHMOD_SET_ALL_MASK;
+
+ if (rc == VINF_SUCCESS)
+ Opts.fModeClear = RTCHMOD_SET_ALL_MASK;
+ else
+ {
+ pszNext++;
+ if ( pszNext[0] == '0'
+ && (pszNext[1] == 'x' || pszNext[1] == 'X'))
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 16, &Opts.fModeClear);
+ else
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 8, &Opts.fModeClear);
+ if (rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unable to parse mode mask: %s\n", ValueUnion.psz);
+ Opts.fModeClear &= RTCHMOD_SET_ALL_MASK;
+ }
+
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fRecursive)
+ rc = rtCmdChModRecursive(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdChModOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdChMod(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTCp.cpp b/src/VBox/Runtime/tools/RTCp.cpp
new file mode 100644
index 00000000..3662a339
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTCp.cpp
@@ -0,0 +1,351 @@
+/* $Id: RTCp.cpp $ */
+/** @file
+ * IPRT - cp like utility.
+ */
+
+/*
+ * Copyright (C) 2017-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * CAT command options.
+ */
+typedef struct RTCMDCPOPTS
+{
+ /** -v, --verbose. */
+ bool fVerbose;
+
+ /** -H */
+ bool fFollowCommandLineSymlinks;
+
+ /** Set if recursive copy. */
+ bool fRecursive;
+ /** -x, --one-filesystem. */
+ bool fOneFileSystem;
+
+ /** Special --no-replace-nor-trucate hack for basic NTFS write support. */
+ bool fNoReplaceNorTruncate;
+
+ /** Number of sources. */
+ size_t cSources;
+ /** Source files/dirs. */
+ const char **papszSources;
+ /** Destination dir/file. */
+ const char *pszDestination;
+} RTCMDCPOPTS;
+/** Pointer to const CAT options. */
+typedef RTCMDCPOPTS const *PCRTCMDCPOPTS;
+
+
+
+/**
+ * Does the copying, source by source.
+ *
+ * @returns exit code.
+ * @param pOpts Options.
+ */
+static RTEXITCODE rtCmdCpDoIt(PCRTCMDCPOPTS pOpts)
+{
+ /*
+ * Check out what the destination is.
+ */
+/** @todo need to cache + share VFS chain elements here! */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTFSOBJINFO DstObjInfo;
+ int rc = RTVfsChainQueryInfo(pOpts->pszDestination, &DstObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->cSources > 1 && !RTFS_IS_DIRECTORY(DstObjInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("Mutiple files to copy and destination is not a directory!");
+ }
+ else if (rc != VERR_FILE_NOT_FOUND)
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pOpts->pszDestination, rc, offError, &ErrInfo.Core);
+ else
+ RT_ZERO(DstObjInfo);
+#if !RT_GNUC_PREREQ(8,2) || RT_GNUC_PREREQ(8,3) /* GCC 8.2 produces a tautological compare warning/error here. */
+ AssertCompile(!RTFS_IS_DIRECTORY(0));
+#endif
+
+ /*
+ * Process the sources.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (size_t iSrc = 0; iSrc < pOpts->cSources; iSrc++)
+ {
+ const char *pszSrc = pOpts->papszSources[iSrc];
+ RTFSOBJINFO SrcObjInfo;
+ RT_ZERO(SrcObjInfo);
+ rc = RTVfsChainQueryInfo(pszSrc, &SrcObjInfo, RTFSOBJATTRADD_UNIX,
+ pOpts->fFollowCommandLineSymlinks ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ continue;
+ }
+
+ /*
+ * Regular file.
+ */
+ if (RTFS_IS_FILE(SrcObjInfo.Attr.fMode))
+ {
+ /* Open source. */
+ RTVFSFILE hVfsSrc;
+ rc = RTVfsChainOpenFile(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ &hVfsSrc, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Make destination name if necessary and open destination.
+ Note! RTFILE_O_READ needed for VFS chains. */
+ char szDstPath[RTPATH_MAX];
+ const char *pszDst = pOpts->pszDestination;
+ if (RTFS_IS_DIRECTORY(DstObjInfo.Attr.fMode))
+ {
+ rc = RTPathJoin(szDstPath, sizeof(szDstPath), pszDst, RTPathFilename(pszSrc));
+ pszDst = szDstPath;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsDst;
+ uint64_t fDstFlags = (pOpts->fNoReplaceNorTruncate ? RTFILE_O_OPEN_CREATE : RTFILE_O_CREATE_REPLACE)
+ | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | (0666 << RTFILE_O_CREATE_MODE_SHIFT);
+ rc = RTVfsChainOpenFile(pszDst, fDstFlags, &hVfsDst, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /* Copy the bytes. */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsFileToIoStream(hVfsSrc);
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsDst);
+
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("'%s' -> '%s'\n", pszSrc, pszDst);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTVfsUtilPumpIoStreams failed for '%s' -> '%s': %Rrc",
+ pszSrc, pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ RTVfsFileRelease(hVfsDst);
+ }
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainOpenFile", pszDst, rc, offError, &ErrInfo.Core);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Destination path too long for source #%u (%Rrc): %s", iSrc, pszSrc, rc);
+ RTVfsFileRelease(hVfsSrc);
+ }
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainOpenFile", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+ /*
+ * Copying a directory requires the -R option to be active.
+ */
+ else if (RTFS_IS_DIRECTORY(SrcObjInfo.Attr.fMode))
+ {
+ if (pOpts->fRecursive)
+ {
+ /** @todo recursive copy */
+ rcExit = RTMsgErrorExitFailure("Recursion not implemented yet!");
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Source #%u is a directory: %s", iSrc + 1, pszSrc);
+ }
+ /*
+ * We currently don't support copying any other file types.
+ */
+ else
+ rcExit = RTMsgErrorExitFailure("Source #%u neither a file nor a directory: %s", iSrc + 1, pszSrc);
+ }
+ return rcExit;
+}
+
+
+/**
+ * A /bin/cp clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTEXITCODE RTCmdCp(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--archive", 'a', RTGETOPT_REQ_NOTHING },
+ { "--backup", 'B', RTGETOPT_REQ_STRING },
+ { "", 'b', RTGETOPT_REQ_NOTHING },
+ { "--copy-contents", 1024, RTGETOPT_REQ_NOTHING },
+ { "", 'd', RTGETOPT_REQ_NOTHING },
+ { "--no-dereference", 'P', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "", 'H', RTGETOPT_REQ_NOTHING },
+ { "--link", 'l', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "", 'p', RTGETOPT_REQ_NOTHING },
+ { "--preserve", 1026, RTGETOPT_REQ_STRING },
+ { "--no-preserve", 1027, RTGETOPT_REQ_STRING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--remove-destination", 1028, RTGETOPT_REQ_NOTHING },
+ { "--reply", 1029, RTGETOPT_REQ_STRING },
+ { "--sparse", 1030, RTGETOPT_REQ_STRING },
+ { "--strip-trailing-slashes", 1031, RTGETOPT_REQ_NOTHING },
+ { "--symbolic-links", 's', RTGETOPT_REQ_NOTHING },
+ { "--suffix", 'S', RTGETOPT_REQ_STRING },
+ { "--target-directory", 't', RTGETOPT_REQ_STRING },
+ { "--no-target-directory", 'T', RTGETOPT_REQ_NOTHING },
+ { "--update", 'u', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--one-file-system", 'x', RTGETOPT_REQ_NOTHING },
+ { "--no-replace-nor-trucate", 1032, RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDCPOPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fFollowCommandLineSymlinks = false;
+ Opts.fRecursive = false;
+ Opts.fOneFileSystem = false;
+ Opts.fNoReplaceNorTruncate = false;
+ Opts.pszDestination = NULL;
+ Opts.cSources = 0;
+ Opts.papszSources = (const char **)RTMemAllocZ(sizeof(const char *) * (cArgs + 2));
+ AssertReturn(Opts.papszSources, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ bool fContinue = true;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ if (Opts.pszDestination == NULL && Opts.cSources == 0)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing source and destination");
+ else if (Opts.pszDestination == NULL && Opts.cSources == 1)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing destination");
+ else if (Opts.pszDestination != NULL && Opts.cSources == 0)
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing source");
+ else
+ {
+ if (Opts.pszDestination == NULL && Opts.cSources > 0)
+ Opts.pszDestination = Opts.papszSources[--Opts.cSources];
+ Assert(Opts.cSources > 0);
+ rcExit = rtCmdCpDoIt(&Opts);
+ }
+ fContinue = false;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ Assert(Opts.cSources < cArgs);
+ Opts.papszSources[Opts.cSources++] = ValueUnion.psz;
+ break;
+
+ case 'H':
+ Opts.fFollowCommandLineSymlinks = true;
+ break;
+
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+ case 'x':
+ Opts.fOneFileSystem = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 1032:
+ Opts.fNoReplaceNorTruncate = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ RTMemFree(Opts.papszSources);
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdCp(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTDbgSymCache.cpp b/src/VBox/Runtime/tools/RTDbgSymCache.cpp
new file mode 100644
index 00000000..088890cb
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTDbgSymCache.cpp
@@ -0,0 +1,1345 @@
+/* $Id: RTDbgSymCache.cpp $ */
+/** @file
+ * IPRT - Debug Symbol Cache Utility.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/formats/mach-o.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Cache file type.
+ */
+typedef enum RTDBGSYMCACHEFILETYPE
+{
+ RTDBGSYMCACHEFILETYPE_INVALID,
+ RTDBGSYMCACHEFILETYPE_DIR,
+ RTDBGSYMCACHEFILETYPE_DIR_FILTER,
+ RTDBGSYMCACHEFILETYPE_DEBUG_FILE,
+ RTDBGSYMCACHEFILETYPE_IMAGE_FILE,
+ RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE,
+ RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE,
+ RTDBGSYMCACHEFILETYPE_IGNORE
+} RTDBGSYMCACHEFILETYPE;
+
+
+/**
+ * Configuration for the 'add' command.
+ */
+typedef struct RTDBGSYMCACHEADDCFG
+{
+ bool fRecursive;
+ bool fOverwriteOnConflict;
+ const char *pszFilter;
+ const char *pszCache;
+} RTDBGSYMCACHEADDCFG;
+/** Pointer to a read only 'add' config. */
+typedef RTDBGSYMCACHEADDCFG const *PCRTDBGSYMCACHEADDCFG;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Bundle suffixes. */
+static const char * const g_apszBundleSuffixes[] =
+{
+ ".kext",
+ ".app",
+ ".framework", /** @todo framework is different. */
+ ".component",
+ ".action",
+ ".caction",
+ ".bundle",
+ ".sourcebundle",
+ ".plugin",
+ ".ppp",
+ ".menu",
+ ".monitorpanel",
+ ".scripting",
+ ".prefPane",
+ ".qlgenerator",
+ ".brailledriver",
+ ".saver",
+ ".SpeechVoice",
+ ".SpeechRecognizer",
+ ".SpeechSynthesizer",
+ ".mdimporter",
+ ".spreporter",
+ ".xpc",
+ NULL
+};
+
+/** Debug bundle suffixes. (Same as above + .dSYM) */
+static const char * const g_apszDSymBundleSuffixes[] =
+{
+ ".kext.dSYM",
+ ".app.dSYM",
+ ".framework.dSYM",
+ ".component.dSYM",
+ ".action.dSYM",
+ ".caction.dSYM",
+ ".bundle.dSYM",
+ ".sourcebundle.dSYM",
+ ".menu.dSYM",
+ ".plugin.dSYM",
+ ".ppp.dSYM",
+ ".monitorpanel.dSYM",
+ ".scripting.dSYM",
+ ".prefPane.dSYM",
+ ".qlgenerator.dSYM",
+ ".brailledriver.dSYM",
+ ".saver.dSYM",
+ ".SpeechVoice.dSYM",
+ ".SpeechRecognizer.dSYM",
+ ".SpeechSynthesizer.dSYM",
+ ".mdimporter.dSYM",
+ ".spreporter.dSYM",
+ ".xpc.dSYM",
+ ".dSYM",
+ NULL
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg);
+
+
+
+/**
+ * Display the version of the cache program.
+ *
+ * @returns exit code.
+ */
+static RTEXITCODE rtDbgSymCacheVersion(void)
+{
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Shows the usage of the cache program.
+ *
+ * @returns Exit code.
+ * @param pszArg0 Program name.
+ * @param pszCommand Command selector, NULL if all.
+ */
+static RTEXITCODE rtDbgSymCacheUsage(const char *pszArg0, const char *pszCommand)
+{
+ if (!pszCommand || !strcmp(pszCommand, "add"))
+ RTPrintf("Usage: %s add [-Rno] <cache-root-dir> <file1[=cache-name]> [fileN..]\n"
+ "\n"
+ "Options:\n"
+ " -R, --recursive\n"
+ " Process directory arguments recursively.\n"
+ " -n, --no-recursive\n"
+ " No recursion. (default)\n"
+ " -o, --overwrite-on-conflict\n"
+ " Overwrite existing cache entry.\n"
+ , RTPathFilename(pszArg0));
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Creates a UUID mapping for the file.
+ *
+ * @returns IPRT status code.
+ * @param pszCacheFile The path to the file in the cache.
+ * @param pFileUuid The UUID of the file.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddCreateUuidMapping(const char *pszCacheFile, PRTUUID pFileUuid,
+ const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Create the UUID map entry first, deep.
+ */
+ char szMapPath[RTPATH_MAX];
+ int rc = RTPathJoin(szMapPath, sizeof(szMapPath) - sizeof("/xxxx/yyyy/xxxx/yyyy/xxxx/zzzzzzzzzzzz") + 1,
+ pCfg->pszCache, pszUuidMapDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing UUID map path (RTPathJoin): %Rrc", rc);
+
+ size_t cch = strlen(szMapPath);
+ szMapPath[cch] = '-';
+
+ rc = RTUuidToStr(pFileUuid, &szMapPath[cch + 2], sizeof(szMapPath) - cch);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing UUID map path (RTUuidToStr): %Rrc", rc);
+
+ /* Uppercase the whole lot. */
+ RTStrToUpper(&szMapPath[cch + 2]);
+
+ /* Split the first dword in two. */
+ szMapPath[cch + 1] = szMapPath[cch + 2];
+ szMapPath[cch + 2] = szMapPath[cch + 3];
+ szMapPath[cch + 3] = szMapPath[cch + 4];
+ szMapPath[cch + 4] = szMapPath[cch + 5];
+ szMapPath[cch + 5] = '-';
+
+ /*
+ * Create the directories in the path.
+ */
+ for (unsigned i = 0; i < 6; i++, cch += 5)
+ {
+ Assert(szMapPath[cch] == '-');
+ szMapPath[cch] = '\0';
+ if (!RTDirExists(szMapPath))
+ {
+ rc = RTDirCreate(szMapPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "RTDirCreate failed on '%s' (UUID map path): %Rrc", szMapPath, rc);
+ }
+ szMapPath[cch] = RTPATH_SLASH;
+ }
+ cch -= 5;
+
+ /*
+ * Calculate a relative path from there to the actual file.
+ */
+ char szLinkTarget[RTPATH_MAX];
+ szMapPath[cch] = '\0';
+ rc = RTPathCalcRelative(szLinkTarget, sizeof(szLinkTarget), szMapPath, false /*fFromFile*/, pszCacheFile);
+ szMapPath[cch] = RTPATH_SLASH;
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Failed to calculate relative path from '%s' to '%s': %Rrc", szMapPath, pszCacheFile, rc);
+
+ /*
+ * If there is already a link there, check if it matches or whether
+ * perhaps it's target doesn't exist.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ rc = RTPathQueryInfoEx(szMapPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCurTarget = NULL;
+ rc = RTSymlinkReadA(szMapPath, &pszCurTarget);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "UUID map: failed to read existing symlink '%s': %Rrc", szMapPath, rc);
+ if (RTPathCompare(pszCurTarget, szLinkTarget) == 0)
+ RTMsgInfo("UUID map: existing link '%s' has the same target ('%s').", szMapPath, pszCurTarget);
+ else
+ {
+ RTMsgError("UUID map: Existing mapping '%s' pointing to '%s' insted of '%s'",
+ szMapPath, pszCurTarget, szLinkTarget);
+ rc = VERR_ALREADY_EXISTS;
+ }
+ RTStrFree(pszCurTarget);
+ return rc;
+ }
+ else
+ RTMsgInfo("UUID map: replacing dangling link '%s'", szMapPath);
+ RTSymlinkDelete(szMapPath, 0 /*fFlags*/);
+ }
+ else if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return RTMsgErrorRc(VERR_IS_A_FILE,
+ "UUID map: found file at '%s', expect symbolic link or nothing.", szMapPath);
+ else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ return RTMsgErrorRc(VERR_IS_A_DIRECTORY,
+ "UUID map: found directory at '%s', expect symbolic link or nothing.", szMapPath);
+ else
+ return RTMsgErrorRc(VERR_NOT_SYMLINK,
+ "UUID map: Expected symbolic link or nothing at '%s', found: fMode=%#x",
+ szMapPath, ObjInfo.Attr.fMode);
+ }
+
+ /*
+ * Create the symbolic link.
+ */
+ rc = RTSymlinkCreate(szMapPath, szLinkTarget, RTSYMLINKTYPE_FILE, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Failed to create UUID map symlink '%s' to '%s': %Rrc", szMapPath, szLinkTarget, rc);
+ RTMsgInfo("UUID map: %s => %s", szMapPath, szLinkTarget);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a file to the cache.
+ *
+ * @returns IPRT status code.
+ * @param pszSrcPath Path to the source file.
+ * @param pszDstName The name of the destionation file (no path stuff).
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszDstSubDir The subdirectory to file it under. This is the
+ * stringification of a relatively unique identifier of
+ * the file in question.
+ * @param pAddToUuidMap Optional file UUID that is used to create a UUID map
+ * entry.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddOneFile(const char *pszSrcPath, const char *pszDstName, const char *pszExtraStuff,
+ const char *pszDstSubDir, PRTUUID pAddToUuidMap, const char *pszUuidMapDir,
+ PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Build and create the destination path, step by step.
+ */
+ char szDstPath[RTPATH_MAX];
+ int rc = RTPathJoin(szDstPath, sizeof(szDstPath), pCfg->pszCache, pszDstName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+
+ if (!RTDirExists(szDstPath))
+ {
+ rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
+ }
+
+ rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstSubDir);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+
+ if (!RTDirExists(szDstPath))
+ {
+ rc = RTDirCreate(szDstPath, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error creating '%s': %Rrc", szDstPath, rc);
+ }
+
+ rc = RTPathAppend(szDstPath, sizeof(szDstPath), pszDstName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+ if (pszExtraStuff)
+ {
+ rc = RTStrCat(szDstPath, sizeof(szDstPath), pszExtraStuff);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error constructing cache path for '%s': %Rrc", pszSrcPath, rc);
+ }
+
+ /*
+ * If the file exists, we compare the two and throws an error if the doesn't match.
+ */
+ if (RTPathExists(szDstPath))
+ {
+ rc = RTFileCompare(pszSrcPath, szDstPath);
+ if (RT_SUCCESS(rc))
+ {
+ RTMsgInfo("%s is already in the cache.", pszSrcPath);
+ if (pAddToUuidMap && pszUuidMapDir)
+ return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
+ return VINF_SUCCESS;
+ }
+ if (rc == VERR_NOT_EQUAL)
+ RTMsgInfo("Cache conflict with existing entry '%s' when inserting '%s'.", szDstPath, pszSrcPath);
+ else
+ RTMsgInfo("Error comparing '%s' with '%s': %Rrc", pszSrcPath, szDstPath, rc);
+ if (!pCfg->fOverwriteOnConflict)
+ return rc;
+ }
+
+ /*
+ * The file doesn't exist or we should overwrite it,
+ */
+ RTMsgInfo("Copying '%s' to '%s'...", pszSrcPath, szDstPath);
+ rc = RTFileCopy(pszSrcPath, szDstPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error copying '%s' to '%s': %Rrc", pszSrcPath, szDstPath, rc);
+ if (pAddToUuidMap && pszUuidMapDir)
+ return rtDbgSymCacheAddCreateUuidMapping(szDstPath, pAddToUuidMap, pszUuidMapDir, pCfg);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker that add the image file to the right place.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the image file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg Configuration data.
+ * @param hLdrMod Image handle.
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszUuidMapDir Optional UUID map cache directory if the image
+ * should be mapped by UUID.
+ * The map is a Mac OS X debug feature supported by
+ * the two native debuggers gdb and lldb. Look for
+ * descriptions of DBGFileMappedPaths in the
+ * com.apple.DebugSymbols in the user defaults.
+ */
+static int rtDbgSymCacheAddImageFileWorker(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg,
+ RTLDRMOD hLdrMod, const char *pszExtrSuff, const char *pszUuidMapDir)
+{
+ /*
+ * Determine which subdirectory to put the files in.
+ */
+ RTUUID Uuid;
+ PRTUUID pUuid = NULL;
+ int rc;
+ char szSubDir[48];
+ RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod);
+ switch (enmFmt)
+ {
+ case RTLDRFMT_MACHO:
+ {
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error quering image UUID from image '%s': %Rrc", pszPath, rc);
+
+ rc = RTUuidToStr(&Uuid, szSubDir, sizeof(szSubDir));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error convering UUID for image '%s' to string: %Rrc", pszPath, rc);
+ pUuid = &Uuid;
+ break;
+ }
+
+ case RTLDRFMT_PE:
+ {
+ uint32_t uTimestamp;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error quering timestamp from image '%s': %Rrc", pszPath, rc);
+
+ size_t cbImage = RTLdrSize(hLdrMod);
+ if (cbImage == ~(size_t)0)
+ return RTMsgErrorRc(rc, "Error quering size of image '%s': %Rrc", pszPath, rc);
+
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
+ break;
+ }
+
+ case RTLDRFMT_AOUT:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of a.out image has not yet been implemented: %s", pszPath);
+ case RTLDRFMT_ELF:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of ELF image has not yet been implemented: %s", pszPath);
+ case RTLDRFMT_LX:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Caching of LX image has not yet been implemented: %s", pszPath);
+ default:
+ return RTMsgErrorRc(VERR_NOT_SUPPORTED, "Unknown loader format for '%s': %d", pszPath, enmFmt);
+ }
+
+ /*
+ * Now add it.
+ */
+ return rtDbgSymCacheAddOneFile(pszPath, pszDstName, pszExtrSuff, szSubDir, pUuid, pszUuidMapDir, pCfg);
+}
+
+
+/**
+ * Adds what we think is an image file to the cache.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the image file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pszExtraSuff Optional extra suffix. Mach-O dSYM hack.
+ * @param pszUuidMapDir The UUID map subdirectory in the cache, if this is
+ * wanted, otherwise NULL.
+ * @param pCfg Configuration data.
+ */
+static int rtDbgSymCacheAddImageFile(const char *pszPath, const char *pszDstName, const char *pszExtraSuff,
+ const char *pszUuidMapDir, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Use the loader to open the alleged image file. We need to open it with
+ * arch set to amd64 and x86_32 in order to handle FAT images from the mac
+ * guys (we should actually enumerate archs, but that's currently not
+ * implemented nor necessary for our current use).
+ */
+ /* Open it as AMD64. */
+ RTLDRMOD hLdrMod64;
+ int rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_AMD64, &hLdrMod64, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_LDR_ARCH_MISMATCH)
+ {
+ if (rc != VERR_INVALID_EXE_SIGNATURE)
+ return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=amd64]: %Rrc%s%s", pszPath, rc,
+ RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
+ RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
+
+ RTMsgInfo("Skipping '%s', no a recognizable image file...", pszPath);
+ return VINF_SUCCESS;
+ }
+ hLdrMod64 = NIL_RTLDRMOD;
+ }
+
+ /* Open it as X86. */
+ RTLDRMOD hLdrMod32;
+ rc = RTLdrOpenEx(pszPath, RTLDR_O_FOR_DEBUG, RTLDRARCH_X86_32, &hLdrMod32, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_LDR_ARCH_MISMATCH)
+ {
+ RTLdrClose(hLdrMod64);
+ return RTMsgErrorRc(rc, "RTLdrOpen failed opening '%s' [arch=x86]: %Rrc%s%s", pszPath, rc,
+ RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "",
+ RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : "");
+ }
+ hLdrMod32 = NIL_RTLDRMOD;
+ }
+
+ /*
+ * Add the file.
+ */
+ if (hLdrMod32 == NIL_RTLDRMOD)
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
+ else if (hLdrMod64 == NIL_RTLDRMOD)
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
+ else
+ {
+ /*
+ * Do we need to add it once or twice?
+ */
+ RTLDRFMT enmFmt = RTLdrGetFormat(hLdrMod32);
+ bool fSame = enmFmt == RTLdrGetFormat(hLdrMod64);
+ if (fSame && enmFmt == RTLDRFMT_MACHO)
+ {
+ RTUUID Uuid32, Uuid64;
+ int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_UUID, &Uuid32, sizeof(Uuid32));
+ int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_UUID, &Uuid64, sizeof(Uuid64));
+ fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
+ if (fSame && RT_SUCCESS(rc32))
+ fSame = RTUuidCompare(&Uuid32, &Uuid64) == 0;
+ }
+ else if (fSame && enmFmt == RTLDRFMT_PE)
+ {
+ fSame = RTLdrSize(hLdrMod32) == RTLdrSize(hLdrMod64);
+ if (fSame)
+ {
+ uint32_t uTimestamp32, uTimestamp64;
+ int rc32 = RTLdrQueryProp(hLdrMod32, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp32, sizeof(uTimestamp32));
+ int rc64 = RTLdrQueryProp(hLdrMod64, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp64, sizeof(uTimestamp64));
+ fSame = RT_SUCCESS(rc32) == RT_SUCCESS(rc64);
+ if (fSame && RT_SUCCESS(rc32))
+ fSame = uTimestamp32 == uTimestamp64;
+ }
+ }
+
+ rc = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod64, pszExtraSuff, pszUuidMapDir);
+ if (!fSame)
+ {
+ /** @todo should symlink or hardlink this second copy. */
+ int rc2 = rtDbgSymCacheAddImageFileWorker(pszPath, pszDstName, pCfg, hLdrMod32, pszExtraSuff, pszUuidMapDir);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ RTLdrClose(hLdrMod32);
+ RTLdrClose(hLdrMod64);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtDbgSymCacheAddDebugFile that adds a Mach-O debug file to the
+ * cache.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the PDB file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ * @param hFile Handle to the file.
+ */
+static int rtDbgSymCacheAddDebugMachO(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /* This shouldn't happen, figure out what to do if it does. */
+ RT_NOREF(pCfg, pszDstName);
+ return RTMsgErrorRc(VERR_NOT_IMPLEMENTED,
+ "'%s' is an OS X image file, did you point me to a file inside a .dSYM or .sym file?",
+ pszPath);
+}
+
+
+/**
+ * Worker for rtDbgSymCacheAddDebugFile that adds PDBs to the cace.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the PDB file.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ * @param hFile Handle to the file.
+ */
+static int rtDbgSymCacheAddDebugPdb(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg, RTFILE hFile)
+{
+ RT_NOREF(pCfg, hFile, pszDstName);
+ return RTMsgErrorRc(VERR_NOT_IMPLEMENTED, "PDB support not implemented: '%s'", pszPath);
+}
+
+
+/**
+ * Adds a debug file to the cache.
+ *
+ * @returns IPRT status code
+ * @param pszPath The path to the debug file in question.
+ * @param pszDstName Add to the cache under this name. Typically the
+ * filename part of @a pszPath.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDebugFile(const char *pszPath, const char *pszDstName, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Need to extract an identifier of sorts here in order to put them in
+ * the right place in the cache. Currently only implemnted for Mach-O
+ * files since these use executable containers.
+ *
+ * We take a look at the file header in hope to figure out what to do
+ * with the file.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "Error opening '%s': %Rrc", pszPath, rc);
+
+ union
+ {
+ uint64_t au64[16];
+ uint32_t au32[16];
+ uint16_t au16[32];
+ uint8_t ab[64];
+ } uBuf;
+ rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for magics and call workers.
+ */
+ if (!memcmp(uBuf.ab, RT_STR_TUPLE("Microsoft C/C++ MSF 7.00")))
+ rc = rtDbgSymCacheAddDebugPdb(pszPath, pszDstName, pCfg, hFile);
+ else if ( uBuf.au32[0] == IMAGE_FAT_SIGNATURE
+ || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE
+ || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE
+ || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE)
+ rc = rtDbgSymCacheAddDebugMachO(pszPath, pszDstName, pCfg);
+ else
+ rc = RTMsgErrorRc(VERR_INVALID_MAGIC, "Unsupported debug file '%s' magic: %#010x", pszPath, uBuf.au32[0]);
+ }
+ else
+ rc = RTMsgErrorRc(rc, "Error reading '%s': %Rrc", pszPath, rc);
+
+ /* close the file. */
+ int rc2 = RTFileClose(hFile);
+ if (RT_FAILURE(rc2))
+ {
+ RTMsgError("Error closing '%s': %Rrc", pszPath, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Constructs the path to the file instide the bundle that we're keen on.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle on input, on successful
+ * return it's the path to the desired file. This
+ * a RTPATH_MAX size buffer.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszSubDir The bundle subdirectory the file lives in.
+ * @param papszSuffixes Pointer to an array of bundle suffixes.
+ */
+static int rtDbgSymCacheConstructBundlePath(char *pszPath, size_t cchPath, size_t cchName, const char *pszSubDir,
+ const char * const *papszSuffixes)
+{
+ /*
+ * Calc the name without the bundle extension.
+ */
+ size_t const cchOrgName = cchName;
+ const char *pszEnd = &pszPath[cchPath + cchName];
+ for (unsigned i = 0; papszSuffixes[i]; i++)
+ {
+ Assert(papszSuffixes[i][0] == '.');
+ size_t cchSuff = strlen(papszSuffixes[i]);
+ if ( cchSuff < cchName
+ && !memcmp(&pszEnd[-(ssize_t)cchSuff], papszSuffixes[i], cchSuff))
+ {
+ cchName -= cchSuff;
+ break;
+ }
+ }
+
+ /*
+ * Check the immediate directory first, in case it's layed out like
+ * IOPCIFamily.kext.
+ */
+ int rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName);
+ if (RT_FAILURE(rc) || !RTFileExists(pszPath))
+ {
+ /*
+ * Not there, ok then try the given subdirectory + name.
+ */
+ pszPath[cchPath + cchOrgName] = '\0';
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppendEx(pszPath, RTPATH_MAX, &pszPath[cchPath], cchName);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath + cchOrgName] = '\0';
+ return RTMsgErrorRc(rc, "Error constructing image bundle path for '%s': %Rrc", pszPath, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a image bundle of some sort.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle. This a RTPATH_MAX size
+ * buffer that we can write to when creating the
+ * path to the file inside the bundle that we're
+ * interested in.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszDstName Add to the cache under this name, NULL if not
+ * specified.
+ * @param pDirEntry The directory entry buffer, for handling bundle
+ * within bundle recursion.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddImageBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
+ PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Assuming these are kexts or simple applications, we only add the image
+ * file itself to the cache. No Info.plist or other files.
+ */
+ /** @todo consider looking for Frameworks and handling framework bundles. */
+ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/MacOS/", g_apszBundleSuffixes);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszDstName)
+ pszDstName = RTPathFilename(pszPath);
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
+ }
+
+ /*
+ * Look for plugins and other sub-bundles.
+ */
+ if (pCfg->fRecursive)
+ {
+ static char const * const s_apszSubBundleDirs[] =
+ {
+ "Contents/Plugins/",
+ /** @todo Frameworks ++ */
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszSubBundleDirs); i++)
+ {
+ pszPath[cchPath + cchName] = '\0';
+ int rc2 = RTPathAppend(pszPath, RTPATH_MAX - 1, s_apszSubBundleDirs[i]);
+ if (RT_SUCCESS(rc2))
+ {
+ if (RTDirExists(pszPath))
+ {
+ size_t cchPath2 = strlen(pszPath);
+ if (!RTPATH_IS_SLASH(pszPath[cchPath2 - 1]))
+ {
+ pszPath[cchPath2++] = RTPATH_SLASH;
+ pszPath[cchPath2] = '\0';
+ }
+ rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath2, pDirEntry, pCfg);
+ }
+ }
+ else
+ {
+ pszPath[cchPath + cchName] = '\0';
+ RTMsgError("Error constructing bundle subdir path for '%s' + '%s': %Rrc", pszPath, s_apszSubBundleDirs[i], rc);
+ }
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds a debug bundle.
+ *
+ * @returns IPRT status code.
+ * @param pszPath Path to the bundle. This a RTPATH_MAX size
+ * buffer that we can write to when creating the
+ * path to the file inside the bundle that we're
+ * interested in.
+ * @param cchPath The length of the path up to the bundle name.
+ * @param cchName The length of the bundle name.
+ * @param pszDstName Add to the cache under this name, NULL if not
+ * specified.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDebugBundle(char *pszPath, size_t cchPath, size_t cchName, const char *pszDstName,
+ PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * The current policy is not to add the whole .dSYM (or .sym) bundle, but
+ * rather just the dwarf image instide it. The <UUID>.plist and Info.plist
+ * files generally doesn't contain much extra information that's really
+ * necessary, I hope. At least this is what the uuidmap example in the
+ * lldb hints at (it links to the dwarf file, not the .dSYM dir).
+ *
+ * To avoid confusion with a .dSYM bundle, as well as collision with the
+ * image file, we use .dwarf suffix for the file.
+ *
+ * For details on the uuid map see rtDbgSymCacheAddImageFile as well as
+ * http://lldb.llvm.org/symbols.html .
+ *
+ * ASSUMES bundles contains Mach-O DWARF files.
+ */
+ int rc = rtDbgSymCacheConstructBundlePath(pszPath, cchPath, cchName, "Contents/Resources/DWARF/", g_apszDSymBundleSuffixes);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszDstName)
+ pszDstName = RTPathFilename(pszPath);
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, RTDBG_CACHE_DSYM_FILE_SUFFIX, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, pCfg);
+ }
+ return rc;
+}
+
+
+/**
+ * Figure the type of a file/dir based on path and FS object info.
+ *
+ * @returns The type.
+ * @param pszPath The path to the file/dir.
+ * @param pObjInfo The object information, symlinks followed.
+ */
+static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType2(const char *pszPath, PCRTFSOBJINFO pObjInfo)
+{
+ const char *pszName = RTPathFilename(pszPath);
+ const char *pszExt = RTPathSuffix(pszName);
+ if (pszExt)
+ pszExt++;
+ else
+ pszExt = "";
+
+ if ( RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)
+ || (pObjInfo->Attr.fMode & RTFS_DOS_DIRECTORY)) /** @todo OS X samba reports reparse points in /Volumes/ that we cannot resolve. */
+ {
+ /* Skip directories shouldn't bother with. */
+ if ( !RTStrICmp(pszName, ".Trashes")
+ || !RTStrICmp(pszName, ".$RESCYCLE.BIN")
+ || !RTStrICmp(pszName, "System.kext") /* Usually only plugins here, so skip it. */
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+
+ /* Directories can also be bundles on the mac. */
+ if (!RTStrICmp(pszExt, "dSYM"))
+ return RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apszBundleSuffixes) - 1; i++)
+ if (!RTStrICmp(pszExt, &g_apszBundleSuffixes[i][1]))
+ return RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE;
+
+ return RTDBGSYMCACHEFILETYPE_DIR;
+ }
+
+ if (!RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ return RTDBGSYMCACHEFILETYPE_INVALID;
+
+ /* Select image vs debug info based on extension. */
+ if ( !RTStrICmp(pszExt, "pdb")
+ || !RTStrICmp(pszExt, "dbg")
+ || !RTStrICmp(pszExt, "sym")
+ || !RTStrICmp(pszExt, "dwo")
+ || !RTStrICmp(pszExt, "dwp")
+ || !RTStrICmp(pszExt, "debug")
+ || !RTStrICmp(pszExt, "dsym")
+ || !RTStrICmp(pszExt, "dwarf")
+ || !RTStrICmp(pszExt, "map")
+ || !RTStrICmp(pszExt, "cv"))
+ return RTDBGSYMCACHEFILETYPE_DEBUG_FILE;
+
+ /* Filter out a bunch of files which obviously shouldn't be images. */
+ if ( !RTStrICmp(pszExt, "txt")
+ || !RTStrICmp(pszExt, "html")
+ || !RTStrICmp(pszExt, "htm")
+ || !RTStrICmp(pszExt, "rtf")
+ || !RTStrICmp(pszExt, "zip")
+ || !RTStrICmp(pszExt, "doc")
+ || !RTStrICmp(pszExt, "gz")
+ || !RTStrICmp(pszExt, "bz2")
+ || !RTStrICmp(pszExt, "xz")
+ || !RTStrICmp(pszExt, "kmk")
+ || !RTStrICmp(pszExt, "c")
+ || !RTStrICmp(pszExt, "cpp")
+ || !RTStrICmp(pszExt, "h")
+ || !RTStrICmp(pszExt, "m")
+ || !RTStrICmp(pszExt, "mm")
+ || !RTStrICmp(pszExt, "asm")
+ || !RTStrICmp(pszExt, "S")
+ || !RTStrICmp(pszExt, "inc")
+ || !RTStrICmp(pszExt, "sh")
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+ if ( !RTStrICmp(pszName, "Makefile")
+ || !RTStrICmp(pszName, "GNUmakefile")
+ || !RTStrICmp(pszName, "createsymbolfiles")
+ || !RTStrICmp(pszName, "kgmacros")
+ )
+ return RTDBGSYMCACHEFILETYPE_IGNORE;
+
+ return RTDBGSYMCACHEFILETYPE_IMAGE_FILE;
+}
+
+
+/**
+ * Figure file type based on name, will stat the file/dir.
+ *
+ * @returns File type.
+ * @param pszPath The path to the file/dir to figure.
+ */
+static RTDBGSYMCACHEFILETYPE rtDbgSymCacheFigureType(const char *pszPath)
+{
+ const char *pszName = RTPathFilename(pszPath);
+
+ /* Trailing slash. */
+ if (!pszName)
+ return RTDBGSYMCACHEFILETYPE_DIR;
+
+ /* Wildcard means listing directory and filtering. */
+ if (strpbrk(pszName, "?*"))
+ return RTDBGSYMCACHEFILETYPE_DIR_FILTER;
+
+ /* Get object info, following links. */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return RTDBGSYMCACHEFILETYPE_INVALID;
+ return rtDbgSymCacheFigureType2(pszPath, &ObjInfo);
+}
+
+
+/**
+ * Recursive worker for rtDbgSymCacheAddDir, for minimal stack wasting.
+ *
+ * @returns IPRT status code (fully bitched).
+ * @param pszPath Pointer to a RTPATH_MAX size buffer containing
+ * the path to the current directory ending with a
+ * slash.
+ * @param cchPath The size of the current directory path.
+ * @param pDirEntry Pointer to the RTDIRENTRYEX structure to use.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDirWorker(char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Open the directory.
+ */
+ RTDIR hDir;
+ int rc, rc2;
+ if (pCfg->pszFilter)
+ {
+ rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX - cchPath, pCfg->pszFilter);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath] = '\0';
+ return RTMsgErrorRc(rc, "Filename too long (%Rrc): '%s" RTPATH_SLASH_STR "%s'", rc, pszPath, pCfg->pszFilter);
+ }
+ rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ }
+ else
+ rc = RTDirOpen(&hDir, pszPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(rc, "RTDirOpen%s failed on '%s': %Rrc", pCfg->pszFilter ? "Filtered" : "", pszPath, rc);
+
+ /*
+ * Enumerate the files.
+ */
+ for (;;)
+ {
+ rc2 = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc2))
+ {
+ pszPath[cchPath] = '\0';
+ if (rc2 != VERR_NO_MORE_FILES)
+ {
+ RTMsgError("RTDirReadEx failed in '%s': %Rrc\n", pszPath, rc2);
+ rc = rc2;
+ }
+ break;
+ }
+
+ /* Skip dot and dot-dot. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ /* Construct a full path. */
+ rc = RTStrCopy(&pszPath[cchPath], RTPATH_MAX, pDirEntry->szName);
+ if (RT_FAILURE(rc))
+ {
+ pszPath[cchPath] = '\0';
+ RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
+ break;
+ }
+
+ switch (rtDbgSymCacheFigureType2(pszPath, &pDirEntry->Info))
+ {
+ case RTDBGSYMCACHEFILETYPE_DIR:
+ if (!pCfg->fRecursive)
+ RTMsgInfo("Skipping directory '%s'...", pszPath);
+ else
+ {
+ if (cchPath + pDirEntry->cbName + 3 <= RTPATH_MAX)
+ {
+ pszPath[cchPath + pDirEntry->cbName] = RTPATH_SLASH;
+ pszPath[cchPath + pDirEntry->cbName + 1] = '\0';
+ rc2 = rtDbgSymCacheAddDirWorker(pszPath, cchPath + pDirEntry->cbName + 1, pDirEntry, pCfg);
+ }
+ else
+ {
+ RTMsgError("File name too long in '%s': '%s' (%Rrc)", pszPath, pDirEntry->szName, rc);
+ rc2 = VERR_FILENAME_TOO_LONG;
+ }
+ }
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
+ rc2 = rtDbgSymCacheAddDebugFile(pszPath, pDirEntry->szName, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
+ rc2 = rtDbgSymCacheAddImageFile(pszPath, pDirEntry->szName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
+ rc2 = rtDbgSymCacheAddDebugBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
+ rc2 = rtDbgSymCacheAddImageBundle(pszPath, cchPath, pDirEntry->cbName, NULL /*pszDstName*/, pDirEntry, pCfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
+ case RTDBGSYMCACHEFILETYPE_INVALID:
+ rc2 = RTMsgErrorRc(VERR_INTERNAL_ERROR_2, "Invalid: '%s'", pszPath);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IGNORE:
+ rc2 = VINF_SUCCESS;
+ break;
+ }
+
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /*
+ * Clean up.
+ */
+ rc2 = RTDirClose(hDir);
+ if (RT_FAILURE(rc2))
+ {
+ RTMsgError("RTDirClose failed in '%s': %Rrc", pszPath, rc);
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a directory.
+ *
+ * @returns IPRT status code (fully bitched).
+ * @param pszPath The directory path.
+ * @param pCfg The configuration.
+ */
+static int rtDbgSymCacheAddDir(const char *pszPath, PCRTDBGSYMCACHEADDCFG pCfg)
+{
+ /*
+ * Set up the path buffer, stripping any filter.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath) - 2, pszPath);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'", pszPath);
+
+ size_t cchPath = strlen(pszPath);
+ if (!cchPath)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path empty: '%s'", pszPath);
+
+ if (pCfg->pszFilter)
+ szPath[cchPath - strlen(pCfg->pszFilter)] = '\0';
+ cchPath = RTPathStripTrailingSlash(szPath);
+ if (!RTPATH_IS_SEP(pszPath[cchPath - 1]))
+ {
+ szPath[cchPath++] = RTPATH_SLASH;
+ szPath[cchPath] = '\0';
+ }
+
+ /*
+ * Let the worker do the rest.
+ */
+ RTDIRENTRYEX DirEntry;
+ return rtDbgSymCacheAddDirWorker(szPath, cchPath, &DirEntry, pCfg);
+}
+
+
+/**
+ * Adds a file or directory.
+ *
+ * @returns Program exit code.
+ * @param pszPath The user supplied path to the file or directory.
+ * @param pszCache The path to the cache.
+ * @param fRecursive Whether to process directories recursively.
+ * @param fOverwriteOnConflict Whether to overwrite existing cache entry on
+ * conflict, or just leave it.
+ */
+static RTEXITCODE rtDbgSymCacheAddFileOrDir(const char *pszPath, const char *pszCache, bool fRecursive,
+ bool fOverwriteOnConflict)
+{
+ RT_NOREF1(fOverwriteOnConflict);
+ RTDBGSYMCACHEADDCFG Cfg;
+ Cfg.fRecursive = fRecursive;
+ Cfg.pszCache = pszCache;
+ Cfg.pszFilter = NULL;
+
+ /* If the filename contains an equal ('=') char, treat the left as the file
+ to add tne right part as the name to add it under (handy for kernels). */
+ char *pszFree = NULL;
+ const char *pszDstName = RTPathFilename(pszPath);
+ const char *pszEqual = pszDstName ? strchr(pszDstName, '=') : NULL;
+ if (pszEqual)
+ {
+ pszPath = pszFree = RTStrDupN(pszPath, pszEqual - pszPath);
+ if (!pszFree)
+ return RTMsgErrorExitFailure("out of memory!\n");
+ pszDstName = pszEqual + 1;
+ if (!*pszDstName)
+ return RTMsgErrorExitFailure("add-as filename is empty!\n");
+ }
+
+ int rc;
+ RTDBGSYMCACHEFILETYPE enmType = rtDbgSymCacheFigureType(pszPath);
+ switch (enmType)
+ {
+ default:
+ case RTDBGSYMCACHEFILETYPE_INVALID:
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid: '%s'", pszPath);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DIR_FILTER:
+ Cfg.pszFilter = RTPathFilename(pszPath);
+ RT_FALL_THRU();
+ case RTDBGSYMCACHEFILETYPE_DIR:
+ if (!pszEqual)
+ rc = rtDbgSymCacheAddDir(pszPath, &Cfg);
+ else
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Add-as filename is not applicable to directories!");
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_FILE:
+ rc = rtDbgSymCacheAddDebugFile(pszPath, pszDstName, &Cfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_IMAGE_FILE:
+ rc = rtDbgSymCacheAddImageFile(pszPath, pszDstName, NULL /*pszExtraSuff*/, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, &Cfg);
+ break;
+
+ case RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE:
+ case RTDBGSYMCACHEFILETYPE_IMAGE_BUNDLE:
+ {
+ size_t cchPath = strlen(pszPath);
+ size_t cchFilename = strlen(RTPathFilename(pszPath));
+ char szPathBuf[RTPATH_MAX];
+ if (cchPath < sizeof(szPathBuf))
+ {
+ memcpy(szPathBuf, pszPath, cchPath + 1);
+ if (enmType == RTDBGSYMCACHEFILETYPE_DEBUG_BUNDLE)
+ rc = rtDbgSymCacheAddDebugBundle(szPathBuf, cchPath - cchFilename, cchFilename,
+ pszEqual ? pszDstName : NULL, &Cfg);
+ else
+ {
+ RTDIRENTRYEX DirEntry;
+ rc = rtDbgSymCacheAddImageBundle(szPathBuf, cchPath - cchFilename, cchFilename,
+ pszEqual ? pszDstName : NULL, &DirEntry, &Cfg);
+ }
+ }
+ else
+ rc = RTMsgErrorRc(VERR_FILENAME_TOO_LONG, "Filename too long: '%s'", pszPath);
+ break;
+ }
+
+ case RTDBGSYMCACHEFILETYPE_IGNORE:
+ rc = RTMsgErrorRc(VERR_INVALID_PARAMETER, "Invalid file: '%s'", pszPath);
+ break;
+ }
+
+ RTStrFree(pszFree);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Handles the 'add' command.
+ *
+ * @returns Program exit code.
+ * @param pszArg0 The program name.
+ * @param cArgs The number of arguments to the 'add' command.
+ * @param papszArgs The argument vector, starting after 'add'.
+ */
+static RTEXITCODE rtDbgSymCacheCmdAdd(const char *pszArg0, int cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--no-recursive", 'n', RTGETOPT_REQ_NOTHING },
+ { "--overwrite-on-conflict", 'o', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszCache = NULL;
+ bool fRecursive = false;
+ bool fOverwriteOnConflict = false;
+
+ RTGETOPTSTATE State;
+ int rc = RTGetOptInit(&State, cArgs, papszArgs, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ //uint32_t cAdded = 0;
+ RTGETOPTUNION ValueUnion;
+ int chOpt;
+ while ((chOpt = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (chOpt)
+ {
+ case 'R':
+ fRecursive = true;
+ break;
+
+ case 'n':
+ fRecursive = false;
+ break;
+
+ case 'o':
+ fOverwriteOnConflict = true;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* The first non-option is a cache directory. */
+ if (!pszCache)
+ {
+ pszCache = ValueUnion.psz;
+ if (!RTPathExists(pszCache))
+ {
+ rc = RTDirCreate(pszCache, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Error creating cache directory '%s': %Rrc", pszCache, rc);
+ }
+ else if (!RTDirExists(pszCache))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specified cache directory is not a directory: '%s'", pszCache);
+ }
+ /* Subsequent non-options are files to be added to the cache. */
+ else
+ {
+ RTEXITCODE rcExit = rtDbgSymCacheAddFileOrDir(ValueUnion.psz, pszCache, fRecursive, fOverwriteOnConflict);
+ if (rcExit != RTEXITCODE_FAILURE)
+ return rcExit;
+ }
+ break;
+
+ case 'h':
+ return rtDbgSymCacheUsage(pszArg0, "add");
+ case 'V':
+ return rtDbgSymCacheVersion();
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+
+ if (!pszCache)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No cache directory or files to add were specified.");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Switch on the command.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SYNTAX;
+ if (argc < 2)
+ rtDbgSymCacheUsage(argv[0], NULL);
+ else if (!strcmp(argv[1], "add"))
+ rcExit = rtDbgSymCacheCmdAdd(argv[0], argc - 2, argv + 2);
+ else if ( !strcmp(argv[1], "-h")
+ || !strcmp(argv[1], "-?")
+ || !strcmp(argv[1], "--help"))
+ rcExit = rtDbgSymCacheUsage(argv[0], NULL);
+ else if ( !strcmp(argv[1], "-V")
+ || !strcmp(argv[1], "--version"))
+ rcExit = rtDbgSymCacheVersion();
+ else
+ RTMsgError("Unknown command: '%s'", argv[1]);
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTEfiFatExtract.cpp b/src/VBox/Runtime/tools/RTEfiFatExtract.cpp
new file mode 100644
index 00000000..e01f987e
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTEfiFatExtract.cpp
@@ -0,0 +1,253 @@
+/* $Id: RTEfiFatExtract.cpp $ */
+/** @file
+ * IPRT - Utility for extracting single files from a fat EFI binary.
+ */
+
+/*
+ * Copyright (C) 2019-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/formats/efi-fat.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+
+static int efiFatExtractList(const char *pszInput)
+{
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszInput, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ EFI_FATHDR Hdr;
+ rc = RTFileReadAt(hFile, 0, &Hdr, sizeof(Hdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(Hdr.u32Magic) == EFI_FATHDR_MAGIC
+ && RT_LE2H_U32(Hdr.cFilesEmbedded) <= 16 /*Arbitrary number*/)
+ {
+ RTFOFF offRead = sizeof(Hdr);
+
+ for (uint32_t i = 0; i < RT_LE2H_U32(Hdr.cFilesEmbedded); i++)
+ {
+ EFI_FATDIRENTRY Entry;
+ rc = RTFileReadAt(hFile, offRead, &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Entry %u:\n", i);
+ RTPrintf(" CPU Type: %#x\n", RT_LE2H_U32(Entry.u32CpuType));
+ RTPrintf(" CPU Subtype: %#x\n", RT_LE2H_U32(Entry.u32CpuSubType));
+ RTPrintf(" Offset: %#x\n", RT_LE2H_U32(Entry.u32OffsetStart));
+ RTPrintf(" Size: %#x\n", RT_LE2H_U32(Entry.cbFile));
+ RTPrintf(" Alignment: %#x\n", RT_LE2H_U32(Entry.u32Alignment));
+ }
+ else
+ {
+ RTPrintf("Failed to read file entry %u of '%s': %Rrc\n", pszInput, i, rc);
+ break;
+ }
+
+ offRead += sizeof(Entry);
+ }
+ }
+ else
+ {
+ rc = VERR_INVALID_MAGIC;
+ RTPrintf("The header contains invalid values\n");
+ }
+ }
+ else
+ RTPrintf("Failed to read header of '%s': %Rrc\n", pszInput, rc);
+
+ RTFileClose(hFile);
+ }
+ else
+ RTPrintf("Failed to open file '%s': %Rrc\n", pszInput, rc);
+
+ return rc;
+}
+
+
+static int efiFatExtractSave(const char *pszInput, uint32_t idxEntry, const char *pszOut)
+{
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszInput, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ EFI_FATHDR Hdr;
+ rc = RTFileReadAt(hFile, 0, &Hdr, sizeof(Hdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(Hdr.u32Magic) == EFI_FATHDR_MAGIC
+ && RT_LE2H_U32(Hdr.cFilesEmbedded) <= 16 /*Arbitrary number*/)
+ {
+ if (idxEntry < RT_LE2H_U32(Hdr.cFilesEmbedded))
+ {
+ EFI_FATDIRENTRY Entry;
+
+ rc = RTFileReadAt(hFile, sizeof(Hdr) + idxEntry * sizeof(Entry), &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvFile = RTMemAllocZ(RT_LE2H_U32(Entry.cbFile));
+ if (RT_LIKELY(pvFile))
+ {
+ rc = RTFileReadAt(hFile, RT_LE2H_U32(Entry.u32OffsetStart), pvFile, RT_LE2H_U32(Entry.cbFile), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFileOut;
+ rc = RTFileOpen(&hFileOut, pszOut, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFileOut, pvFile, RT_LE2H_U32(Entry.cbFile), NULL);
+ if (RT_FAILURE(rc))
+ RTPrintf("Failed to write output file '%s': %Rrc\n", pszOut, rc);
+ RTFileClose(hFileOut);
+ }
+ else
+ RTPrintf("Failed to create output file '%s': %Rrc\n", pszOut, rc);
+ }
+ else
+ RTPrintf("Failed to read embedded file %u: %Rrc\n", idxEntry, rc);
+
+ RTMemFree(pvFile);
+ }
+ else
+ RTPrintf("Failed to allocate %u bytes of memory\n", RT_LE2H_U32(Entry.cbFile));
+ }
+ else
+ RTPrintf("Failed to read file entry %u of '%s': %Rrc\n", pszInput, idxEntry, rc);
+ }
+ else
+ {
+ rc = VERR_INVALID_PARAMETER;
+ RTPrintf("Given index out of range, maximum is %u\n", RT_LE2H_U32(Hdr.cFilesEmbedded));
+ }
+ }
+ else
+ {
+ rc = VERR_INVALID_MAGIC;
+ RTPrintf("The header contains invalid values\n");
+ }
+ }
+ else
+ RTPrintf("Failed to read header of '%s': %Rrc\n", pszInput, rc);
+
+ RTFileClose(hFile);
+ }
+ else
+ RTPrintf("Failed to open file '%s': %Rrc\n", pszInput, rc);
+
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--entry", 'e', RTGETOPT_REQ_UINT32 },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszInput = NULL;
+ const char *pszOut = NULL;
+ uint32_t idxEntry = UINT32_C(0xffffffff);
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=<file>\n"
+ " Input file\n"
+ " -e,--entry=<idx>\n"
+ " Selects the entry for saving\n"
+ " -o,--output=file\n"
+ " Save the specified entry to this file\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+ case 'V':
+ RTPrintf("$Revision: 135976 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'i':
+ pszInput = ValueUnion.psz;
+ break;
+ case 'o':
+ pszOut = ValueUnion.psz;
+ break;
+ case 'e':
+ idxEntry = ValueUnion.u32;
+ break;
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!pszInput)
+ {
+ RTPrintf("An input path must be given\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (!pszOut || idxEntry == UINT32_C(0xffffffff))
+ rc = efiFatExtractList(pszInput);
+ else
+ rc = efiFatExtractSave(pszInput, idxEntry, pszOut);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTFTPServer.cpp b/src/VBox/Runtime/tools/RTFTPServer.cpp
new file mode 100644
index 00000000..238aecc5
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFTPServer.cpp
@@ -0,0 +1,659 @@
+/* $Id: RTFTPServer.cpp $ */
+/** @file
+ * IPRT - Utility for running a (simple) FTP server.
+ */
+
+/*
+ * Copyright (C) 2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+/*
+ * Use this setup to best see what's going on:
+ *
+ * VBOX_LOG=rt_ftp=~0
+ * VBOX_LOG_DEST="nofile stderr"
+ * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <signal.h>
+
+#include <iprt/ftp.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Definitations *
+*********************************************************************************************************************************/
+typedef struct FTPSERVERDATA
+{
+ /** The absolute path of the FTP server's root directory. */
+ char szPathRootAbs[RTPATH_MAX];
+ /** The relative current working directory (CWD) to szRootDir. */
+ char szCWD[RTPATH_MAX];
+} FTPSERVERDATA;
+typedef FTPSERVERDATA *PFTPSERVERDATA;
+
+/**
+ * Enumeration specifying the VFS handle type of the FTP server.
+ */
+typedef enum FTPSERVERVFSHANDLETYPE
+{
+ FTPSERVERVFSHANDLETYPE_INVALID = 0,
+ FTPSERVERVFSHANDLETYPE_FILE,
+ FTPSERVERVFSHANDLETYPE_DIR,
+ /** The usual 32-bit hack. */
+ FTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
+} FTPSERVERVFSHANDLETYPE;
+
+/**
+ * Structure for keeping a VFS handle of the FTP server.
+ */
+typedef struct FTPSERVERVFSHANDLE
+{
+ /** The type of the handle, stored in the union below. */
+ FTPSERVERVFSHANDLETYPE enmType;
+ union
+ {
+ /** The VFS (chain) handle to use for this file. */
+ RTVFSFILE hVfsFile;
+ /** The VFS (chain) handle to use for this directory. */
+ RTVFSDIR hVfsDir;
+ } u;
+} FTPSERVERVFSHANDLE;
+typedef FTPSERVERVFSHANDLE *PFTPSERVERVFSHANDLE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Set by the signal handler when the FTP server shall be terminated. */
+static volatile bool g_fCanceled = false;
+static FTPSERVERDATA g_FTPServerData;
+
+
+#ifdef RT_OS_WINDOWS
+static BOOL WINAPI signalHandler(DWORD dwCtrlType)
+{
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ ASMAtomicWriteBool(&g_fCanceled, true);
+ fEventHandled = TRUE;
+ break;
+ default:
+ break;
+ /** @todo Add other events here. */
+ }
+
+ return fEventHandled;
+}
+#else /* !RT_OS_WINDOWS */
+/**
+ * Signal handler that sets g_fCanceled.
+ *
+ * This can be executed on any thread in the process, on Windows it may even be
+ * a thread dedicated to delivering this signal. Don't do anything
+ * unnecessary here.
+ */
+static void signalHandler(int iSignal)
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fCanceled, true);
+}
+#endif
+
+/**
+ * Installs a custom signal handler to get notified
+ * whenever the user wants to intercept the program.
+ *
+ * @todo Make this handler available for all VBoxManage modules?
+ */
+static int signalHandlerInstall(void)
+{
+ g_fCanceled = false;
+
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+# ifdef SIGBREAK
+ signal(SIGBREAK, signalHandler);
+# endif
+#endif
+ return rc;
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int signalHandlerUninstall(void)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
+ }
+#else
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+# ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+ return rc;
+}
+
+static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
+{
+ RT_NOREF(pData, pcszUser);
+
+ RTPrintf("User '%s' connected\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
+{
+ RT_NOREF(pData, pcszUser, pcszPassword);
+
+ RTPrintf("Authenticating user '%s' ...\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
+{
+ RT_NOREF(pData);
+
+ RTPrintf("User '%s' disconnected\n", pcszUser);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
+ if (!pHandle)
+ return VERR_NO_MEMORY;
+
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ int rc = RTVfsChainOpenFile(pszPathAbs, fMode, &pHandle->u.hVfsFile, NULL /*poffError */, NULL /* pErrInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->enmType = FTPSERVERVFSHANDLETYPE_FILE;
+
+ *ppvHandle = pHandle;
+ }
+ else
+ RTMemFree(pHandle);
+
+ RTStrFree(pszPathAbs);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
+
+ return RTVfsFileRead(pHandle->u.hVfsFile, pvBuf, cbToRead, pcbRead);
+}
+
+static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
+
+ int rc = RTVfsFileRelease(pHandle->u.hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTMemFree(pvHandle);
+ pvHandle = NULL;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ char *pszStat = NULL;
+ if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pcszPath,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileQuerySize(hFile, puSize);
+ if (RT_SUCCESS(rc))
+ RTPrintf("File size is: %RU64\n", *puSize);
+ RTFileClose(hFile);
+ }
+
+ RTStrFree(pszStat);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ char *pszStat = NULL;
+ if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Stat for '%s'\n", pszStat);
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszStat,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO fsObjInfo;
+ rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (pFsObjInfo)
+ *pFsObjInfo = fsObjInfo;
+ }
+
+ RTFileClose(hFile);
+ }
+
+ RTStrFree(pszStat);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ RTPrintf("Setting current directory to '%s'\n", pcszCWD);
+
+ /** @todo BUGBUG Santiy checks! */
+
+ return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
+}
+
+static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
+
+ return RTStrCopy(pszPWD, cbPWD, pThis->szCWD);
+}
+
+static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
+{
+ RT_NOREF(pData);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onDirOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvHandle)
+{
+ PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+ Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
+ if (!pHandle)
+ return VERR_NO_MEMORY;
+
+ /* Construct absolute path. */
+ char *pszPathAbs = NULL;
+ if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
+ return VERR_NO_MEMORY;
+
+ RTPrintf("Opening directory '%s'\n", pszPathAbs);
+
+ int rc = RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, &pHandle->u.hVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->enmType = FTPSERVERVFSHANDLETYPE_DIR;
+
+ *ppvHandle = pHandle;
+ }
+ else
+ {
+ RTMemFree(pHandle);
+ }
+
+ RTStrFree(pszPathAbs);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) onDirClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
+{
+ RT_NOREF(pData);
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
+
+ RTVfsDirRelease(pHandle->u.hVfsDir);
+
+ RTMemFree(pHandle);
+ pHandle = NULL;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onDirRead(PRTFTPCALLBACKDATA pData, void *pvHandle, char **ppszEntry,
+ PRTFSOBJINFO pInfo, char **ppszOwner, char **ppszGroup, char **ppszTarget)
+{
+ RT_NOREF(pData);
+ RT_NOREF(ppszTarget); /* No symlinks yet */
+
+ PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
+
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ return VERR_NO_MEMORY;
+
+ int rc;
+
+ for (;;)
+ {
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(pHandle->u.hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID)
+ {
+ RTFSOBJINFO OwnerInfo;
+ rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
+ pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(rc)
+ && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ {
+ *ppszOwner = RTStrDup(&OwnerInfo.Attr.u.UnixOwner.szName[0]);
+ if (!*ppszOwner)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID)
+ {
+ RTFSOBJINFO GroupInfo;
+ rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
+ pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ if ( RT_SUCCESS(rc)
+ && GroupInfo.Attr.u.UnixGroup.szName[0])
+ {
+ *ppszGroup = RTStrDup(&GroupInfo.Attr.u.UnixGroup.szName[0]);
+ if (!*ppszGroup)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ *ppszEntry = RTStrDup(pDirEntry->szName);
+ AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
+
+ *pInfo = pDirEntry->Info;
+
+ break;
+
+ } /* for */
+
+ RTMemTmpFree(pDirEntry);
+ pDirEntry = NULL;
+
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /* Use some sane defaults. */
+ char szAddress[64] = "localhost";
+ uint16_t uPort = 2121;
+
+ RT_ZERO(g_FTPServerData);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
+ /** @todo Implement IPv6 support? */
+ { "--port", 'p', RTGETOPT_REQ_UINT16 },
+ { "--root-dir", 'r', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned uVerbosityLevel = 1;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'a':
+ RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
+ ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
+ break;
+
+ case 'p':
+ uPort = ValueUnion.u16;
+ break;
+
+ case 'r':
+ RTStrCopy(g_FTPServerData.szPathRootAbs, sizeof(g_FTPServerData.szPathRootAbs), ValueUnion.psz);
+ break;
+
+ case 'v':
+ uVerbosityLevel++;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -a, --address (default: localhost)\n"
+ " Specifies the address to use for listening.\n"
+ " -p, --port (default: 2121)\n"
+ " Specifies the port to use for listening.\n"
+ " -r, --root-dir (default: current dir)\n"
+ " Specifies the root directory being served.\n"
+ " -v, --verbose\n"
+ " Controls the verbosity level.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 135814 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!strlen(g_FTPServerData.szPathRootAbs))
+ {
+ /* By default use the current directory as serving root directory. */
+ rc = RTPathGetCurrent(g_FTPServerData.szPathRootAbs, sizeof(g_FTPServerData.szPathRootAbs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
+ }
+
+ /* Initialize CWD. */
+ RTStrPrintf2(g_FTPServerData.szCWD, sizeof(g_FTPServerData.szCWD), "/");
+
+ /* Install signal handler. */
+ rc = signalHandlerInstall();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the FTP server instance.
+ */
+ RTFTPSERVERCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+
+ Callbacks.pfnOnUserConnect = onUserConnect;
+ Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
+ Callbacks.pfnOnUserDisconnect = onUserDisonnect;
+ Callbacks.pfnOnFileOpen = onFileOpen;
+ Callbacks.pfnOnFileRead = onFileRead;
+ Callbacks.pfnOnFileClose = onFileClose;
+ Callbacks.pfnOnFileGetSize = onFileGetSize;
+ Callbacks.pfnOnFileStat = onFileStat;
+ Callbacks.pfnOnPathSetCurrent = onPathSetCurrent;
+ Callbacks.pfnOnPathGetCurrent = onPathGetCurrent;
+ Callbacks.pfnOnPathUp = onPathUp;
+ Callbacks.pfnOnDirOpen = onDirOpen;
+ Callbacks.pfnOnDirClose = onDirClose;
+ Callbacks.pfnOnDirRead = onDirRead;
+
+ RTFTPSERVER hFTPServer;
+ rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
+ &g_FTPServerData, sizeof(g_FTPServerData));
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
+ RTPrintf("Root directory is '%s'\n", g_FTPServerData.szPathRootAbs);
+
+ RTPrintf("Running FTP server ...\n");
+
+ for (;;)
+ {
+ RTThreadSleep(200);
+
+ if (g_fCanceled)
+ break;
+ }
+
+ RTPrintf("Stopping FTP server ...\n");
+
+ int rc2 = RTFtpServerDestroy(hFTPServer);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTPrintf("Stopped FTP server\n");
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
+
+ int rc2 = signalHandlerUninstall();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /* Set rcExit on failure in case we forgot to do so before. */
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTFuzzClient.cpp b/src/VBox/Runtime/tools/RTFuzzClient.cpp
new file mode 100644
index 00000000..f3f19ed8
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFuzzClient.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTFuzzClient.cpp $ */
+/** @file
+ * IPRT - Fuzzing client utility.
+ */
+
+/*
+ * Copyright (C) 2019-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFuzzCmdFuzzingClient(argc, argv, NULL, NULL);
+}
+
diff --git a/src/VBox/Runtime/tools/RTFuzzMaster.cpp b/src/VBox/Runtime/tools/RTFuzzMaster.cpp
new file mode 100644
index 00000000..e35cf26c
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTFuzzMaster.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTFuzzMaster.cpp $ */
+/** @file
+ * IPRT - Fuzzing master Utility.
+ */
+
+/*
+ * Copyright (C) 2018-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFuzzCmdMaster(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTGzip.cpp b/src/VBox/Runtime/tools/RTGzip.cpp
new file mode 100644
index 00000000..b55b4c93
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTGzip.cpp
@@ -0,0 +1,45 @@
+/* $Id: RTGzip.cpp $ */
+/** @file
+ * IPRT - GZIP Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipGzipCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTHttp.cpp b/src/VBox/Runtime/tools/RTHttp.cpp
new file mode 100644
index 00000000..0720318c
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTHttp.cpp
@@ -0,0 +1,178 @@
+/* $Id: RTHttp.cpp $ */
+/** @file
+ * IPRT - Utility for retriving URLs.
+ */
+
+/*
+ * Copyright (C) 2006-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/http.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Create a HTTP client instance.
+ */
+ RTHTTP hHttp;
+ rc = RTHttpCreate(&hHttp);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpCreate failed: %Rrc", rc);
+ rc = RTHttpSetFollowRedirects(hHttp, 8);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpSetFollowRedirects(,8) failed: %Rrc", rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--set-header", 's', RTGETOPT_REQ_STRING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszOutput = NULL;
+ unsigned uVerbosityLevel = 1;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'o':
+ pszOutput = ValueUnion.psz;
+ break;
+
+ case 'q':
+ uVerbosityLevel--;
+ break;
+ case 'v':
+ uVerbosityLevel++;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] URL0 [URL1 [...]]\n"
+ "\n"
+ "Options:\n"
+ " -o,--output=file\n"
+ " Output file. If not given, the file is displayed on stdout.\n"
+ " -q, --quiet\n"
+ " -v, --verbose\n"
+ " Controls the verbosity level.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 135976 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 's':
+ {
+ char *pszColon = (char *)strchr(ValueUnion.psz, ':');
+ if (!pszColon)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No colon in --set-header value: %s", ValueUnion.psz);
+ *pszColon = '\0'; /* evil */
+ const char *pszValue = pszColon + 1;
+ if (RT_C_IS_BLANK(*pszValue))
+ pszValue++;
+ rc = RTHttpAddHeader(hHttp, ValueUnion.psz, pszValue, RTSTR_MAX, RTHTTPADDHDR_F_BACK);
+ *pszColon = ':';
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTHttpAddHeader failed: %Rrc (on %s)", rc, ValueUnion.psz);
+ break;
+ }
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ int rcHttp;
+ if (pszOutput && strcmp("-", pszOutput))
+ {
+ if (uVerbosityLevel > 0)
+ RTStrmPrintf(g_pStdErr, "Fetching '%s' into '%s'...\n", ValueUnion.psz, pszOutput);
+ rcHttp = RTHttpGetFile(hHttp, ValueUnion.psz, pszOutput);
+ }
+ else
+ {
+ if (uVerbosityLevel > 0)
+ RTStrmPrintf(g_pStdErr, "Fetching '%s'...\n", ValueUnion.psz);
+
+ void *pvResp;
+ size_t cbResp;
+ rcHttp = RTHttpGetBinary(hHttp, ValueUnion.psz, &pvResp, &cbResp);
+ if (RT_SUCCESS(rcHttp))
+ {
+ RTVFSIOSTREAM hVfsIos;
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, 0, true /*fLeaveOpen*/, &hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(hVfsIos, pvResp, cbResp, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error writing to stdout: %Rrc", rc);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening stdout: %Rrc", rc);
+ RTHttpFreeResponse(pvResp);
+ }
+ }
+ if (RT_FAILURE(rcHttp))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc getting '%s'", rcHttp, ValueUnion.psz);
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ RTHttpDestroy(hHttp);
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTIsoMaker.cpp b/src/VBox/Runtime/tools/RTIsoMaker.cpp
new file mode 100644
index 00000000..0322fb3c
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTIsoMaker.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTIsoMaker.cpp $ */
+/** @file
+ * IPRT - ISO maker Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fsisomaker.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFsIsoMakerCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTKrnlModInfo.cpp b/src/VBox/Runtime/tools/RTKrnlModInfo.cpp
new file mode 100644
index 00000000..a94b7acb
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTKrnlModInfo.cpp
@@ -0,0 +1,91 @@
+/* $Id: RTKrnlModInfo.cpp $ */
+/** @file
+ * IPRT - Utility for getting information about loaded kernel modules.
+ */
+
+/*
+ * Copyright (C) 2017-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/krnlmod.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /** @todo proper argument parsing, please. */
+ if (argc != 1)
+ {
+ RTMsgError("Syntax error: This tool takes no parameters. It just lists the modules\n");
+ return RTEXITCODE_SYNTAX;
+ }
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ uint32_t cKrnlMods = RTKrnlModLoadedGetCount();
+ if (cKrnlMods)
+ {
+ PRTKRNLMODINFO pahKrnlModInfo = (PRTKRNLMODINFO)RTMemAllocZ(cKrnlMods * sizeof(RTKRNLMODINFO));
+ if (pahKrnlModInfo)
+ {
+ rc = RTKrnlModLoadedQueryInfoAll(pahKrnlModInfo, cKrnlMods, &cKrnlMods);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Index Load address Size Ref count Name \n");
+ for (unsigned i = 0; i < cKrnlMods; i++)
+ {
+ RTKRNLMODINFO hKrnlModInfo = pahKrnlModInfo[i];
+ RTPrintf("%5u %#-18RHv %-10u %-10u %s\n", i,
+ RTKrnlModInfoGetLoadAddr(hKrnlModInfo),
+ RTKrnlModInfoGetSize(hKrnlModInfo),
+ RTKrnlModInfoGetRefCnt(hKrnlModInfo),
+ RTKrnlModInfoGetName(hKrnlModInfo));
+ RTKrnlModInfoRelease(hKrnlModInfo);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc querying kernel modules", rc);
+
+ RTMemFree(pahKrnlModInfo);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %Rrc allocating memory", VERR_NO_MEMORY);
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTLdrCheckImports.cpp b/src/VBox/Runtime/tools/RTLdrCheckImports.cpp
new file mode 100644
index 00000000..758a09ca
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLdrCheckImports.cpp
@@ -0,0 +1,697 @@
+/* $Id: RTLdrCheckImports.cpp $ */
+/** @file
+ * IPRT - Module dependency checker.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Import checker options.
+ */
+typedef struct RTCHECKIMPORTSOPTS
+{
+ /** Number of paths to search. */
+ size_t cPaths;
+ /** Search directories. */
+ char **papszPaths;
+ /** The loader architecture. */
+ RTLDRARCH enmLdrArch;
+ /** Verbosity level. */
+ unsigned cVerbosity;
+ /** Whether to also list orinals in the export listing. */
+ bool fListOrdinals;
+} RTCHECKIMPORTSOPTS;
+/** Pointer to the checker options. */
+typedef RTCHECKIMPORTSOPTS *PRTCHECKIMPORTSOPTS;
+/** Pointer to the const checker options. */
+typedef RTCHECKIMPORTSOPTS const *PCRTCHECKIMPORTSOPTS;
+
+
+/**
+ * Import module.
+ */
+typedef struct RTCHECKIMPORTMODULE
+{
+ /** The module. If NIL, then we've got a export list (papszExports). */
+ RTLDRMOD hLdrMod;
+ /** Number of export in the export list. (Zero if hLdrMod is valid.) */
+ size_t cExports;
+ /** Export list. (NULL if hLdrMod is valid.) */
+ char **papszExports;
+ /** The module name. */
+ char szModule[256];
+} RTCHECKIMPORTMODULE;
+/** Pointer to an import module. */
+typedef RTCHECKIMPORTMODULE *PRTCHECKIMPORTMODULE;
+
+
+/**
+ * Import checker state (for each image being checked).
+ */
+typedef struct RTCHECKIMPORTSTATE
+{
+ /** The image we're processing. */
+ const char *pszImage;
+ /** The image we're processing. */
+ PCRTCHECKIMPORTSOPTS pOpts;
+ /** Status code. */
+ int iRc;
+ /** Import hint. */
+ uint32_t iHint;
+ /** Number modules. */
+ uint32_t cImports;
+ /** Import modules. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTCHECKIMPORTMODULE aImports[RT_FLEXIBLE_ARRAY];
+} RTCHECKIMPORTSTATE;
+/** Pointer to the import checker state. */
+typedef RTCHECKIMPORTSTATE *PRTCHECKIMPORTSTATE;
+
+
+
+/**
+ * Looks up a symbol/ordinal in the given import module.
+ *
+ * @returns IPRT status code.
+ * @param pModule The import module.
+ * @param pszSymbol The symbol name (NULL if not used).
+ * @param uSymbol The ordinal (~0 if unused).
+ * @param pValue Where to return a fake address.
+ */
+static int QuerySymbolFromImportModule(PRTCHECKIMPORTMODULE pModule, const char *pszSymbol, unsigned uSymbol, PRTLDRADDR pValue)
+{
+ if (pModule->hLdrMod != NIL_RTLDRMOD)
+ return RTLdrGetSymbolEx(pModule->hLdrMod, NULL, _128M, uSymbol, pszSymbol, pValue);
+
+ /*
+ * Search the export list. Ordinal imports are stringified: #<ordinal>
+ */
+ char szOrdinal[32];
+ if (!pszSymbol)
+ {
+ RTStrPrintf(szOrdinal, sizeof(szOrdinal), "#%u", uSymbol);
+ pszSymbol = szOrdinal;
+ }
+
+ size_t i = pModule->cExports;
+ while (i-- > 0)
+ if (strcmp(pModule->papszExports[i], pszSymbol) == 0)
+ {
+ *pValue = _128M + i*4;
+ return VINF_SUCCESS;
+ }
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRIMPORT}
+ */
+static DECLCALLBACK(int) GetImportCallback(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol,
+ unsigned uSymbol, PRTLDRADDR pValue, void *pvUser)
+{
+ PRTCHECKIMPORTSTATE pState = (PRTCHECKIMPORTSTATE)pvUser;
+ int rc;
+ NOREF(hLdrMod);
+
+ /*
+ * If a module is given, lookup the symbol/ordinal there.
+ */
+ if (pszModule)
+ {
+ uint32_t iModule = pState->iHint;
+ if ( iModule > pState->cImports
+ || strcmp(pState->aImports[iModule].szModule, pszModule) != 0)
+ {
+ for (iModule = 0; iModule < pState->cImports; iModule++)
+ if (strcmp(pState->aImports[iModule].szModule, pszModule) == 0)
+ break;
+ if (iModule >= pState->cImports)
+ return RTMsgErrorRc(VERR_MODULE_NOT_FOUND, "%s: Failed to locate import module '%s'", pState->pszImage, pszModule);
+ pState->iHint = iModule;
+ }
+
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_LDR_FORWARDER)
+ rc= VINF_SUCCESS;
+ else
+ {
+ if (pszSymbol)
+ RTMsgError("%s: Missing import '%s' from '%s'!", pState->pszImage, pszSymbol, pszModule);
+ else
+ RTMsgError("%s: Missing import #%u from '%s'!", pState->pszImage, uSymbol, pszModule);
+ pState->iRc = rc;
+ rc = VINF_SUCCESS;
+ *pValue = _128M + _4K;
+ }
+ }
+ /*
+ * Otherwise we need to scan all modules.
+ */
+ else
+ {
+ Assert(pszSymbol);
+ uint32_t iModule = pState->iHint;
+ if (iModule < pState->cImports)
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ else
+ rc = VERR_SYMBOL_NOT_FOUND;
+ if (rc == VERR_SYMBOL_NOT_FOUND)
+ {
+ for (iModule = 0; iModule < pState->cImports; iModule++)
+ {
+ rc = QuerySymbolFromImportModule(&pState->aImports[iModule], pszSymbol, uSymbol, pValue);
+ if (rc != VERR_SYMBOL_NOT_FOUND)
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: Missing import '%s'!", pState->pszImage, pszSymbol);
+ pState->iRc = rc;
+ rc = VINF_SUCCESS;
+ *pValue = _128M + _4K;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Loads an imported module.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pModule The import module.
+ * @param pErrInfo Error buffer (to avoid wasting stack).
+ * @param pszImage The image we're processing (for error messages).
+ */
+static int LoadImportModule(PCRTCHECKIMPORTSOPTS pOpts, PRTCHECKIMPORTMODULE pModule, PRTERRINFO pErrInfo, const char *pszImage)
+
+{
+ /*
+ * Look for real DLLs.
+ */
+ for (uint32_t iPath = 0; iPath < pOpts->cPaths; iPath++)
+ {
+ char szPath[RTPATH_MAX];
+ int rc = RTPathJoin(szPath, sizeof(szPath), pOpts->papszPaths[iPath], pModule->szModule);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offError;
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsChainQueryInfo(szPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ RTLDRMOD hLdrMod;
+ rc = RTLdrOpenVfsChain(szPath, RTLDR_O_FOR_DEBUG, pOpts->enmLdrArch, &hLdrMod, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pModule->hLdrMod = hLdrMod;
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Import '%s' -> '%s'\n", pModule->szModule, szPath);
+ }
+ else if (RTErrInfoIsSet(pErrInfo))
+ RTMsgError("%s: Failed opening import image '%s': %Rrc - %s", pszImage, szPath, rc, pErrInfo->pszMsg);
+ else
+ RTMsgError("%s: Failed opening import image '%s': %Rrc", pszImage, szPath, rc);
+ return rc;
+ }
+ }
+ else if ( rc != VERR_PATH_NOT_FOUND
+ && rc != VERR_FILE_NOT_FOUND)
+ RTVfsChainMsgError("RTVfsChainQueryInfo", szPath, rc, offError, pErrInfo);
+
+ /*
+ * Check for export file.
+ */
+ RTStrCat(szPath, sizeof(szPath), ".exports");
+ RTVFSFILE hVfsFile;
+ rc = RTVfsChainOpenFile(szPath, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile, &offError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read it into a memory buffer. */
+ uint64_t cbFile;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile < _4M)
+ {
+ char *pszFile = (char *)RTMemAlloc((size_t)cbFile + 1);
+ if (pszFile)
+ {
+ rc = RTVfsFileRead(hVfsFile, pszFile, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pszFile[(size_t)cbFile] = '\0';
+ rc = RTStrValidateEncoding(pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse it.
+ */
+ size_t iLine = 1;
+ size_t off = 0;
+ while (off < cbFile)
+ {
+ size_t const offStartLine = off;
+
+ /* skip leading blanks */
+ while (RT_C_IS_BLANK(pszFile[off]))
+ off++;
+
+ char ch = pszFile[off];
+ if ( ch != ';' /* comment */
+ && !RT_C_IS_CNTRL(ch))
+ {
+ /* find length of symbol */
+ size_t const offSymbol = off;
+ while ( (ch = pszFile[off]) != '\0'
+ && !RT_C_IS_SPACE(ch))
+ off++;
+ size_t const cchSymbol = off - offSymbol;
+
+ /* add it. */
+ if ((pModule->cExports & 127) == 0)
+ {
+ void *pvNew = RTMemRealloc(pModule->papszExports,
+ (pModule->cExports + 128) * sizeof(char *));
+ if (!pvNew)
+ {
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s:%u: out of memory!", pszImage, szPath, iLine);
+ break;
+ }
+ pModule->papszExports = (char **)pvNew;
+ }
+ pModule->papszExports[pModule->cExports] = RTStrDupN(&pszFile[offSymbol], cchSymbol);
+ if (pModule->papszExports[pModule->cExports])
+ pModule->cExports++;
+ else
+ {
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s:%u: out of memory!", pszImage, szPath, iLine);
+ break;
+ }
+
+ /* check what comes next is a comment or end of line/file */
+ while (RT_C_IS_BLANK(pszFile[off]))
+ off++;
+ ch = pszFile[off];
+ if ( ch != '\0'
+ && ch != '\n'
+ && ch != '\r'
+ && ch != ';')
+ rc = RTMsgErrorRc(VERR_PARSE_ERROR, "%s: %s:%u: Unexpected text at position %u!",
+ pszImage, szPath, iLine, off - offStartLine);
+ }
+
+ /* advance to the end of the the line */
+ while ( (ch = pszFile[off]) != '\0'
+ && ch != '\n')
+ off++;
+ off++;
+ iLine++;
+ }
+
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Import '%s' -> '%s' (%u exports)\n",
+ pModule->szModule, szPath, pModule->cExports);
+ }
+ else
+ RTMsgError("%s: %s: Invalid UTF-8 encoding in export file: %Rrc", pszImage, szPath, rc);
+ }
+ RTMemFree(pszFile);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s: Out of memory reading export file (%#RX64 bytes)",
+ pszImage, szPath, cbFile + 1);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: %s: Export file is too big: %#RX64 bytes, max 4MiB",
+ pszImage, szPath, cbFile);
+ }
+ else
+ RTMsgError("%s: %s: RTVfsFileQuerySize failed on export file: %Rrc", pszImage, szPath, rc);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+ }
+ else if ( rc != VERR_PATH_NOT_FOUND
+ && rc != VERR_FILE_NOT_FOUND)
+ RTVfsChainMsgError("RTVfsChainOpenFile", szPath, rc, offError, pErrInfo);
+ }
+ }
+
+ return RTMsgErrorRc(VERR_MODULE_NOT_FOUND, "%s: Import module '%s' was not found!", pszImage, pModule->szModule);
+}
+
+
+/**
+ * Checks the imports for the given image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pszImage The image to check.
+ */
+static int rtCheckImportsForImage(PCRTCHECKIMPORTSOPTS pOpts, const char *pszImage)
+{
+ if (pOpts->cVerbosity > 0)
+ RTMsgInfo("Checking '%s'...\n", pszImage);
+
+ /*
+ * Open the image.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpenVfsChain(pszImage, 0 /*fFlags*/, RTLDRARCH_WHATEVER, &hLdrMod, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (RT_FAILURE(rc) && RTErrInfoIsSet(&ErrInfo.Core))
+ return RTMsgErrorRc(rc, "Failed opening image '%s': %Rrc - %s", pszImage, rc, ErrInfo.Core.pszMsg);
+ return RTMsgErrorRc(rc, "Failed opening image '%s': %Rrc", pszImage, rc);
+ }
+
+ /*
+ * Do the import modules first.
+ */
+ uint32_t cImports = 0;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_IMPORT_COUNT, &cImports, sizeof(cImports));
+ if (RT_SUCCESS(rc))
+ {
+ RTCHECKIMPORTSTATE *pState = (RTCHECKIMPORTSTATE *)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCHECKIMPORTSTATE, aImports[cImports + 1]));
+ if (pState)
+ {
+ pState->pszImage = pszImage;
+ pState->pOpts = pOpts;
+ pState->cImports = cImports;
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ pState->aImports[iImport].hLdrMod = NIL_RTLDRMOD;
+
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ {
+ *(uint32_t *)&pState->aImports[iImport].szModule[0] = iImport;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_IMPORT_MODULE, pState->aImports[iImport].szModule,
+ sizeof(pState->aImports[iImport].szModule));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: Error querying import #%u: %Rrc", pszImage, iImport, rc);
+ break;
+ }
+ rc = LoadImportModule(pOpts, &pState->aImports[iImport], &ErrInfo.Core, pszImage);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the image bits, indirectly resolving imports.
+ */
+ size_t cbImage = RTLdrSize(hLdrMod);
+ void *pvImage = RTMemAllocZ(cbImage);
+ if (pvImage)
+ {
+ pState->iRc = VINF_SUCCESS;
+ rc = RTLdrGetBits(hLdrMod, pvImage, _4M, GetImportCallback, pState);
+ if (RT_SUCCESS(rc))
+ rc = pState->iRc;
+ else
+ RTMsgError("%s: RTLdrGetBits failed: %Rrc", pszImage, rc);
+
+ RTMemFree(pvImage);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: out of memory", pszImage);
+ }
+
+ for (uint32_t iImport = 0; iImport < cImports; iImport++)
+ if (pState->aImports[iImport].hLdrMod != NIL_RTLDRMOD)
+ {
+ RTLdrClose(pState->aImports[iImport].hLdrMod);
+
+ size_t i = pState->aImports[iImport].cExports;
+ while (i-- > 0)
+ RTStrFree(pState->aImports[iImport].papszExports[i]);
+ RTMemFree(pState->aImports[iImport].papszExports);
+ }
+ RTMemFree(pState);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: out of memory", pszImage);
+ }
+ else
+ RTMsgError("%s: Querying RTLDRPROP_IMPORT_COUNT failed: %Rrc", pszImage, rc);
+ RTLdrClose(hLdrMod);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRENUMSYMS}
+ */
+static DECLCALLBACK(int) PrintSymbolForExportList(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol,
+ RTLDRADDR Value, void *pvUser)
+{
+ if (pszSymbol)
+ RTPrintf("%s\n", pszSymbol);
+ if (uSymbol != ~(unsigned)0 && (!pszSymbol || ((PCRTCHECKIMPORTSOPTS)pvUser)->fListOrdinals))
+ RTPrintf("#%u\n", uSymbol);
+ RT_NOREF(hLdrMod, Value, pvUser);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Produces the export list for the given image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The check program options.
+ * @param pszImage Path to the image.
+ */
+static int ProduceExportList(PCRTCHECKIMPORTSOPTS pOpts, const char *pszImage)
+{
+ /*
+ * Open the image.
+ */
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpenVfsChain(pszImage, RTLDR_O_FOR_DEBUG, RTLDRARCH_WHATEVER, &hLdrMod, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Some info about the file.
+ */
+ RTPrintf(";\n"
+ "; Generated from: %s\n", pszImage);
+
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsChainQueryInfo(pszImage, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ RTPrintf("; Size file: %#RX64 (%RU64)\n", ObjInfo.cbObject, ObjInfo.cbObject);
+
+ switch (RTLdrGetFormat(hLdrMod))
+ {
+ case RTLDRFMT_AOUT: RTPrintf("; Format: a.out\n"); break;
+ case RTLDRFMT_ELF: RTPrintf("; Format: ELF\n"); break;
+ case RTLDRFMT_LX: RTPrintf("; Format: LX\n"); break;
+ case RTLDRFMT_MACHO: RTPrintf("; Format: Mach-O\n"); break;
+ case RTLDRFMT_PE: RTPrintf("; Format: PE\n"); break;
+ default: RTPrintf("; Format: %u\n", RTLdrGetFormat(hLdrMod)); break;
+
+ }
+
+ RTPrintf("; Size of image: %#x (%u)\n", RTLdrSize(hLdrMod), RTLdrSize(hLdrMod));
+
+ switch (RTLdrGetArch(hLdrMod))
+ {
+ case RTLDRARCH_AMD64: RTPrintf("; Architecture: AMD64\n"); break;
+ case RTLDRARCH_X86_32: RTPrintf("; Architecture: X86\n"); break;
+ default: RTPrintf("; Architecture: %u\n", RTLdrGetArch(hLdrMod)); break;
+ }
+
+ uint64_t uTimestamp;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp));
+ if (RT_SUCCESS(rc))
+ {
+ RTTIMESPEC Timestamp;
+ char szTime[128];
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Timestamp, uTimestamp), szTime, sizeof(szTime));
+ char *pszEnd = strchr(szTime, '\0');
+ while (pszEnd[0] != '.')
+ pszEnd--;
+ *pszEnd = '\0';
+ RTPrintf("; Timestamp: %#RX64 - %s\n", uTimestamp, szTime);
+ }
+
+ RTUUID ImageUuid;
+ rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_UUID, &ImageUuid, sizeof(ImageUuid));
+ if (RT_SUCCESS(rc))
+ RTPrintf("; UUID: %RTuuid\n", &ImageUuid);
+
+ RTPrintf(";\n");
+
+ /*
+ * The list of exports.
+ */
+ rc = RTLdrEnumSymbols(hLdrMod, 0 /*fFlags*/, NULL, _4M, PrintSymbolForExportList, (void *)pOpts);
+ if (RT_FAILURE(rc))
+ RTMsgError("%s: RTLdrEnumSymbols failed: %Rrc", pszImage, rc);
+
+ /* done */
+ RTLdrClose(hLdrMod);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ RTMsgError("Failed opening image '%s': %Rrc - %s", pszImage, rc, ErrInfo.Core.pszMsg);
+ else
+ RTMsgError("Failed opening image '%s': %Rrc", pszImage, rc);
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ RTCHECKIMPORTSOPTS Opts;
+ Opts.cPaths = 0;
+ Opts.papszPaths = NULL;
+ Opts.enmLdrArch = RTLDRARCH_WHATEVER;
+ Opts.cVerbosity = 1;
+ Opts.fListOrdinals = false;
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--path", 'p', RTGETOPT_REQ_STRING },
+ { "--export", 'e', RTGETOPT_REQ_STRING },
+ { "--list-ordinals", 'O', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+ RTGETOPTSTATE State;
+ rc = RTGetOptInit(&State, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTUNION ValueUnion;
+ while ((rc = RTGetOpt(&State, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'p':
+ if ((Opts.cPaths % 16) == 0)
+ {
+ void *pvNew = RTMemRealloc(Opts.papszPaths, sizeof(Opts.papszPaths[0]) * (Opts.cPaths + 16));
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ Opts.papszPaths = (char **)pvNew;
+ }
+ Opts.papszPaths[Opts.cPaths] = RTStrDup(ValueUnion.psz);
+ AssertReturn(Opts.papszPaths[Opts.cPaths], RTEXITCODE_FAILURE);
+ Opts.cPaths++;
+ break;
+
+ case 'e':
+ rc = ProduceExportList(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+
+ case 'O':
+ Opts.fListOrdinals = true;
+ break;
+
+ case 'q':
+ Opts.cVerbosity = 0;
+ break;
+
+ case 'v':
+ Opts.cVerbosity = 0;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ rc = rtCheckImportsForImage(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: RTCheckImports [-p|--path <dir>] [-v|--verbose] [-q|--quiet] <image [..]>\n"
+ " or: RTCheckImports -e <image>\n"
+ " or: RTCheckImports <-h|--help>\n"
+ " or: RTCheckImports <-V|--version>\n"
+ "Checks library imports. VFS chain syntax supported.\n"
+ "\n"
+ "Options:\n"
+ " -p, --path <dir>\n"
+ " Search the specified directory for imported modules or their export lists.\n"
+ " -e, --export <image>\n"
+ " Write export list for the file to stdout. (Redirect to a .export file.)\n"
+ " -O, --list-ordinals\n"
+ " Whether to list ordinals as well as names in the export list.\n"
+ " -q, --quiet\n"
+ " Quiet execution.\n"
+ " -v, --verbose\n"
+ " Increases verbosity.\n"
+ ""
+ );
+ return RTEXITCODE_SUCCESS;
+
+#ifndef IPRT_IN_BUILD_TOOL
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+#endif
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTLdrFlt.cpp b/src/VBox/Runtime/tools/RTLdrFlt.cpp
new file mode 100644
index 00000000..65a4faff
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLdrFlt.cpp
@@ -0,0 +1,445 @@
+/* $Id: RTLdrFlt.cpp $ */
+/** @file
+ * IPRT - Utility for translating addresses into symbols+offset.
+ */
+
+/*
+ * Copyright (C) 2006-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+
+/**
+ * Tries to parse out an address at the head of the string.
+ *
+ * @returns true if found address, false if not.
+ * @param psz Where to start parsing.
+ * @param pcchAddress Where to store the address length.
+ * @param pu64Address Where to store the address value.
+ */
+static bool TryParseAddress(const char *psz, size_t *pcchAddress, uint64_t *pu64Address)
+{
+ const char *pszStart = psz;
+
+ /*
+ * Hex prefix?
+ */
+ if (psz[0] == '0' && (psz[1] == 'x' || psz[1] == 'X'))
+ psz += 2;
+
+ /*
+ * How many hex digits? We want at least 4 and at most 16.
+ */
+ size_t off = 0;
+ while (RT_C_IS_XDIGIT(psz[off]))
+ off++;
+ if (off < 4 || off > 16)
+ return false;
+
+ /*
+ * Check for separator (xxxxxxxx'yyyyyyyy).
+ */
+ bool fHave64bitSep = off <= 8
+ && psz[off] == '\''
+ && RT_C_IS_XDIGIT(psz[off + 1])
+ && RT_C_IS_XDIGIT(psz[off + 2])
+ && RT_C_IS_XDIGIT(psz[off + 3])
+ && RT_C_IS_XDIGIT(psz[off + 4])
+ && RT_C_IS_XDIGIT(psz[off + 5])
+ && RT_C_IS_XDIGIT(psz[off + 6])
+ && RT_C_IS_XDIGIT(psz[off + 7])
+ && RT_C_IS_XDIGIT(psz[off + 8])
+ && !RT_C_IS_XDIGIT(psz[off + 9]);
+ if (fHave64bitSep)
+ {
+ uint32_t u32High;
+ int rc = RTStrToUInt32Ex(psz, NULL, 16, &u32High);
+ if (rc != VWRN_TRAILING_CHARS)
+ return false;
+
+ uint32_t u32Low;
+ rc = RTStrToUInt32Ex(&psz[off + 1], NULL, 16, &u32Low);
+ if ( rc != VINF_SUCCESS
+ && rc != VWRN_TRAILING_SPACES
+ && rc != VWRN_TRAILING_CHARS)
+ return false;
+
+ *pu64Address = RT_MAKE_U64(u32Low, u32High);
+ off += 1 + 8;
+ }
+ else
+ {
+ int rc = RTStrToUInt64Ex(psz, NULL, 16, pu64Address);
+ if ( rc != VINF_SUCCESS
+ && rc != VWRN_TRAILING_SPACES
+ && rc != VWRN_TRAILING_CHARS)
+ return false;
+ }
+
+ *pcchAddress = psz + off - pszStart;
+ return true;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Create an empty address space that we can load modules and stuff into
+ * as we parse the parameters.
+ */
+ RTDBGAS hDbgAs;
+ rc = RTDbgAsCreate(&hDbgAs, 0, RTUINTPTR_MAX, "");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDBgAsCreate -> %Rrc", rc);
+
+ /*
+ * Create a debugging configuration instance to work with so that we can
+ * make use of (i.e. test) path searching and such.
+ */
+ RTDBGCFG hDbgCfg;
+ rc = RTDbgCfgCreate(&hDbgCfg, "IPRT", true /*fNativePaths*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgCfgCreate -> %Rrc", rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--local-file", 'l', RTGETOPT_REQ_NOTHING },
+ { "--cache-file", 'c', RTGETOPT_REQ_NOTHING },
+ { "--pe-image", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--x86", '8', RTGETOPT_REQ_NOTHING },
+ { "--amd64", '6', RTGETOPT_REQ_NOTHING },
+ { "--whatever", '*', RTGETOPT_REQ_NOTHING },
+ };
+
+ PRTSTREAM pInput = g_pStdIn;
+ PRTSTREAM pOutput = g_pStdOut;
+ unsigned cVerbosityLevel = 0;
+ enum {
+ kOpenMethod_FromImage,
+ kOpenMethod_FromPeImage
+ } enmOpenMethod = kOpenMethod_FromImage;
+ bool fCacheFile = false;
+ RTLDRARCH enmArch = RTLDRARCH_WHATEVER;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'i':
+ rc = RTStrmOpen(ValueUnion.psz, "r", &pInput);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open '%s' for reading: %Rrc", ValueUnion.psz, rc);
+ break;
+
+ case 'c':
+ fCacheFile = true;
+ break;
+
+ case 'l':
+ fCacheFile = false;
+ break;
+
+ case 'p':
+ enmOpenMethod = kOpenMethod_FromPeImage;
+ break;
+
+ case 'v':
+ cVerbosityLevel++;
+ break;
+
+ case '8':
+ enmArch = RTLDRARCH_X86_32;
+ break;
+
+ case '6':
+ enmArch = RTLDRARCH_AMD64;
+ break;
+
+ case '*':
+ enmArch = RTLDRARCH_WHATEVER;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <module> <address> [<module> <address> [..]]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=file\n"
+ " Specify a input file instead of standard input.\n"
+ " --pe-image\n"
+ " Use RTDbgModCreateFromPeImage to open the file."
+ " -v, --verbose\n"
+ " Display the address space before doing the filtering.\n"
+ " --amd64,--x86,--whatever\n"
+ " Selects the desired architecture.\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("$Revision: 135976 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ /* <module> <address> */
+ const char *pszModule = ValueUnion.psz;
+
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ uint64_t u64Address = ValueUnion.u64;
+
+ uint32_t cbImage = 0;
+ uint32_t uTimestamp = 0;
+ if (fCacheFile)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ cbImage = ValueUnion.u32;
+
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX);
+ if (RT_FAILURE(rc))
+ return RTGetOptPrintError(rc, &ValueUnion);
+ uTimestamp = ValueUnion.u32;
+ }
+
+ RTDBGMOD hMod;
+ if (enmOpenMethod == kOpenMethod_FromImage)
+ rc = RTDbgModCreateFromImage(&hMod, pszModule, NULL, enmArch, hDbgCfg);
+ else
+ rc = RTDbgModCreateFromPeImage(&hMod, pszModule, NULL, NULL, cbImage, uTimestamp, hDbgCfg);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgModCreateFromImage(,%s,,) -> %Rrc", pszModule, rc);
+
+ rc = RTDbgAsModuleLink(hDbgAs, hMod, u64Address, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDbgAsModuleLink(,%s,%llx,) -> %Rrc", pszModule, u64Address, rc);
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Display the address space.
+ */
+ if (cVerbosityLevel)
+ {
+ RTPrintf("*** Address Space Dump ***\n");
+ uint32_t cModules = RTDbgAsModuleCount(hDbgAs);
+ for (uint32_t iModule = 0; iModule < cModules; iModule++)
+ {
+ RTDBGMOD hDbgMod = RTDbgAsModuleByIndex(hDbgAs, iModule);
+ RTPrintf("Module #%u: %s\n", iModule, RTDbgModName(hDbgMod));
+
+ RTDBGASMAPINFO aMappings[128];
+ uint32_t cMappings = RT_ELEMENTS(aMappings);
+ rc = RTDbgAsModuleQueryMapByIndex(hDbgAs, iModule, &aMappings[0], &cMappings, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t iMapping = 0; iMapping < cMappings; iMapping++)
+ {
+ if (aMappings[iMapping].iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTPrintf(" mapping #%u: %RTptr-%RTptr\n",
+ iMapping,
+ aMappings[iMapping].Address,
+ aMappings[iMapping].Address + RTDbgModImageSize(hDbgMod) - 1);
+ if (cVerbosityLevel > 2)
+ {
+ uint32_t cSegments = RTDbgModSegmentCount(hDbgMod);
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hDbgMod, iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" seg #%u: %RTptr LB %RTptr '%s'\n",
+ iSeg, SegInfo.uRva, SegInfo.cb, SegInfo.szName);
+ else
+ RTPrintf(" seg #%u: %Rrc\n", iSeg, rc);
+ }
+ }
+ }
+ else
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hDbgMod, aMappings[iMapping].iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" mapping #%u: %RTptr-%RTptr (segment #%u - '%s')\n",
+ iMapping,
+ aMappings[iMapping].Address,
+ aMappings[iMapping].Address + SegInfo.cb,
+ SegInfo.iSeg, SegInfo.szName);
+ else
+ RTPrintf(" mapping #%u: %RTptr-???????? (segment #%u) rc=%Rrc\n",
+ iMapping, aMappings[iMapping].Address, aMappings[iMapping].iSeg, rc);
+ }
+
+ if (cVerbosityLevel > 1)
+ {
+ uint32_t cSymbols = RTDbgModSymbolCount(hDbgMod);
+ RTPrintf(" %u symbols\n", cSymbols);
+ for (uint32_t iSymbol = 0; iSymbol < cSymbols; iSymbol++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByOrdinal(hDbgMod, iSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ RTPrintf(" #%04u at %08x:%RTptr (%RTptr) %05llx %s\n",
+ SymInfo.iOrdinal, SymInfo.iSeg, SymInfo.offSeg, SymInfo.Value,
+ (uint64_t)SymInfo.cb, SymInfo.szName);
+ }
+ }
+ }
+ }
+ else
+ RTMsgError("RTDbgAsModuleQueryMapByIndex failed: %Rrc", rc);
+ RTDbgModRelease(hDbgMod);
+ }
+ RTPrintf("*** End of Address Space Dump ***\n");
+ }
+
+ /*
+ * Read text from standard input and see if there is anything we can translate.
+ */
+ for (;;)
+ {
+ /* Get a line. */
+ char szLine[_64K];
+ rc = RTStrmGetLine(pInput, szLine, sizeof(szLine));
+ if (rc == VERR_EOF)
+ break;
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmGetLine() -> %Rrc\n", rc);
+
+ /*
+ * Search the line for potential addresses and replace them with
+ * symbols+offset.
+ */
+ const char *pszStart = szLine;
+ const char *psz = szLine;
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ size_t cchAddress;
+ uint64_t u64Address;
+
+ if ( ( ch == '0'
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && TryParseAddress(psz, &cchAddress, &u64Address))
+ || ( RT_C_IS_XDIGIT(ch)
+ && TryParseAddress(psz, &cchAddress, &u64Address))
+ )
+ {
+ /* Print. */
+ psz += cchAddress;
+ if (pszStart != psz)
+ RTStrmWrite(pOutput, pszStart, psz - pszStart);
+ pszStart = psz;
+
+ /* Try get the module. */
+ RTUINTPTR uAddr;
+ RTDBGSEGIDX iSeg;
+ RTDBGMOD hDbgMod;
+ rc = RTDbgAsModuleByAddr(hDbgAs, u64Address, &hDbgMod, &uAddr, &iSeg);
+ if (RT_SUCCESS(rc))
+ {
+ if (iSeg != UINT32_MAX)
+ RTStrmPrintf(pOutput, "=[%s:%u", RTDbgModName(hDbgMod), iSeg);
+ else
+ RTStrmPrintf(pOutput, "=[%s", RTDbgModName(hDbgMod));
+
+ /*
+ * Do we have symbols?
+ */
+ RTDBGSYMBOL Symbol;
+ RTINTPTR offSym;
+ rc = RTDbgAsSymbolByAddr(hDbgAs, u64Address, RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL, &offSym, &Symbol, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!offSym)
+ RTStrmPrintf(pOutput, "!%s", Symbol.szName);
+ else if (offSym > 0)
+ RTStrmPrintf(pOutput, "!%s+%#llx", Symbol.szName, offSym);
+ else
+ RTStrmPrintf(pOutput, "!%s-%#llx", Symbol.szName, -offSym);
+ }
+ else
+ RTStrmPrintf(pOutput, "+%#llx", u64Address - uAddr);
+
+ /*
+ * Do we have line numbers?
+ */
+ RTDBGLINE Line;
+ RTINTPTR offLine;
+ rc = RTDbgAsLineByAddr(hDbgAs, u64Address, &offLine, &Line, NULL);
+ if (RT_SUCCESS(rc))
+ RTStrmPrintf(pOutput, " %Rbn(%u)", Line.szFilename, Line.uLineNo);
+
+ RTStrmPrintf(pOutput, "]");
+ RTDbgModRelease(hDbgMod);
+ }
+ }
+ else
+ psz++;
+ }
+
+ if (pszStart != psz)
+ RTStrmWrite(pOutput, pszStart, psz - pszStart);
+ RTStrmPutCh(pOutput, '\n');
+
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/tools/RTLs.cpp b/src/VBox/Runtime/tools/RTLs.cpp
new file mode 100644
index 00000000..df35b746
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTLs.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTLs.cpp $ */
+/** @file
+ * IPRT - /bin/ls like utility for testing the VFS code.
+ */
+
+/*
+ * Copyright (C) 2017-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/errcore.h>
+#include <iprt/fs.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTFsCmdLs(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTManifest.cpp b/src/VBox/Runtime/tools/RTManifest.cpp
new file mode 100644
index 00000000..fef422db
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTManifest.cpp
@@ -0,0 +1,394 @@
+/* $Id: RTManifest.cpp $ */
+/** @file
+ * IPRT - Manifest Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/manifest.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/**
+ * Verify a manifest.
+ *
+ * @returns Program exit code, failures with error message.
+ * @param pszManifest The manifest file. NULL if standard input.
+ * @param fStdFormat Whether to expect standard format (true) or
+ * java format (false).
+ * @param pszChDir The directory to change into before processing
+ * the files in the manifest.
+ */
+static RTEXITCODE rtManifestDoVerify(const char *pszManifest, bool fStdFormat, const char *pszChDir)
+{
+ RT_NOREF_PV(pszChDir); /** @todo implement pszChDir! */
+
+ /*
+ * Open the manifest.
+ */
+ int rc;
+ RTVFSIOSTREAM hVfsIos;
+ if (!pszManifest)
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, RTFILE_O_READ, false /*fLeaveOpen*/, &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard input for reading: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszManifest, rc, offError, &ErrInfo.Core);
+ }
+
+ /*
+ * Read it.
+ */
+ RTMANIFEST hManifest;
+ rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
+ if (RT_SUCCESS(rc))
+ {
+ if (fStdFormat)
+ {
+ char szErr[4096 + 1024];
+ rc = RTManifestReadStandardEx(hManifest, hVfsIos, szErr, sizeof(szErr));
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Do the verification.
+ */
+ /** @todo We're missing some enumeration APIs here! */
+ RTMsgError("The manifest read fine, but the actual verification code is yet to be written. Sorry.");
+ rc = VERR_NOT_IMPLEMENTED;
+#if 1 /* For now, just write the manifest to stdout so we can test the read routine. */
+ RTVFSIOSTREAM hVfsIosOut;
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIosOut);
+ if (RT_SUCCESS(rc))
+ {
+ RTManifestWriteStandard(hManifest, hVfsIosOut);
+ RTVfsIoStrmRelease(hVfsIosOut);
+ }
+#endif
+ }
+ else if (szErr[0])
+ RTMsgError("Error reading manifest: %s", szErr);
+ else
+ RTMsgError("Error reading manifest: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("Support for Java manifest files is not implemented yet");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ RTManifestRelease(hManifest);
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Adds a file to the manifest.
+ *
+ * @returns IPRT status code, failures with error message.
+ * @param hManifest The manifest to add it to.
+ * @param pszFilename The name of the file to add.
+ * @param fAttr The manifest attributes to add.
+ */
+static int rtManifestAddFileToManifest(RTMANIFEST hManifest, const char *pszFilename, uint32_t fAttr)
+{
+ RTVFSIOSTREAM hVfsIos;
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTVfsChainOpenIoStream(pszFilename, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ RTVfsChainMsgError("RTVfsChainOpenIoStream", pszFilename, rc, offError, &ErrInfo.Core);
+ return rc;
+ }
+
+ rc = RTManifestEntryAddIoStream(hManifest, hVfsIos, pszFilename, fAttr);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTManifestEntryAddIoStream failed for '%s': %Rrc", pszFilename, rc);
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return rc;
+}
+
+
+/**
+ * Create a manifest from the specified input files.
+ *
+ * @returns Program exit code, failures with error message.
+ * @param pszManifest The name of the output manifest file. NULL if
+ * it should be written to standard output.
+ * @param fStdFormat Whether to expect standard format (true) or
+ * java format (false).
+ * @param pszChDir The directory to change into before processing
+ * the file arguments.
+ * @param fAttr The file attributes to put in the manifest.
+ * @param pGetState The RTGetOpt state.
+ * @param pUnion What the last RTGetOpt() call returned.
+ * @param chOpt What the last RTGetOpt() call returned.
+ */
+static RTEXITCODE rtManifestDoCreate(const char *pszManifest, bool fStdFormat, const char *pszChDir, uint32_t fAttr,
+ PRTGETOPTSTATE pGetState, PRTGETOPTUNION pUnion, int chOpt)
+{
+ /*
+ * Open the manifest file.
+ */
+ int rc;
+ RTVFSIOSTREAM hVfsIos;
+ if (!pszManifest)
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, false /*fLeaveOpen*/, &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard output for writing: %Rrc", rc);
+ }
+ else
+ {
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ rc = RTVfsChainOpenIoStream(pszManifest, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszManifest, rc, offError, &ErrInfo.Core);
+ }
+
+ /*
+ * Create the internal manifest.
+ */
+ RTMANIFEST hManifest;
+ rc = RTManifestCreate(0 /*fFlags*/, &hManifest);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Change directory and start processing the specified files.
+ */
+ if (pszChDir)
+ {
+ rc = RTPathSetCurrent(pszChDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to change directory to '%s': %Rrc", pszChDir, rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while (chOpt == VINF_GETOPT_NOT_OPTION)
+ {
+ rc = rtManifestAddFileToManifest(hManifest, pUnion->psz, fAttr);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* next */
+ chOpt = RTGetOpt(pGetState, pUnion);
+ }
+ if (RT_SUCCESS(rc) && chOpt != 0)
+ {
+ RTGetOptPrintError(chOpt, pUnion);
+ rc = chOpt < 0 ? chOpt : -chOpt;
+ }
+ }
+
+ /*
+ * Write the manifest.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (fStdFormat)
+ {
+ rc = RTManifestWriteStandard(hManifest, hVfsIos);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTManifestWriteStandard failed: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("Support for Java manifest files is not implemented yet");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+
+ RTManifestRelease(hManifest);
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static RTGETOPTDEF const s_aOptions[] =
+ {
+ { "--manifest", 'm', RTGETOPT_REQ_STRING },
+ { "--java", 'j', RTGETOPT_REQ_NOTHING },
+ { "--chdir", 'C', RTGETOPT_REQ_STRING },
+ { "--attribute", 'a', RTGETOPT_REQ_STRING },
+ { "--verify", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ bool fVerify = false;
+ bool fStdFormat = true;
+ const char *pszManifest = NULL;
+ const char *pszChDir = NULL;
+ uint32_t fAttr = RTMANIFEST_ATTR_UNKNOWN;
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc", rc);
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'a':
+ {
+ static struct
+ {
+ const char *pszAttr;
+ uint32_t fAttr;
+ } s_aAttributes[] =
+ {
+ { "size", RTMANIFEST_ATTR_SIZE },
+ { "md5", RTMANIFEST_ATTR_MD5 },
+ { "sha1", RTMANIFEST_ATTR_SHA1 },
+ { "sha256", RTMANIFEST_ATTR_SHA256 },
+ { "sha512", RTMANIFEST_ATTR_SHA512 }
+ };
+ uint32_t fThisAttr = RTMANIFEST_ATTR_UNKNOWN;
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aAttributes); i++)
+ if (!RTStrICmp(s_aAttributes[i].pszAttr, ValueUnion.psz))
+ {
+ fThisAttr = s_aAttributes[i].fAttr;
+ break;
+ }
+ if (fThisAttr == RTMANIFEST_ATTR_UNKNOWN)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown attribute type '%s'", ValueUnion.psz);
+
+ if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
+ fAttr = fThisAttr;
+ else
+ fAttr |= fThisAttr;
+ break;
+ }
+
+ case 'j':
+ fStdFormat = false;
+ break;
+
+ case 'm':
+ if (pszManifest)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one manifest can be specified");
+ pszManifest = ValueUnion.psz;
+ break;
+
+ case 'v':
+ fVerify = true;
+ break;
+
+ case 'C':
+ if (pszChDir)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Only one directory change can be specified");
+ pszChDir = ValueUnion.psz;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [--manifest <file>] [--chdir <dir>] [--attribute <attrib-name> [..]] <files>\n"
+ " or %s --verify [--manifest <file>] [--chdir <dir>]\n"
+ "\n"
+ "attrib-name: size, md5, sha1, sha256 or sha512\n"
+ , RTProcShortName(), RTProcShortName());
+ return RTEXITCODE_SUCCESS;
+
+#ifndef IN_BLD_PROG /* RTBldCfgVersion or RTBldCfgRevision in build time IPRT lib. */
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+#endif
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Take action.
+ */
+ RTEXITCODE rcExit;
+ if (!fVerify)
+ {
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ RTMsgWarning("No files specified, the manifest will be empty.");
+ if (fAttr == RTMANIFEST_ATTR_UNKNOWN)
+ fAttr = RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_MD5
+ | RTMANIFEST_ATTR_SHA1 | RTMANIFEST_ATTR_SHA256 | RTMANIFEST_ATTR_SHA512;
+ rcExit = rtManifestDoCreate(pszManifest, fStdFormat, pszChDir, fAttr, &GetState, &ValueUnion, rc);
+ }
+ else
+ {
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "No files should be specified when verifying a manifest (--verfiy), "
+ "only a manifest via the --manifest option");
+ if (fAttr != RTMANIFEST_ATTR_UNKNOWN)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "The --attribute (-a) option does not combine with --verify (-v)");
+
+
+ rcExit = rtManifestDoVerify(pszManifest, fStdFormat, pszChDir);
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTMkDir.cpp b/src/VBox/Runtime/tools/RTMkDir.cpp
new file mode 100644
index 00000000..8f452fff
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTMkDir.cpp
@@ -0,0 +1,369 @@
+/* $Id: RTMkDir.cpp $ */
+/** @file
+ * IPRT - Creates directory.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+#include <iprt/vfs.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTCMDMKDIROPTS
+{
+ /** -v, --verbose */
+ bool fVerbose;
+ /** -p, --parents */
+ bool fParents;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+ /** Directory creation flags (RTDIRCREATE_FLAGS_XXX). */
+ uint32_t fCreateFlags;
+ /** The directory mode. */
+ RTFMODE fMode;
+} RTCMDMKDIROPTS;
+
+
+/**
+ * Create one directory and any missing parent directories.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static int rtCmdMkDirOneWithParents(RTCMDMKDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ {
+ /*
+ * Use the API for doing the entire job. Unfortuantely, this means we
+ * can't be very verbose about what we're doing.
+ */
+ rc = RTDirCreateFullPath(pszDir, pOpts->fMode);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to create directory '%s' (or a parent): %Rrc", pszDir, rc);
+ else if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ }
+ else
+ {
+ /*
+ * Strip the final path element from the pszDir spec.
+ */
+ char *pszCopy = RTStrDup(pszDir);
+ if (!pszCopy)
+ return RTMsgErrorExitFailure("Out of string memory!");
+
+ char *pszFinalPath;
+ char *pszSpec;
+ uint32_t offError;
+ rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ {
+ const char * const pszFullFinalPath = pszFinalPath;
+
+ /*
+ * Open the root director/whatever.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSDIR hVfsCurDir;
+ if (pszSpec)
+ {
+ rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsCurDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core);
+ else if (!pszFinalPath)
+ pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX);
+ }
+ else if (!RTPathStartsWithRoot(pszFinalPath))
+ {
+ rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsCurDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath);
+ }
+ else
+ {
+ char *pszRoot = pszFinalPath;
+ pszFinalPath = RTPathSkipRootSpec(pszFinalPath);
+ char const chSaved = *pszFinalPath;
+ *pszFinalPath = '\0';
+ rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsCurDir);
+ *pszFinalPath = chSaved;
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot);
+ }
+
+ /*
+ * Walk the path component by component.
+ */
+ while (RT_SUCCESS(rc))
+ {
+ /*
+ * Strip leading slashes.
+ */
+ while (RTPATH_IS_SLASH(*pszFinalPath))
+ pszFinalPath++;
+ if (*pszFinalPath == '\0')
+ {
+ RTVfsDirRelease(hVfsCurDir);
+ break;
+ }
+
+ /*
+ * Find the end of the next path component.
+ */
+ size_t cchComponent = 0;
+ char ch;
+ while ( (ch = pszFinalPath[cchComponent]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+
+ /*
+ * Open or create the component.
+ */
+ pszFinalPath[cchComponent] = '\0';
+ RTVFSDIR hVfsNextDir = NIL_RTVFSDIR;
+ for (uint32_t cTries = 0; cTries < 8; cTries++)
+ {
+ /* Try open it. */
+ rc = RTVfsDirOpenDir(hVfsCurDir, pszFinalPath, 0 /*fFlags*/, &hVfsNextDir);
+ if (RT_SUCCESS(rc))
+ break;
+ if ( rc != VERR_FILE_NOT_FOUND
+ && rc != VERR_PATH_NOT_FOUND)
+ {
+ if (ch == '\0')
+ RTMsgError("Failed opening directory '%s': %Rrc", pszDir, rc);
+ else
+ RTMsgError("Failed opening dir '%s' (for creating '%s'): %Rrc", pszFullFinalPath, pszDir, rc);
+ break;
+ }
+
+ /* Not found, so try create it. */
+ rc = RTVfsDirCreateDir(hVfsCurDir, pszFinalPath, pOpts->fMode, pOpts->fCreateFlags, &hVfsNextDir);
+ if (rc == VERR_ALREADY_EXISTS)
+ continue; /* We lost a creation race, try again. */
+ if (RT_SUCCESS(rc) && pOpts->fVerbose)
+ {
+ if (pszSpec)
+ RTPrintf("%s:%s\n", pszSpec, pszFullFinalPath);
+ else
+ RTPrintf("%s\n", pszFullFinalPath);
+ }
+ else if (RT_FAILURE(rc))
+ {
+ if (ch == '\0')
+ RTMsgError("Failed creating directory '%s': %Rrc", pszDir, rc);
+ else
+ RTMsgError("Failed creating dir '%s' (for '%s'): %Rrc", pszFullFinalPath, pszDir, rc);
+ }
+ break;
+ }
+ pszFinalPath[cchComponent] = ch;
+
+ RTVfsDirRelease(hVfsCurDir);
+ hVfsCurDir = hVfsNextDir;
+ pszFinalPath += cchComponent;
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL);
+ RTStrFree(pszCopy);
+ }
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Create one directory.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static RTEXITCODE rtCmdMkDirOne(RTCMDMKDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ rc = RTDirCreate(pszDir, pOpts->fMode, 0);
+ else
+ {
+ RTVFSDIR hVfsDir;
+ const char *pszChild;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsDirCreateDir(hVfsDir, pszChild, pOpts->fMode, 0 /*fFlags*/, NULL);
+ RTVfsDirRelease(hVfsDir);
+ }
+ else
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("Failed to create '%s': %Rrc", pszDir, rc);
+}
+
+
+static RTEXITCODE RTCmdMkDir(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--mode", 'm', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+ { "--allow-content-indexing", 'i', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDMKDIROPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fParents = false;
+ Opts.fAlwaysUseChainApi = false;
+ Opts.fCreateFlags = RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET;
+ Opts.fMode = 0775 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'm':
+ /** @todo DOS+NT attributes and symbolic notation. */
+ Opts.fMode &= ~07777;
+ Opts.fMode |= ValueUnion.u32 & 07777;
+ break;
+
+ case 'p':
+ Opts.fParents = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'i':
+ Opts.fCreateFlags &= ~RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET;
+ Opts.fCreateFlags |= RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <dir> [..]\n"
+ "\n"
+ "Options:\n"
+ " -m <mode>, --mode <mode>\n"
+ " The creation mode. Default is 0775.\n"
+ " -p, --parent\n"
+ " Create parent directories too. Ignore any existing directories.\n"
+ " -v, --verbose\n"
+ " Tell which directories get created.\n"
+ " -A, --always-use-vfs-chain-api\n"
+ " Always use the VFS API.\n"
+ " -i, --allow-content-indexing\n"
+ " Don't set flags to disable context indexing on windows.\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fParents)
+ rc = rtCmdMkDirOneWithParents(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdMkDirOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdMkDir(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTNtDbgHelp.cpp b/src/VBox/Runtime/tools/RTNtDbgHelp.cpp
new file mode 100644
index 00000000..dcad1cc1
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTNtDbgHelp.cpp
@@ -0,0 +1,383 @@
+/* $Id: RTNtDbgHelp.cpp $ */
+/** @file
+ * IPRT - RTNtDbgHelp - Tool for working/exploring DbgHelp.dll.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/win/windows.h>
+#include <iprt/win/dbghelp.h>
+
+#include <iprt/alloca.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/env.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+#include <iprt/win/lazy-dbghelp.h>
+
+#include <iprt/ldrlazy.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug module record.
+ *
+ * Used for dumping the whole context.
+ */
+typedef struct RTNTDBGHELPMOD
+{
+ /** The list bits. */
+ RTLISTNODE ListEntry;
+ /** The module address. */
+ uint64_t uModAddr;
+ /** Pointer to the name part of szFullName. */
+ char *pszName;
+ /** The module name. */
+ char szFullName[1];
+} RTNTDBGHELPMOD;
+/** Pointer to a debug module. */
+typedef RTNTDBGHELPMOD *PRTNTDBGHELPMOD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Verbosity level. */
+static int g_iOptVerbose = 1;
+
+/** Fake process handle. */
+static HANDLE g_hFake = (HANDLE)0x1234567;
+/** Number of modules in the list. */
+static uint32_t g_cModules = 0;
+/** Module list. */
+static RTLISTANCHOR g_ModuleList;
+/** Set when initialized, clear until then. Lazy init on first operation. */
+static bool g_fInitialized = false;
+
+/** The current address register. */
+static uint64_t g_uCurAddress = 0;
+
+
+
+/**
+ * For debug/verbose output.
+ *
+ * @param iMin The minimum verbosity level for this message.
+ * @param pszFormat The format string.
+ * @param ... The arguments referenced in the format string.
+ */
+static void infoPrintf(int iMin, const char *pszFormat, ...)
+{
+ if (g_iOptVerbose >= iMin)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintf("info: ");
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+static BOOL CALLBACK symDebugCallback64(HANDLE hProcess, ULONG uAction, ULONG64 ullData, ULONG64 ullUserCtx)
+{
+ NOREF(hProcess); NOREF(ullUserCtx);
+ switch (uAction)
+ {
+ case CBA_DEBUG_INFO:
+ {
+ const char *pszMsg = (const char *)(uintptr_t)ullData;
+ size_t cchMsg = strlen(pszMsg);
+ if (cchMsg > 0 && pszMsg[cchMsg - 1] == '\n')
+ RTPrintf("cba_debug_info: %s", pszMsg);
+ else
+ RTPrintf("cba_debug_info: %s\n", pszMsg);
+ return TRUE;
+ }
+
+ case CBA_DEFERRED_SYMBOL_LOAD_CANCEL:
+ return FALSE;
+
+ case CBA_EVENT:
+ return FALSE;
+
+ default:
+ RTPrintf("cba_???: uAction=%#x ullData=%#llx\n", uAction, ullData);
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Lazy initialization.
+ * @returns Exit code with any relevant complaints printed.
+ */
+static RTEXITCODE ensureInitialized(void)
+{
+ if (!g_fInitialized)
+ {
+ if (!SymInitialize(g_hFake, NULL, FALSE))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymInitialied failed: %u\n", GetLastError());
+ if (!SymRegisterCallback64(g_hFake, symDebugCallback64, 0))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymRegisterCallback64 failed: %u\n", GetLastError());
+ g_fInitialized = true;
+ infoPrintf(2, "SymInitialized(,,)\n");
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Loads the given module, the address is either automatic or a previously given
+ * one.
+ *
+ * @returns Exit code with any relevant complaints printed.
+ * @param pszFile The file to load.
+ */
+static RTEXITCODE loadModule(const char *pszFile)
+{
+ RTEXITCODE rcExit = ensureInitialized();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ uint64_t uModAddrReq = g_uCurAddress == 0 ? UINT64_C(0x1000000) * g_cModules : g_uCurAddress;
+ uint64_t uModAddrGot = SymLoadModuleEx(g_hFake, NULL /*hFile*/, pszFile, NULL /*pszModuleName*/,
+ uModAddrReq, 0, NULL /*pData*/, 0 /*fFlags*/);
+ if (uModAddrGot == 0)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymLoadModuleEx failed: %u\n", GetLastError());
+
+ size_t cbFullName = strlen(pszFile) + 1;
+ PRTNTDBGHELPMOD pMod = (PRTNTDBGHELPMOD)RTMemAlloc(RT_UOFFSETOF_DYN(RTNTDBGHELPMOD, szFullName[cbFullName + 1]));
+ memcpy(pMod->szFullName, pszFile, cbFullName);
+ pMod->pszName = RTPathFilename(pMod->szFullName);
+ pMod->uModAddr = uModAddrGot;
+ RTListAppend(&g_ModuleList, &pMod->ListEntry);
+ infoPrintf(1, "%#018RX64 %s\n", pMod->uModAddr, pMod->pszName);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Translates SYM_TYPE to string.
+ *
+ * @returns String.
+ * @param enmType The symbol type value.
+ */
+static const char *symTypeName(SYM_TYPE enmType)
+{
+ switch (enmType)
+ {
+ case SymCoff: return "SymCoff";
+ case SymCv: return "SymCv";
+ case SymPdb: return "SymPdb";
+ case SymExport: return "SymExport";
+ case SymDeferred: return "SymDeferred";
+ case SymSym: return "SymSym";
+ case SymDia: return "SymDia";
+ case SymVirtual: return "SymVirtual";
+ default:
+ {
+ static char s_szBuf[32];
+ RTStrPrintf(s_szBuf, sizeof(s_szBuf), "Unknown-%#x", enmType);
+ return s_szBuf;
+ }
+ }
+}
+
+
+/**
+ * Symbol enumeration callback.
+ *
+ * @returns TRUE (continue enum).
+ * @param pSymInfo The symbol info.
+ * @param cbSymbol The symbol length (calculated).
+ * @param pvUser NULL.
+ */
+static BOOL CALLBACK dumpSymbolCallback(PSYMBOL_INFO pSymInfo, ULONG cbSymbol, PVOID pvUser)
+{
+ NOREF(pvUser);
+ RTPrintf(" %#018RX64 LB %#07x %s\n", pSymInfo->Address, cbSymbol, pSymInfo->Name);
+ return TRUE;
+}
+
+/**
+ * Dumps all info.
+ * @returns Exit code with any relevant complaints printed.
+ */
+static RTEXITCODE dumpAll(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ PRTNTDBGHELPMOD pMod;
+ RTListForEach(&g_ModuleList, pMod, RTNTDBGHELPMOD, ListEntry)
+ {
+ RTPrintf("*** %#018RX64 - %s ***\n", pMod->uModAddr, pMod->szFullName);
+
+ static const int8_t s_acbVariations[] = { 0, -4, -8, -12, -16, -20, -24, -28, -32, 4, 8, 12, 16, 20, 24, 28, 32 };
+ unsigned iVariation = 0;
+ union
+ {
+ IMAGEHLP_MODULE64 ModInfo;
+ uint8_t abPadding[sizeof(IMAGEHLP_MODULE64) + 64];
+ } u;
+
+ BOOL fRc;
+ do
+ {
+ RT_ZERO(u.ModInfo);
+ u.ModInfo.SizeOfStruct = sizeof(u.ModInfo) + s_acbVariations[iVariation++];
+ fRc = SymGetModuleInfo64(g_hFake, pMod->uModAddr, &u.ModInfo);
+ } while (!fRc && GetLastError() == ERROR_INVALID_PARAMETER && iVariation < RT_ELEMENTS(s_acbVariations));
+
+ if (fRc)
+ {
+ RTPrintf(" BaseOfImage = %#018llx\n", u.ModInfo.BaseOfImage);
+ RTPrintf(" ImageSize = %#010x\n", u.ModInfo.ImageSize);
+ RTPrintf(" TimeDateStamp = %#010x\n", u.ModInfo.TimeDateStamp);
+ RTPrintf(" CheckSum = %#010x\n", u.ModInfo.CheckSum);
+ RTPrintf(" NumSyms = %#010x (%u)\n", u.ModInfo.NumSyms, u.ModInfo.NumSyms);
+ RTPrintf(" SymType = %s\n", symTypeName(u.ModInfo.SymType));
+ RTPrintf(" ModuleName = %.32s\n", u.ModInfo.ModuleName);
+ RTPrintf(" ImageName = %.256s\n", u.ModInfo.ImageName);
+ RTPrintf(" LoadedImageName = %.256s\n", u.ModInfo.LoadedImageName);
+ RTPrintf(" LoadedPdbName = %.256s\n", u.ModInfo.LoadedPdbName);
+ RTPrintf(" CVSig = %#010x\n", u.ModInfo.CVSig);
+ /** @todo CVData. */
+ RTPrintf(" PdbSig = %#010x\n", u.ModInfo.PdbSig);
+ RTPrintf(" PdbSig70 = %RTuuid\n", &u.ModInfo.PdbSig70);
+ RTPrintf(" PdbAge = %#010x\n", u.ModInfo.PdbAge);
+ RTPrintf(" PdbUnmatched = %RTbool\n", u.ModInfo.PdbUnmatched);
+ RTPrintf(" DbgUnmatched = %RTbool\n", u.ModInfo.DbgUnmatched);
+ RTPrintf(" LineNumbers = %RTbool\n", u.ModInfo.LineNumbers);
+ RTPrintf(" GlobalSymbols = %RTbool\n", u.ModInfo.GlobalSymbols);
+ RTPrintf(" TypeInfo = %RTbool\n", u.ModInfo.TypeInfo);
+ RTPrintf(" SourceIndexed = %RTbool\n", u.ModInfo.SourceIndexed);
+ RTPrintf(" Publics = %RTbool\n", u.ModInfo.Publics);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymGetModuleInfo64 failed: %u\n", GetLastError());
+
+ if (!SymEnumSymbols(g_hFake, pMod->uModAddr, NULL, dumpSymbolCallback, NULL))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymEnumSymbols failed: %u\n", GetLastError());
+
+ }
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ RTListInit(&g_ModuleList);
+
+ /*
+ * Parse options.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--dump-all", 'd', RTGETOPT_REQ_NOTHING },
+ { "--load", 'l', RTGETOPT_REQ_STRING },
+ { "--set-address", 'a', RTGETOPT_REQ_UINT64 },
+#define OPT_SET_DEBUG_INFO 0x1000
+ { "--set-debug-info", OPT_SET_DEBUG_INFO, RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ //const char *pszOutput = "-";
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'v':
+ g_iOptVerbose++;
+ break;
+
+ case 'q':
+ g_iOptVerbose++;
+ break;
+
+ case 'l':
+ rcExit = loadModule(ValueUnion.psz);
+ break;
+
+ case 'a':
+ g_uCurAddress = ValueUnion.u64;
+ break;
+
+ case 'd':
+ rcExit = dumpAll();
+ break;
+
+ case OPT_SET_DEBUG_INFO:
+ rcExit = ensureInitialized();
+ if (rcExit == RTEXITCODE_SUCCESS && !SymSetOptions(SymGetOptions() | SYMOPT_DEBUG))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymSetOptions failed: %u\n", GetLastError());
+ break;
+
+
+ case 'V':
+ RTPrintf("$Revision: 143400 $");
+ break;
+
+ case 'h':
+ RTPrintf("usage: %s [-v|--verbose] [-q|--quiet] [--set-debug-info] [-a <addr>] [-l <file>] [-d] [...]\n"
+ " or: %s [-V|--version]\n"
+ " or: %s [-h|--help]\n",
+ argv[0], argv[0], argv[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTRm.cpp b/src/VBox/Runtime/tools/RTRm.cpp
new file mode 100644
index 00000000..3f24b0f0
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTRm.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTRm.cpp $ */
+/** @file
+ * IPRT - Remove Directory Entries Utility.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTPathRmCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTRmDir.cpp b/src/VBox/Runtime/tools/RTRmDir.cpp
new file mode 100644
index 00000000..8c7d029d
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTRmDir.cpp
@@ -0,0 +1,359 @@
+/* $Id: RTRmDir.cpp $ */
+/** @file
+ * IPRT - Removes directory.
+ */
+
+/*
+ * Copyright (C) 2013-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+#include <iprt/vfs.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/getopt.h>
+#include <iprt/buildconfig.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTCMDRMDIROPTS
+{
+ /** -v, --verbose */
+ bool fVerbose;
+ /** -p, --parents */
+ bool fParents;
+ /** Don't fail if directories that aren't empty. */
+ bool fIgnoreNotEmpty;
+ /** Don't fail a directory doesn't exist (i.e. has already been removed). */
+ bool fIgnoreNonExisting;
+ /** Whether to always use the VFS chain API (for testing). */
+ bool fAlwaysUseChainApi;
+} RTCMDRMDIROPTS;
+
+
+/**
+ * Create one directory and any missing parent directories.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static int rtCmdRmDirOneWithParents(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
+{
+ /* We need a copy we can work with here. */
+ char *pszCopy = RTStrDup(pszDir);
+ if (!pszCopy)
+ return RTMsgErrorExitFailure("Out of string memory!");
+
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ {
+ size_t cchCopy = strlen(pszCopy);
+ do
+ {
+ rc = RTDirRemove(pszCopy);
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszCopy);
+ }
+ else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ rc = VINF_SUCCESS;
+ else
+ {
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ rc = VINF_SUCCESS;
+ else
+ RTMsgError("Failed to remove directory '%s': %Rrc", pszCopy, rc);
+ break;
+ }
+
+ /* Strip off a component. */
+ while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ while (cchCopy > 0 && !RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ while (cchCopy > 0 && RTPATH_IS_SLASH(pszCopy[cchCopy - 1]))
+ cchCopy--;
+ pszCopy[cchCopy] = '\0';
+ } while (cchCopy > 0);
+ }
+ else
+ {
+ /*
+ * Strip the final path element from the pszDir spec.
+ */
+ char *pszFinalPath;
+ char *pszSpec;
+ uint32_t offError;
+ rc = RTVfsChainSplitOffFinalPath(pszCopy, &pszSpec, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Open the root director/whatever.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSDIR hVfsBaseDir;
+ if (pszSpec)
+ {
+ rc = RTVfsChainOpenDir(pszSpec, 0 /*fOpen*/, &hVfsBaseDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTVfsChainMsgError("RTVfsChainOpenDir", pszSpec, rc, offError, &ErrInfo.Core);
+ else if (!pszFinalPath)
+ pszFinalPath = RTStrEnd(pszSpec, RTSTR_MAX);
+ }
+ else if (!RTPathStartsWithRoot(pszFinalPath))
+ {
+ rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsBaseDir);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open '.' (for %s): %Rrc", rc, pszFinalPath);
+ }
+ else
+ {
+ char *pszRoot = pszFinalPath;
+ pszFinalPath = RTPathSkipRootSpec(pszFinalPath);
+ char const chSaved = *pszFinalPath;
+ *pszFinalPath = '\0';
+ rc = RTVfsDirOpenNormal(pszRoot, 0 /*fOpen*/, &hVfsBaseDir);
+ *pszFinalPath = chSaved;
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open root dir for '%s': %Rrc", rc, pszRoot);
+ }
+
+ /*
+ * Walk the path component by component, starting at the end.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchFinalPath = strlen(pszFinalPath);
+ while (RT_SUCCESS(rc) && cchFinalPath > 0)
+ {
+ rc = RTVfsDirRemoveDir(hVfsBaseDir, pszFinalPath, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszCopy);
+ }
+ else if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ rc = VINF_SUCCESS;
+ else
+ {
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ rc = VINF_SUCCESS;
+ else if (pszSpec)
+ RTMsgError("Failed to remove directory '%s:%s': %Rrc", pszSpec, pszFinalPath, rc);
+ else
+ RTMsgError("Failed to remove directory '%s': %Rrc", pszFinalPath, rc);
+ break;
+ }
+
+ /* Strip off a component. */
+ while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ while (cchFinalPath > 0 && !RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ while (cchFinalPath > 0 && RTPATH_IS_SLASH(pszFinalPath[cchFinalPath - 1]))
+ cchFinalPath--;
+ pszFinalPath[cchFinalPath] = '\0';
+ }
+
+ RTVfsDirRelease(hVfsBaseDir);
+ }
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainOpenParentDir", pszCopy, rc, offError, NULL);
+ }
+ RTStrFree(pszCopy);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Removes one directory.
+ *
+ * @returns exit code
+ * @param pOpts The mkdir option.
+ * @param pszDir The path to the new directory.
+ */
+static RTEXITCODE rtCmdRmDirOne(RTCMDRMDIROPTS const *pOpts, const char *pszDir)
+{
+ int rc;
+ if (!pOpts->fAlwaysUseChainApi && !RTVfsChainIsSpec(pszDir) )
+ rc = RTDirRemove(pszDir);
+ else
+ {
+ RTVFSDIR hVfsDir;
+ const char *pszChild;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenParentDir(pszDir, 0 /*fOpen*/, &hVfsDir, &pszChild, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsDirRemoveDir(hVfsDir, pszChild, 0 /*fFlags*/);
+ RTVfsDirRelease(hVfsDir);
+ }
+ else
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenParentDir", pszDir, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDir);
+ return RTEXITCODE_SUCCESS;
+ }
+ if ((rc == VERR_DIR_NOT_EMPTY || rc == VERR_SHARING_VIOLATION) && pOpts->fIgnoreNotEmpty)
+ return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
+ if ((rc == VERR_PATH_NOT_FOUND || rc == VERR_FILE_NOT_FOUND) && pOpts->fIgnoreNonExisting)
+ return RTEXITCODE_SUCCESS; /** @todo be verbose about this? */
+ return RTMsgErrorExitFailure("Failed to remove '%s': %Rrc", pszDir, rc);
+}
+
+
+static RTEXITCODE RTCmdRmDir(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING },
+ { "--ignore-fail-on-non-empty", 'F', RTGETOPT_REQ_NOTHING },
+ { "--ignore-non-existing", 'E', RTGETOPT_REQ_NOTHING },
+ { "--always-use-vfs-chain-api", 'A', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTCMDRMDIROPTS Opts;
+ Opts.fVerbose = false;
+ Opts.fParents = false;
+ Opts.fIgnoreNotEmpty = false;
+ Opts.fIgnoreNonExisting = false;
+ Opts.fAlwaysUseChainApi = false;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'p':
+ Opts.fParents = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'A':
+ Opts.fAlwaysUseChainApi = true;
+ break;
+
+ case 'E':
+ Opts.fIgnoreNonExisting = true;
+ break;
+
+ case 'F':
+ Opts.fIgnoreNotEmpty = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: %s [options] <dir> [..]\n"
+ "\n"
+ "Removes empty directories.\n"
+ "\n"
+ "Options:\n"
+ " -p, --parent\n"
+ " Remove specified parent directories too.\n"
+ " -F, --ignore-fail-on-non-empty\n"
+ " Do not fail if a directory is not empty, just ignore it.\n"
+ " This is really handy with the -p option.\n"
+ " -E, --ignore-non-existing\n"
+ " Do not fail if a specified directory is not there.\n"
+ " -v, --verbose\n"
+ " Tell which directories get remove.\n"
+ " -A, --always-use-vfs-chain-api\n"
+ " Always use the VFS API.\n"
+ , papszArgs[0]);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No directories specified.\n");
+
+ /*
+ * Work thru the specified dirs.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (Opts.fParents)
+ rc = rtCmdRmDirOneWithParents(&Opts, ValueUnion.psz);
+ else
+ rc = rtCmdRmDirOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ return rcExit;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTCmdRmDir(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTShutdown.cpp b/src/VBox/Runtime/tools/RTShutdown.cpp
new file mode 100644
index 00000000..017d0db1
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTShutdown.cpp
@@ -0,0 +1,104 @@
+/* $Id: RTShutdown.cpp $ */
+/** @file
+ * IPRT Testcase - System Shutdown.
+ */
+
+/*
+ * Copyright (C) 2012-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/system.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--halt", 'H', RTGETOPT_REQ_NOTHING },
+ { "--poweroff", 'p', RTGETOPT_REQ_NOTHING },
+ { "--reboot", 'r', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--delay", 'd', RTGETOPT_REQ_UINT32 },
+ { "--message", 'm', RTGETOPT_REQ_STRING }
+ };
+
+ const char *pszMsg = "RTShutdown";
+ RTMSINTERVAL cMsDelay = 0;
+ uint32_t fFlags = RTSYSTEM_SHUTDOWN_POWER_OFF | RTSYSTEM_SHUTDOWN_PLANNED;
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ if (rc == 0)
+ break;
+ switch (rc)
+ {
+ case 'H': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_HALT; break;
+ case 'p': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_POWER_OFF_HALT; break;
+ case 'r': fFlags = (fFlags & ~RTSYSTEM_SHUTDOWN_ACTION_MASK) | RTSYSTEM_SHUTDOWN_REBOOT; break;
+ case 'f': fFlags |= RTSYSTEM_SHUTDOWN_FORCE; break;
+ case 'd': cMsDelay = ValueUnion.u32; break;
+ case 'm': pszMsg = ValueUnion.psz; break;
+
+ case 'h':
+ RTPrintf("Usage: RTShutdown [-H|-p|-r] [-f] [-d <milliseconds>] [-m <msg>]\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Do the deed.
+ */
+ rc = RTSystemShutdown(cMsDelay, fFlags, pszMsg);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTSystemShutdown(%u, %#x, \"%s\") returned %Rrc\n", cMsDelay, fFlags, pszMsg, rc);
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/tools/RTSignTool.cpp b/src/VBox/Runtime/tools/RTSignTool.cpp
new file mode 100644
index 00000000..1b9027a3
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTSignTool.cpp
@@ -0,0 +1,2765 @@
+/* $Id: RTSignTool.cpp $ */
+/** @file
+ * IPRT - Signing Tool.
+ */
+
+/*
+ * Copyright (C) 2006-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/zero.h>
+#ifndef RT_OS_WINDOWS
+# include <iprt/formats/pecoff.h>
+#endif
+#include <iprt/crypto/applecodesign.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/x509.h>
+#include <iprt/crypto/pkcs7.h>
+#include <iprt/crypto/store.h>
+#include <iprt/crypto/spc.h>
+#ifdef VBOX
+# include <VBox/sup.h> /* Certificates */
+#endif
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/win/imagehlp.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Help detail levels. */
+typedef enum RTSIGNTOOLHELP
+{
+ RTSIGNTOOLHELP_USAGE,
+ RTSIGNTOOLHELP_FULL
+} RTSIGNTOOLHELP;
+
+
+/**
+ * PKCS\#7 signature data.
+ */
+typedef struct SIGNTOOLPKCS7
+{
+ /** The raw signature. */
+ uint8_t *pbBuf;
+ /** Size of the raw signature. */
+ size_t cbBuf;
+ /** The filename. */
+ const char *pszFilename;
+ /** The outer content info wrapper. */
+ RTCRPKCS7CONTENTINFO ContentInfo;
+ /** Pointer to the decoded SignedData inside the ContentInfo member. */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+
+ /** Newly encoded raw signature.
+ * @sa SignToolPkcs7_Encode() */
+ uint8_t *pbNewBuf;
+ /** Size of newly encoded raw signature. */
+ size_t cbNewBuf;
+
+} SIGNTOOLPKCS7;
+typedef SIGNTOOLPKCS7 *PSIGNTOOLPKCS7;
+
+
+/**
+ * PKCS\#7 signature data for executable.
+ */
+typedef struct SIGNTOOLPKCS7EXE : public SIGNTOOLPKCS7
+{
+ /** The module handle. */
+ RTLDRMOD hLdrMod;
+} SIGNTOOLPKCS7EXE;
+typedef SIGNTOOLPKCS7EXE *PSIGNTOOLPKCS7EXE;
+
+
+/**
+ * Data for the show exe (signature) command.
+ */
+typedef struct SHOWEXEPKCS7 : public SIGNTOOLPKCS7EXE
+{
+ /** The verbosity. */
+ unsigned cVerbosity;
+ /** The prefix buffer. */
+ char szPrefix[256];
+ /** Temporary buffer. */
+ char szTmp[4096];
+} SHOWEXEPKCS7;
+typedef SHOWEXEPKCS7 *PSHOWEXEPKCS7;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static RTEXITCODE HandleHelp(int cArgs, char **papszArgs);
+static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel);
+static RTEXITCODE HandleVersion(int cArgs, char **papszArgs);
+static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix,
+ PCRTCRPKCS7CONTENTINFO pContentInfo);
+
+
+/**
+ * Deletes the structure.
+ *
+ * @param pThis The structure to initialize.
+ */
+static void SignToolPkcs7_Delete(PSIGNTOOLPKCS7 pThis)
+{
+ RTCrPkcs7ContentInfo_Delete(&pThis->ContentInfo);
+ pThis->pSignedData = NULL;
+ RTMemFree(pThis->pbBuf);
+ pThis->pbBuf = NULL;
+ pThis->cbBuf = 0;
+ RTMemFree(pThis->pbNewBuf);
+ pThis->pbNewBuf = NULL;
+ pThis->cbNewBuf = 0;
+}
+
+
+/**
+ * Deletes the structure.
+ *
+ * @param pThis The structure to initialize.
+ */
+static void SignToolPkcs7Exe_Delete(PSIGNTOOLPKCS7EXE pThis)
+{
+ if (pThis->hLdrMod != NIL_RTLDRMOD)
+ {
+ int rc2 = RTLdrClose(pThis->hLdrMod);
+ if (RT_FAILURE(rc2))
+ RTMsgError("RTLdrClose failed: %Rrc\n", rc2);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ }
+ SignToolPkcs7_Delete(pThis);
+}
+
+
+/**
+ * Decodes the PKCS #7 blob pointed to by pThis->pbBuf.
+ *
+ * @returns IPRT status code (error message already shown on failure).
+ * @param pThis The PKCS\#7 signature to decode.
+ * @param fCatalog Set if catalog file, clear if executable.
+ */
+static int SignToolPkcs7_Decode(PSIGNTOOLPKCS7 pThis, bool fCatalog)
+{
+ RTERRINFOSTATIC ErrInfo;
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pThis->pbBuf, (uint32_t)pThis->cbBuf, RTErrInfoInitStatic(&ErrInfo),
+ &g_RTAsn1DefaultAllocator, 0, "WinCert");
+
+ int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &pThis->ContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCrPkcs7ContentInfo_IsSignedData(&pThis->ContentInfo))
+ {
+ pThis->pSignedData = pThis->ContentInfo.u.pSignedData;
+
+ /*
+ * Decode the authenticode bits.
+ */
+ if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
+ {
+ PRTCRSPCINDIRECTDATACONTENT pIndData = pThis->pSignedData->ContentInfo.u.pIndirectDataContent;
+ Assert(pIndData);
+
+ /*
+ * Check that things add up.
+ */
+ rc = RTCrPkcs7SignedData_CheckSanity(pThis->pSignedData,
+ RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
+ RTErrInfoInitStatic(&ErrInfo), "SD");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrSpcIndirectDataContent_CheckSanityEx(pIndData,
+ pThis->pSignedData,
+ RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RTMsgError("SPC indirect data content sanity check failed for '%s': %Rrc - %s\n",
+ pThis->pszFilename, rc, ErrInfo.szMsg);
+ }
+ else
+ RTMsgError("PKCS#7 sanity check failed for '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg);
+ }
+ else if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
+ { /* apple code signing */ }
+ else if (!fCatalog)
+ RTMsgError("Unexpected the signed content in '%s': %s (expected %s)", pThis->pszFilename,
+ pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_CR_PKCS7_NOT_SIGNED_DATA,
+ "PKCS#7 content is inside '%s' is not 'signedData': %s\n",
+ pThis->pszFilename, pThis->ContentInfo.ContentType.szObjId);
+ }
+ else
+ RTMsgError("RTCrPkcs7ContentInfo_DecodeAsn1 failed on '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg);
+ return rc;
+}
+
+
+/**
+ * Reads and decodes PKCS\#7 signature from the given cat file.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The structure to initialize.
+ * @param pszFilename The catalog (or any other DER PKCS\#7) filename.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_InitFromFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity)
+{
+ /*
+ * Init the return structure.
+ */
+ RT_ZERO(*pThis);
+ pThis->pszFilename = pszFilename;
+
+ /*
+ * Lazy bird uses RTFileReadAll and duplicates the allocation.
+ */
+ void *pvFile;
+ int rc = RTFileReadAll(pszFilename, &pvFile, &pThis->cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pbBuf = (uint8_t *)RTMemDup(pvFile, pThis->cbBuf);
+ RTFileReadAllFree(pvFile, pThis->cbBuf);
+ if (pThis->pbBuf)
+ {
+ if (cVerbosity > 2)
+ RTPrintf("PKCS#7 signature: %u bytes\n", pThis->cbBuf);
+
+ /*
+ * Decode it.
+ */
+ rc = SignToolPkcs7_Decode(pThis, true /*fCatalog*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("Out of memory!");
+ }
+ else
+ RTMsgError("Error reading '%s' into memory: %Rrc", pszFilename, rc);
+
+ SignToolPkcs7_Delete(pThis);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Encodes the signature into the SIGNTOOLPKCS7::pbNewBuf and
+ * SIGNTOOLPKCS7::cbNewBuf members.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The signature to encode.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_Encode(PSIGNTOOLPKCS7 pThis, unsigned cVerbosity)
+{
+ RTERRINFOSTATIC StaticErrInfo;
+ PRTASN1CORE pRoot = RTCrPkcs7ContentInfo_GetAsn1Core(&pThis->ContentInfo);
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity >= 4)
+ RTAsn1Dump(pRoot, 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ RTMemFree(pThis->pbNewBuf);
+ pThis->cbNewBuf = cbEncoded;
+ pThis->pbNewBuf = (uint8_t *)RTMemAllocZ(cbEncoded);
+ if (pThis->pbNewBuf)
+ {
+ rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pThis->pbNewBuf, pThis->cbNewBuf,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 1)
+ RTMsgInfo("Encoded signature to %u bytes", cbEncoded);
+ return RTEXITCODE_SUCCESS;
+ }
+ RTMsgError("RTAsn1EncodeToBuffer failed: %Rrc", rc);
+
+ RTMemFree(pThis->pbNewBuf);
+ pThis->pbNewBuf = NULL;
+ }
+ else
+ RTMsgError("Failed to allocate %u bytes!", cbEncoded);
+ }
+ else
+ RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Adds the @a pSrc signature as a nested signature.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The signature to modify.
+ * @param pSrc The signature to add as nested.
+ * @param cVerbosity The verbosity.
+ * @param fPrepend Whether to prepend (true) or append (false) the
+ * source signature to the nested attribute.
+ */
+static RTEXITCODE SignToolPkcs7_AddNestedSignature(PSIGNTOOLPKCS7 pThis, PSIGNTOOLPKCS7 pSrc,
+ unsigned cVerbosity, bool fPrepend)
+{
+ PRTCRPKCS7SIGNERINFO pSignerInfo = pThis->pSignedData->SignerInfos.papItems[0];
+ int rc;
+
+ /*
+ * Deal with UnauthenticatedAttributes being absent before trying to append to the array.
+ */
+ if (pSignerInfo->UnauthenticatedAttributes.cItems == 0)
+ {
+ /* HACK ALERT! Invent ASN.1 setters/whatever for members to replace this mess. */
+
+ if (pSignerInfo->AuthenticatedAttributes.cItems == 0)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No authenticated or unauthenticated attributes! Sorry, no can do.");
+
+ Assert(pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag == 0);
+ rc = RTAsn1SetCore_Init(&pSignerInfo->UnauthenticatedAttributes.SetCore,
+ pSignerInfo->AuthenticatedAttributes.SetCore.Asn1Core.pOps);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTAsn1SetCore_Init failed: %Rrc", rc);
+ pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag = 1;
+ pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.fClass = ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED;
+ RTAsn1MemInitArrayAllocation(&pSignerInfo->UnauthenticatedAttributes.Allocation,
+ pSignerInfo->AuthenticatedAttributes.Allocation.pAllocator,
+ sizeof(**pSignerInfo->UnauthenticatedAttributes.papItems));
+ }
+
+ /*
+ * Find or add an unauthenticated attribute for nested signatures.
+ */
+ rc = VERR_NOT_FOUND;
+ PRTCRPKCS7ATTRIBUTE pAttr = NULL;
+ int32_t iPos = pSignerInfo->UnauthenticatedAttributes.cItems;
+ while (iPos-- > 0)
+ if (pSignerInfo->UnauthenticatedAttributes.papItems[iPos]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ if (iPos < 0)
+ {
+ iPos = RTCrPkcs7Attributes_Append(&pSignerInfo->UnauthenticatedAttributes);
+ if (iPos >= 0)
+ {
+ if (cVerbosity >= 3)
+ RTMsgInfo("Adding UnauthenticatedAttribute #%u...", iPos);
+ Assert((uint32_t)iPos < pSignerInfo->UnauthenticatedAttributes.cItems);
+
+ pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos];
+ rc = RTAsn1ObjId_InitFromString(&pAttr->Type, RTCR_PKCS9_ID_MS_NESTED_SIGNATURE, pAttr->Allocation.pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Generalize the Type + enmType DYN stuff and generate setters. */
+ Assert(pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT);
+ Assert(pAttr->uValues.pContentInfos == NULL);
+ pAttr->enmType = RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE;
+ rc = RTAsn1MemAllocZ(&pAttr->Allocation, (void **)&pAttr->uValues.pContentInfos,
+ sizeof(*pAttr->uValues.pContentInfos));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPkcs7SetOfContentInfos_Init(pAttr->uValues.pContentInfos, pAttr->Allocation.pAllocator);
+ if (!RT_SUCCESS(rc))
+ RTMsgError("RTCrPkcs7ContentInfos_Init failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTAsn1MemAllocZ failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTAsn1ObjId_InitFromString failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrPkcs7Attributes_Append failed: %Rrc", iPos);
+ }
+ else if (cVerbosity >= 2)
+ RTMsgInfo("Found UnauthenticatedAttribute #%u...", iPos);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Append/prepend the signature.
+ */
+ uint32_t iActualPos = UINT32_MAX;
+ iPos = fPrepend ? 0 : pAttr->uValues.pContentInfos->cItems;
+ rc = RTCrPkcs7SetOfContentInfos_InsertEx(pAttr->uValues.pContentInfos, iPos, &pSrc->ContentInfo,
+ pAttr->Allocation.pAllocator, &iActualPos);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("Added nested signature (#%u)", iActualPos);
+ if (cVerbosity >= 3)
+ {
+ RTMsgInfo("SingerInfo dump after change:");
+ RTAsn1Dump(RTCrPkcs7SignerInfo_GetAsn1Core(pSignerInfo), 0, 2, RTStrmDumpPrintfV, g_pStdOut);
+ }
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTMsgError("RTCrPkcs7ContentInfos_InsertEx failed: %Rrc", rc);
+ }
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Writes the signature to the file.
+ *
+ * Caller must have called SignToolPkcs7_Encode() prior to this function.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error
+ * message on failure.
+ * @param pThis The file which to write.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7_WriteSignatureToFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity)
+{
+ AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE);
+
+ /*
+ * Open+truncate file, write new signature, close. Simple.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pThis->pbNewBuf, pThis->cbNewBuf, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("Wrote %u bytes to %s", pThis->cbNewBuf, pszFilename);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ RTMsgError("RTFileClose failed on %s: %Rrc", pszFilename, rc);
+ }
+ else
+ RTMsgError("Write error on %s: %Rrc", pszFilename, rc);
+ }
+ else
+ RTMsgError("Failed to open %s for writing: %Rrc", pszFilename, rc);
+ return RTEXITCODE_FAILURE;
+}
+
+
+
+/**
+ * Worker for recursively searching for MS nested signatures and signer infos.
+ *
+ * @returns Pointer to the signer info corresponding to @a iSignature. NULL if
+ * not found.
+ * @param pSignedData The signature to search.
+ * @param piNextSignature Pointer to the variable keeping track of the next
+ * signature number.
+ * @param iReqSignature The request signature number.
+ * @param ppSignedData Where to return the signature data structure.
+ */
+static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndexWorker(PRTCRPKCS7SIGNEDDATA pSignedData,
+ uint32_t *piNextSignature,
+ uint32_t iReqSignature,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ for (uint32_t iSignerInfo = 0; iSignerInfo < pSignedData->SignerInfos.cItems; iSignerInfo++)
+ {
+ /* Match?*/
+ PRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[iSignerInfo];
+ if (*piNextSignature == iReqSignature)
+ {
+ *ppSignedData = pSignedData;
+ return pSignerInfo;
+ }
+ *piNextSignature += 1;
+
+ /* Look for nested signatures. */
+ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
+ if (pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ PRTCRPKCS7SETOFCONTENTINFOS pCntInfos;
+ pCntInfos = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->uValues.pContentInfos;
+ for (uint32_t iCntInfo = 0; iCntInfo < pCntInfos->cItems; iCntInfo++)
+ {
+ PRTCRPKCS7CONTENTINFO pCntInfo = pCntInfos->papItems[iCntInfo];
+ if (RTCrPkcs7ContentInfo_IsSignedData(pCntInfo))
+ {
+ PRTCRPKCS7SIGNERINFO pRet;
+ pRet = SignToolPkcs7_FindNestedSignatureByIndexWorker(pCntInfo->u.pSignedData, piNextSignature,
+ iReqSignature, ppSignedData);
+ if (pRet)
+ return pRet;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Locates the given nested signature.
+ *
+ * @returns Pointer to the signer info corresponding to @a iSignature. NULL if
+ * not found.
+ * @param pThis The PKCS\#7 structure to search.
+ * @param iReqSignature The requested signature number.
+ * @param ppSignedData Where to return the pointer to the signed data that
+ * the returned signer info belongs to.
+ *
+ * @todo Move into SPC or PKCS\#7.
+ */
+static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndex(PSIGNTOOLPKCS7 pThis, uint32_t iReqSignature,
+ PRTCRPKCS7SIGNEDDATA *ppSignedData)
+{
+ uint32_t iNextSignature = 0;
+ return SignToolPkcs7_FindNestedSignatureByIndexWorker(pThis->pSignedData, &iNextSignature, iReqSignature, ppSignedData);
+}
+
+
+
+/**
+ * Reads and decodes PKCS\#7 signature from the given executable.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message
+ * on failure.
+ * @param pThis The structure to initialize.
+ * @param pszFilename The executable filename.
+ * @param cVerbosity The verbosity.
+ * @param enmLdrArch For FAT binaries.
+ */
+static RTEXITCODE SignToolPkcs7Exe_InitFromFile(PSIGNTOOLPKCS7EXE pThis, const char *pszFilename,
+ unsigned cVerbosity, RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER)
+{
+ /*
+ * Init the return structure.
+ */
+ RT_ZERO(*pThis);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ pThis->pszFilename = pszFilename;
+
+ /*
+ * Open the image and check if it's signed.
+ */
+ int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, enmLdrArch, &pThis->hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIsSigned = false;
+ rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_IS_SIGNED, &fIsSigned, sizeof(fIsSigned));
+ if (RT_SUCCESS(rc) && fIsSigned)
+ {
+ /*
+ * Query the PKCS#7 data (assuming M$ style signing) and hand it to a worker.
+ */
+ size_t cbActual = 0;
+#ifdef DEBUG
+ size_t cbBuf = 64;
+#else
+ size_t cbBuf = _512K;
+#endif
+ void *pvBuf = RTMemAllocZ(cbBuf);
+ if (pvBuf)
+ {
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/, pvBuf, cbBuf, &cbActual);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemFree(pvBuf);
+ cbBuf = cbActual;
+ pvBuf = RTMemAllocZ(cbActual);
+ if (pvBuf)
+ rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/,
+ pvBuf, cbBuf, &cbActual);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ pThis->pbBuf = (uint8_t *)pvBuf;
+ pThis->cbBuf = cbActual;
+ if (RT_SUCCESS(rc))
+ {
+ if (cVerbosity > 2)
+ RTPrintf("PKCS#7 signature: %u bytes\n", cbActual);
+ if (cVerbosity > 3)
+ RTPrintf("%.*Rhxd\n", cbActual, pvBuf);
+
+ /*
+ * Decode it.
+ */
+ rc = SignToolPkcs7_Decode(pThis, false /*fCatalog*/);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ RTMsgError("RTLdrQueryPropEx/RTLDRPROP_PKCS7_SIGNED_DATA failed on '%s': %Rrc\n", pszFilename, rc);
+ }
+ else if (RT_SUCCESS(rc))
+ RTMsgInfo("'%s': not signed\n", pszFilename);
+ else
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_IS_SIGNED failed on '%s': %Rrc\n", pszFilename, rc);
+ }
+ else
+ RTMsgError("Error opening executable image '%s': %Rrc", pszFilename, rc);
+
+ SignToolPkcs7Exe_Delete(pThis);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Calculates the checksum of an executable.
+ *
+ * @returns Success indicator (errors are reported)
+ * @param pThis The exe file to checksum.
+ * @param hFile The file handle.
+ * @param puCheckSum Where to return the checksum.
+ */
+static bool SignToolPkcs7Exe_CalcPeCheckSum(PSIGNTOOLPKCS7EXE pThis, RTFILE hFile, uint32_t *puCheckSum)
+{
+#ifdef RT_OS_WINDOWS
+ /*
+ * Try use IMAGEHLP!MapFileAndCheckSumW first.
+ */
+ PRTUTF16 pwszPath;
+ int rc = RTStrToUtf16(pThis->pszFilename, &pwszPath);
+ if (RT_SUCCESS(rc))
+ {
+ decltype(MapFileAndCheckSumW) *pfnMapFileAndCheckSumW;
+ pfnMapFileAndCheckSumW = (decltype(MapFileAndCheckSumW) *)RTLdrGetSystemSymbol("IMAGEHLP.DLL", "MapFileAndCheckSumW");
+ if (pfnMapFileAndCheckSumW)
+ {
+ DWORD uHeaderSum = UINT32_MAX;
+ DWORD uCheckSum = UINT32_MAX;
+ DWORD dwRc = pfnMapFileAndCheckSumW(pwszPath, &uHeaderSum, &uCheckSum);
+ if (dwRc == CHECKSUM_SUCCESS)
+ {
+ *puCheckSum = uCheckSum;
+ return true;
+ }
+ }
+ }
+#endif
+
+ RT_NOREF(pThis, hFile, puCheckSum);
+ RTMsgError("Implement check sum calcuation fallback!");
+ return false;
+}
+
+
+/**
+ * Writes the signature to the file.
+ *
+ * This has the side-effect of closing the hLdrMod member. So, it can only be
+ * called once!
+ *
+ * Caller must have called SignToolPkcs7_Encode() prior to this function.
+ *
+ * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error
+ * message on failure.
+ * @param pThis The file which to write.
+ * @param cVerbosity The verbosity.
+ */
+static RTEXITCODE SignToolPkcs7Exe_WriteSignatureToFile(PSIGNTOOLPKCS7EXE pThis, unsigned cVerbosity)
+{
+ AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE);
+
+ /*
+ * Get the file header offset and arch before closing the destination handle.
+ */
+ uint32_t offNtHdrs;
+ int rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_FILE_OFF_HEADER, &offNtHdrs, sizeof(offNtHdrs));
+ if (RT_SUCCESS(rc))
+ {
+ RTLDRARCH enmLdrArch = RTLdrGetArch(pThis->hLdrMod);
+ if (enmLdrArch != RTLDRARCH_INVALID)
+ {
+ RTLdrClose(pThis->hLdrMod);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ unsigned cbNtHdrs = 0;
+ switch (enmLdrArch)
+ {
+ case RTLDRARCH_AMD64:
+ cbNtHdrs = sizeof(IMAGE_NT_HEADERS64);
+ break;
+ case RTLDRARCH_X86_32:
+ cbNtHdrs = sizeof(IMAGE_NT_HEADERS32);
+ break;
+ default:
+ RTMsgError("Unknown image arch: %d", enmLdrArch);
+ }
+ if (cbNtHdrs > 0)
+ {
+ if (cVerbosity > 0)
+ RTMsgInfo("offNtHdrs=%#x cbNtHdrs=%u\n", offNtHdrs, cbNtHdrs);
+
+ /*
+ * Open the executable file for writing.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pThis->pszFilename, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read the file header and locate the security directory entry. */
+ union
+ {
+ IMAGE_NT_HEADERS32 NtHdrs32;
+ IMAGE_NT_HEADERS64 NtHdrs64;
+ } uBuf;
+ PIMAGE_DATA_DIRECTORY pSecDir = cbNtHdrs == sizeof(IMAGE_NT_HEADERS64)
+ ? &uBuf.NtHdrs64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]
+ : &uBuf.NtHdrs32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
+
+ rc = RTFileReadAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if ( RT_SUCCESS(rc)
+ && uBuf.NtHdrs32.Signature == IMAGE_NT_SIGNATURE)
+ {
+ /*
+ * Drop any old signature by truncating the file.
+ */
+ if ( pSecDir->Size > 8
+ && pSecDir->VirtualAddress > offNtHdrs + sizeof(IMAGE_NT_HEADERS32))
+ {
+ rc = RTFileSetSize(hFile, pSecDir->VirtualAddress);
+ if (RT_FAILURE(rc))
+ RTMsgError("Error truncating file to %#x bytes: %Rrc", pSecDir->VirtualAddress, rc);
+ }
+ else
+ rc = RTMsgErrorRc(VERR_BAD_EXE_FORMAT, "Bad security directory entry: VA=%#x Size=%#x",
+ pSecDir->VirtualAddress, pSecDir->Size);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Sector align the signature portion.
+ */
+ uint32_t const cbWinCert = RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate);
+ uint64_t offCur = 0;
+ rc = RTFileQuerySize(hFile, &offCur);
+ if ( RT_SUCCESS(rc)
+ && offCur < _2G)
+ {
+ if (offCur & 0x1ff)
+ {
+ uint32_t cbNeeded = 0x200 - ((uint32_t)offCur & 0x1ff);
+ rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbNeeded, NULL);
+ if (RT_SUCCESS(rc))
+ offCur += cbNeeded;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Write the header followed by the signature data.
+ */
+ uint32_t const cbZeroPad = (uint32_t)(RT_ALIGN_Z(pThis->cbNewBuf, 8) - pThis->cbNewBuf);
+ pSecDir->VirtualAddress = (uint32_t)offCur;
+ pSecDir->Size = cbWinCert + (uint32_t)pThis->cbNewBuf + cbZeroPad;
+ if (cVerbosity >= 2)
+ RTMsgInfo("Writing %u (%#x) bytes of signature at %#x (%u).\n",
+ pSecDir->Size, pSecDir->Size, pSecDir->VirtualAddress, pSecDir->VirtualAddress);
+
+ WIN_CERTIFICATE WinCert;
+ WinCert.dwLength = pSecDir->Size;
+ WinCert.wRevision = WIN_CERT_REVISION_2_0;
+ WinCert.wCertificateType = WIN_CERT_TYPE_PKCS_SIGNED_DATA;
+
+ rc = RTFileWriteAt(hFile, offCur, &WinCert, cbWinCert, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offCur += cbWinCert;
+ rc = RTFileWriteAt(hFile, offCur, pThis->pbNewBuf, pThis->cbNewBuf, NULL);
+ }
+ if (RT_SUCCESS(rc) && cbZeroPad)
+ {
+ offCur += pThis->cbNewBuf;
+ rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbZeroPad, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Reset the checksum (sec dir updated already) and rewrite the header.
+ */
+ uBuf.NtHdrs32.OptionalHeader.CheckSum = 0;
+ offCur = offNtHdrs;
+ rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTFileFlush(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Calc checksum and write out the header again.
+ */
+ uint32_t uCheckSum = UINT32_MAX;
+ if (SignToolPkcs7Exe_CalcPeCheckSum(pThis, hFile, &uCheckSum))
+ {
+ uBuf.NtHdrs32.OptionalHeader.CheckSum = uCheckSum;
+ rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTFileFlush(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ RTMsgError("RTFileClose failed: %Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ RTMsgError("Write error at %#RX64: %Rrc", offCur, rc);
+ }
+ else if (RT_SUCCESS(rc))
+ RTMsgError("File to big: %'RU64 bytes", offCur);
+ else
+ RTMsgError("RTFileQuerySize failed: %Rrc", rc);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ RTMsgError("Not NT executable header!");
+ else
+ RTMsgError("Error reading NT headers (%#x bytes) at %#x: %Rrc", cbNtHdrs, offNtHdrs, rc);
+ RTFileClose(hFile);
+ }
+ else
+ RTMsgError("Failed to open '%s' for writing: %Rrc", pThis->pszFilename, rc);
+ }
+ }
+ else
+ RTMsgError("RTLdrGetArch failed!");
+ }
+ else
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_FILE_OFF_HEADER failed: %Rrc", rc);
+ return RTEXITCODE_FAILURE;
+}
+
+
+
+/*
+ * The 'extract-exe-signer-cert' command.
+ */
+static RTEXITCODE HelpExtractExeSignerCert(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "extract-exe-signer-cert [--ber|--cer|--der] [--signature-index|-i <num>] [--exe|-e] <exe> [--output|-o] <outfile.cer>\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleExtractExeSignerCert(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--ber", 'b', RTGETOPT_REQ_NOTHING },
+ { "--cer", 'c', RTGETOPT_REQ_NOTHING },
+ { "--der", 'd', RTGETOPT_REQ_NOTHING },
+ { "--exe", 'e', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--signature-index", 'i', RTGETOPT_REQ_UINT32 },
+ };
+
+ const char *pszExe = NULL;
+ const char *pszOut = NULL;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+ unsigned cVerbosity = 0;
+ uint32_t fCursorFlags = RTASN1CURSOR_FLAGS_DER;
+ uint32_t iSignature = 0;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'e': pszExe = ValueUnion.psz; break;
+ case 'o': pszOut = ValueUnion.psz; break;
+ case 'b': fCursorFlags = 0; break;
+ case 'c': fCursorFlags = RTASN1CURSOR_FLAGS_CER; break;
+ case 'd': fCursorFlags = RTASN1CURSOR_FLAGS_DER; break;
+ case 'i': iSignature = ValueUnion.u32; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpExtractExeSignerCert(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszExe)
+ pszExe = ValueUnion.psz;
+ else if (!pszOut)
+ pszOut = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszExe)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+ if (!pszOut)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given.");
+ if (RTPathExists(pszOut))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pszOut);
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE This;
+ RTEXITCODE rcExit = SignToolPkcs7Exe_InitFromFile(&This, pszExe, cVerbosity, enmLdrArch);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Find the signing certificate (ASSUMING that the certificate used is shipped in the set of certificates). */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(&This, iSignature, &pSignedData);
+ rcExit = RTEXITCODE_FAILURE;
+ if (pSignerInfo)
+ {
+ PCRTCRPKCS7ISSUERANDSERIALNUMBER pISN = &pSignedData->SignerInfos.papItems[0]->IssuerAndSerialNumber;
+ PCRTCRX509CERTIFICATE pCert;
+ pCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
+ &pISN->Name, &pISN->SerialNumber);
+ if (pCert)
+ {
+ /*
+ * Write it out.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszOut, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbCert = pCert->SeqCore.Asn1Core.cbHdr + pCert->SeqCore.Asn1Core.cb;
+ rc = RTFileWrite(hFile, pCert->SeqCore.Asn1Core.uData.pu8 - pCert->SeqCore.Asn1Core.cbHdr,
+ cbCert, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ {
+ hFile = NIL_RTFILE;
+ rcExit = RTEXITCODE_SUCCESS;
+ RTMsgInfo("Successfully wrote %u bytes to '%s'", cbCert, pszOut);
+ }
+ else
+ RTMsgError("RTFileClose failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTFileWrite failed: %Rrc", rc);
+ RTFileClose(hFile);
+ }
+ else
+ RTMsgError("Error opening '%s' for writing: %Rrc", pszOut, rc);
+ }
+ else
+ RTMsgError("Certificate not found.");
+ }
+ else
+ RTMsgError("Could not locate signature #%u!", iSignature);
+
+ /* Delete the signature data. */
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ return rcExit;
+}
+
+
+/*
+ * The 'add-nested-exe-signature' command.
+ */
+static RTEXITCODE HelpAddNestedExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "add-nested-exe-signature [-v|--verbose] [-d|--debug] [-p|--prepend] <destination-exe> <source-exe>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmPrintf(pStrm,
+ "\n"
+ "The --debug option allows the source-exe to be omitted in order to test the\n"
+ "encoding and PE file modification.\n"
+ "\n"
+ "The --prepend option puts the nested signature first rather than appending it\n"
+ "to the end of of the nested signature set. Windows reads nested signatures in\n"
+ "reverse order, so --prepend will logically putting it last.\n"
+ );
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleAddNestedExeSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--prepend", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--debug", 'd', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszDst = NULL;
+ const char *pszSrc = NULL;
+ unsigned cVerbosity = 0;
+ bool fDebug = false;
+ bool fPrepend = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'd': fDebug = pszSrc == NULL; break;
+ case 'p': fPrepend = true; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpAddNestedExeSignature(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszDst)
+ pszDst = ValueUnion.psz;
+ else if (!pszSrc)
+ {
+ pszSrc = ValueUnion.psz;
+ fDebug = false;
+ }
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszDst)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination executable given.");
+ if (!pszSrc && !fDebug)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source executable file given.");
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the source PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Src;
+ RTEXITCODE rcExit = pszSrc ? SignToolPkcs7Exe_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS;
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Ditto for the destination PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Dst;
+ rcExit = SignToolPkcs7Exe_InitFromFile(&Dst, pszDst, cVerbosity);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Do the signature manipulation. */
+ if (pszSrc)
+ rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity);
+
+ /* Update the destination executable file. */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7Exe_WriteSignatureToFile(&Dst, cVerbosity);
+
+ SignToolPkcs7Exe_Delete(&Dst);
+ }
+ if (pszSrc)
+ SignToolPkcs7Exe_Delete(&Src);
+ }
+
+ return rcExit;
+}
+
+
+/*
+ * The 'add-nested-cat-signature' command.
+ */
+static RTEXITCODE HelpAddNestedCatSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "add-nested-cat-signature [-v|--verbose] [-d|--debug] [-p|--prepend] <destination-cat> <source-cat>\n");
+ if (enmLevel == RTSIGNTOOLHELP_FULL)
+ RTStrmPrintf(pStrm,
+ "\n"
+ "The --debug option allows the source-cat to be omitted in order to test the\n"
+ "ASN.1 re-encoding of the destination catalog file.\n"
+ "\n"
+ "The --prepend option puts the nested signature first rather than appending it\n"
+ "to the end of of the nested signature set. Windows reads nested signatures in\n"
+ "reverse order, so --prepend will logically putting it last.\n"
+ );
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleAddNestedCatSignature(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--prepend", 'p', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--debug", 'd', RTGETOPT_REQ_NOTHING },
+ };
+
+ const char *pszDst = NULL;
+ const char *pszSrc = NULL;
+ unsigned cVerbosity = 0;
+ bool fDebug = false;
+ bool fPrepend = false;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'd': fDebug = pszSrc == NULL; break;
+ case 'p': fPrepend = true; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpAddNestedCatSignature(g_pStdOut, RTSIGNTOOLHELP_FULL);
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pszDst)
+ pszDst = ValueUnion.psz;
+ else if (!pszSrc)
+ {
+ pszSrc = ValueUnion.psz;
+ fDebug = false;
+ }
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!pszDst)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination catalog file given.");
+ if (!pszSrc && !fDebug)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source catalog file given.");
+
+ /*
+ * Do it.
+ */
+ /* Read & decode the source PKCS#7 signature. */
+ SIGNTOOLPKCS7 Src;
+ RTEXITCODE rcExit = pszSrc ? SignToolPkcs7_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS;
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Ditto for the destination PKCS#7 signature. */
+ SIGNTOOLPKCS7EXE Dst;
+ rcExit = SignToolPkcs7_InitFromFile(&Dst, pszDst, cVerbosity);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /* Do the signature manipulation. */
+ if (pszSrc)
+ rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity);
+
+ /* Update the destination executable file. */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = SignToolPkcs7_WriteSignatureToFile(&Dst, pszDst, cVerbosity);
+
+ SignToolPkcs7_Delete(&Dst);
+ }
+ if (pszSrc)
+ SignToolPkcs7_Delete(&Src);
+ }
+
+ return rcExit;
+}
+
+#ifndef IPRT_IN_BUILD_TOOL
+
+/*
+ * The 'verify-exe' command.
+ */
+static RTEXITCODE HelpVerifyExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm,
+ "verify-exe [--verbose|--quiet] [--kernel] [--root <root-cert.der>] [--additional <supp-cert.der>]\n"
+ " [--type <win|osx>] <exe1> [exe2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+typedef struct VERIFYEXESTATE
+{
+ RTCRSTORE hRootStore;
+ RTCRSTORE hKernelRootStore;
+ RTCRSTORE hAdditionalStore;
+ bool fKernel;
+ int cVerbose;
+ enum { kSignType_Windows, kSignType_OSX } enmSignType;
+ RTLDRARCH enmLdrArch;
+ uint32_t cBad;
+ uint32_t cOkay;
+ const char *pszFilename;
+} VERIFYEXESTATE;
+
+# ifdef VBOX
+/** Certificate store load set.
+ * Declared outside HandleVerifyExe because of braindead gcc visibility crap. */
+struct STSTORESET
+{
+ RTCRSTORE hStore;
+ PCSUPTAENTRY paTAs;
+ unsigned cTAs;
+};
+# endif
+
+/**
+ * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK,
+ * Standard code signing. Use this for Microsoft SPC.}
+ */
+static DECLCALLBACK(int) VerifyExecCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser;
+ uint32_t cPaths = hCertPaths != NIL_RTCRX509CERTPATHS ? RTCrX509CertPathsGetPathCount(hCertPaths) : 0;
+
+ /*
+ * Dump all the paths.
+ */
+ if (pState->cVerbose > 0)
+ {
+ for (uint32_t iPath = 0; iPath < cPaths; iPath++)
+ {
+ RTPrintf("---\n");
+ RTCrX509CertPathsDumpOne(hCertPaths, iPath, pState->cVerbose, RTStrmDumpPrintfV, g_pStdOut);
+ *pErrInfo->pszMsg = '\0';
+ }
+ RTPrintf("---\n");
+ }
+
+ /*
+ * Test signing certificates normally doesn't have all the necessary
+ * features required below. So, treat them as special cases.
+ */
+ if ( hCertPaths == NIL_RTCRX509CERTPATHS
+ && RTCrX509Name_Compare(&pCert->TbsCertificate.Issuer, &pCert->TbsCertificate.Subject) == 0)
+ {
+ RTMsgInfo("Test signed.\n");
+ return VINF_SUCCESS;
+ }
+
+ if (hCertPaths == NIL_RTCRX509CERTPATHS)
+ RTMsgInfo("Signed by trusted certificate.\n");
+
+ /*
+ * Standard code signing capabilites required.
+ */
+ int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo);
+ if ( RT_SUCCESS(rc)
+ && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA))
+ {
+ /*
+ * If windows kernel signing, a valid certificate path must be anchored
+ * by the microsoft kernel signing root certificate. The only
+ * alternative is test signing.
+ */
+ if ( pState->fKernel
+ && hCertPaths != NIL_RTCRX509CERTPATHS
+ && pState->enmSignType == VERIFYEXESTATE::kSignType_Windows)
+ {
+ uint32_t cFound = 0;
+ uint32_t cValid = 0;
+ for (uint32_t iPath = 0; iPath < cPaths; iPath++)
+ {
+ bool fTrusted;
+ PCRTCRX509NAME pSubject;
+ PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo;
+ int rcVerify;
+ rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, &pPublicKeyInfo,
+ NULL, NULL /*pCertCtx*/, &rcVerify);
+ AssertRCBreak(rc);
+
+ if (RT_SUCCESS(rcVerify))
+ {
+ Assert(fTrusted);
+ cValid++;
+
+ /* Search the kernel signing root store for a matching anchor. */
+ RTCRSTORECERTSEARCH Search;
+ rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(pState->hKernelRootStore, pSubject, &Search);
+ AssertRCBreak(rc);
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(pState->hKernelRootStore, &Search)) != NULL)
+ {
+ PCRTCRX509SUBJECTPUBLICKEYINFO pPubKeyInfo;
+ if (pCertCtx->pCert)
+ pPubKeyInfo = &pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo;
+ else if (pCertCtx->pTaInfo)
+ pPubKeyInfo = &pCertCtx->pTaInfo->PubKey;
+ else
+ pPubKeyInfo = NULL;
+ if (RTCrX509SubjectPublicKeyInfo_Compare(pPubKeyInfo, pPublicKeyInfo) == 0)
+ cFound++;
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ int rc2 = RTCrStoreCertSearchDestroy(pState->hKernelRootStore, &Search); AssertRC(rc2);
+ }
+ }
+ if (RT_SUCCESS(rc) && cFound == 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Not valid kernel code signature.");
+ if (RT_SUCCESS(rc) && cValid != 2)
+ RTMsgWarning("%u valid paths, expected 2", cValid);
+ }
+ /*
+ * For Mac OS X signing, check for special developer ID attributes.
+ */
+ else if (pState->enmSignType == VERIFYEXESTATE::kSignType_OSX)
+ {
+ uint32_t cDevIdApp = 0;
+ uint32_t cDevIdKext = 0;
+ for (uint32_t i = 0; i < pCert->TbsCertificate.T3.Extensions.cItems; i++)
+ {
+ PCRTCRX509EXTENSION pExt = pCert->TbsCertificate.T3.Extensions.papItems[i];
+ if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) == 0)
+ {
+ cDevIdApp++;
+ if (!pExt->Critical.fValue)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Dev ID Application certificate extension is not flagged critical");
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) == 0)
+ {
+ cDevIdKext++;
+ if (!pExt->Critical.fValue)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Dev ID kext certificate extension is not flagged critical");
+ }
+ }
+ if (cDevIdApp == 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Certificate is missing the 'Dev ID Application' extension");
+ if (cDevIdKext == 0 && pState->fKernel)
+ rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
+ "Certificate is missing the 'Dev ID kext' extension");
+ }
+ }
+
+ return rc;
+}
+
+/** @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} */
+static DECLCALLBACK(int) VerifyExeCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, PRTERRINFO pErrInfo, void *pvUser)
+{
+ VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser;
+ RT_NOREF_PV(hLdrMod);
+
+ switch (pInfo->enmType)
+ {
+ case RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA:
+ {
+ PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->pvSignature;
+
+ /*
+ * Dump the signed data if so requested and it's the first one, assuming that
+ * additional signatures in contained wihtin the same ContentInfo structure.
+ */
+ if (pState->cVerbose && pInfo->iSignature == 0)
+ RTAsn1Dump(&pContentInfo->SeqCore.Asn1Core, 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ /*
+ * We'll try different alternative timestamps here.
+ */
+ struct { RTTIMESPEC TimeSpec; const char *pszDesc; } aTimes[2];
+ unsigned cTimes = 0;
+
+ /* Linking timestamp: */
+ uint64_t uLinkingTime = 0;
+ int rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uLinkingTime, sizeof(uLinkingTime));
+ if (RT_SUCCESS(rc))
+ {
+ RTTimeSpecSetSeconds(&aTimes[0].TimeSpec, uLinkingTime);
+ aTimes[0].pszDesc = "at link time";
+ cTimes++;
+ }
+ else if (rc != VERR_NOT_FOUND)
+ RTMsgError("RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on '%s': %Rrc\n", pState->pszFilename, rc);
+
+ /* Now: */
+ RTTimeNow(&aTimes[cTimes].TimeSpec);
+ aTimes[cTimes].pszDesc = "now";
+ cTimes++;
+
+ /*
+ * Do the actual verification.
+ */
+ for (unsigned iTime = 0; iTime < cTimes; iTime++)
+ {
+ if (pInfo->pvExternalData)
+ rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT
+ /*| RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS - r138075 */,
+ pState->hAdditionalStore, pState->hRootStore,
+ &aTimes[iTime].TimeSpec,
+ VerifyExecCertVerifyCallback, pState,
+ pInfo->pvExternalData, pInfo->cbExternalData, pErrInfo);
+ else
+ rc = RTCrPkcs7VerifySignedData(pContentInfo,
+ RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT
+ | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT
+ /*| RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS - r138075 */,
+ pState->hAdditionalStore, pState->hRootStore,
+ &aTimes[iTime].TimeSpec,
+ VerifyExecCertVerifyCallback, pState, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(rc == VINF_SUCCESS);
+ if (pInfo->cSignatures == 1)
+ RTMsgInfo("'%s' is valid %s.\n", pState->pszFilename, aTimes[iTime].pszDesc);
+ else
+ RTMsgInfo("'%s' signature #%u is valid %s.\n",
+ pState->pszFilename, pInfo->iSignature + 1, aTimes[iTime].pszDesc);
+ pState->cOkay++;
+ return VINF_SUCCESS;
+ }
+ if (rc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME)
+ {
+ if (pInfo->cSignatures == 1)
+ RTMsgError("%s: Failed to verify signature: %Rrc%#RTeim\n", pState->pszFilename, rc, pErrInfo);
+ else
+ RTMsgError("%s: Failed to verify signature #%u: %Rrc%#RTeim\n",
+ pState->pszFilename, pInfo->iSignature + 1, rc, pErrInfo);
+ pState->cBad++;
+ return VINF_SUCCESS;
+ }
+ }
+
+ if (pInfo->cSignatures == 1)
+ RTMsgError("%s: Signature is not valid at present or link time.\n", pState->pszFilename);
+ else
+ RTMsgError("%s: Signature #%u is not valid at present or link time.\n",
+ pState->pszFilename, pInfo->iSignature + 1);
+ pState->cBad++;
+ return VINF_SUCCESS;
+ }
+
+ default:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported signature type: %d", pInfo->enmType);
+ }
+}
+
+/**
+ * Worker for HandleVerifyExe.
+ */
+static RTEXITCODE HandleVerifyExeWorker(VERIFYEXESTATE *pState, const char *pszFilename, PRTERRINFOSTATIC pStaticErrInfo)
+{
+ /*
+ * Open the executable image and verify it.
+ */
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, pState->enmLdrArch, &hLdrMod);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening executable image '%s': %Rrc", pszFilename, rc);
+
+ /* Reset the state. */
+ pState->cBad = 0;
+ pState->cOkay = 0;
+ pState->pszFilename = pszFilename;
+
+ rc = RTLdrVerifySignature(hLdrMod, VerifyExeCallback, pState, RTErrInfoInitStatic(pStaticErrInfo));
+ if (RT_FAILURE(rc))
+ RTMsgError("RTLdrVerifySignature failed on '%s': %Rrc - %s\n", pszFilename, rc, pStaticErrInfo->szMsg);
+
+ int rc2 = RTLdrClose(hLdrMod);
+ if (RT_FAILURE(rc2))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTLdrClose failed: %Rrc\n", rc2);
+ if (RT_FAILURE(rc))
+ return rc != VERR_LDRVI_NOT_SIGNED ? RTEXITCODE_FAILURE : RTEXITCODE_SKIPPED;
+
+ return pState->cOkay > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+static RTEXITCODE HandleVerifyExe(int cArgs, char **papszArgs)
+{
+ RTERRINFOSTATIC StaticErrInfo;
+
+ /* Note! This code does not try to clean up the crypto stores on failure.
+ This is intentional as the code is only expected to be used in a
+ one-command-per-process environment where we do exit() upon
+ returning from this function. */
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--kernel", 'k', RTGETOPT_REQ_NOTHING },
+ { "--root", 'r', RTGETOPT_REQ_STRING },
+ { "--additional", 'a', RTGETOPT_REQ_STRING },
+ { "--add", 'a', RTGETOPT_REQ_STRING },
+ { "--type", 't', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ VERIFYEXESTATE State =
+ {
+ NIL_RTCRSTORE, NIL_RTCRSTORE, NIL_RTCRSTORE, false, 0,
+ VERIFYEXESTATE::kSignType_Windows, RTLDRARCH_WHATEVER,
+ 0, 0, NULL
+ };
+ int rc = RTCrStoreCreateInMem(&State.hRootStore, 0);
+ if (RT_SUCCESS(rc))
+ rc = RTCrStoreCreateInMem(&State.hKernelRootStore, 0);
+ if (RT_SUCCESS(rc))
+ rc = RTCrStoreCreateInMem(&State.hAdditionalStore, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error creating in-memory certificate store: %Rrc", rc);
+
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'r': case 'a':
+ rc = RTCrStoreCertAddFromFile(ch == 'r' ? State.hRootStore : State.hAdditionalStore,
+ RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
+ ValueUnion.psz, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error loading certificate '%s': %Rrc - %s",
+ ValueUnion.psz, rc, StaticErrInfo.szMsg);
+ if (RTErrInfoIsSet(&StaticErrInfo.Core))
+ RTMsgWarning("Warnings loading certificate '%s': %s", ValueUnion.psz, StaticErrInfo.szMsg);
+ break;
+
+ case 't':
+ if (!strcmp(ValueUnion.psz, "win") || !strcmp(ValueUnion.psz, "windows"))
+ State.enmSignType = VERIFYEXESTATE::kSignType_Windows;
+ else if (!strcmp(ValueUnion.psz, "osx") || !strcmp(ValueUnion.psz, "apple"))
+ State.enmSignType = VERIFYEXESTATE::kSignType_OSX;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown signing type: '%s'", ValueUnion.psz);
+ break;
+
+ case 'k': State.fKernel = true; break;
+ case 'v': State.cVerbose++; break;
+ case 'q': State.cVerbose = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpVerifyExe(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Populate the certificate stores according to the signing type.
+ */
+# ifdef VBOX
+ unsigned cSets = 0;
+ struct STSTORESET aSets[6];
+ switch (State.enmSignType)
+ {
+ case VERIFYEXESTATE::kSignType_Windows:
+ aSets[cSets].hStore = State.hRootStore;
+ aSets[cSets].paTAs = g_aSUPTimestampTAs;
+ aSets[cSets].cTAs = g_cSUPTimestampTAs;
+ cSets++;
+ aSets[cSets].hStore = State.hRootStore;
+ aSets[cSets].paTAs = g_aSUPSpcRootTAs;
+ aSets[cSets].cTAs = g_cSUPSpcRootTAs;
+ cSets++;
+ aSets[cSets].hStore = State.hRootStore;
+ aSets[cSets].paTAs = g_aSUPNtKernelRootTAs;
+ aSets[cSets].cTAs = g_cSUPNtKernelRootTAs;
+ cSets++;
+ aSets[cSets].hStore = State.hKernelRootStore;
+ aSets[cSets].paTAs = g_aSUPNtKernelRootTAs;
+ aSets[cSets].cTAs = g_cSUPNtKernelRootTAs;
+ cSets++;
+ break;
+
+ case VERIFYEXESTATE::kSignType_OSX:
+ aSets[cSets].hStore = State.hRootStore;
+ aSets[cSets].paTAs = g_aSUPAppleRootTAs;
+ aSets[cSets].cTAs = g_cSUPAppleRootTAs;
+ cSets++;
+ break;
+ }
+ for (unsigned i = 0; i < cSets; i++)
+ for (unsigned j = 0; j < aSets[i].cTAs; j++)
+ {
+ rc = RTCrStoreCertAddEncoded(aSets[i].hStore, RTCRCERTCTX_F_ENC_TAF_DER, aSets[i].paTAs[j].pch,
+ aSets[i].paTAs[j].cb, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTCrStoreCertAddEncoded failed (%u/%u): %s",
+ i, j, StaticErrInfo.szMsg);
+ }
+# endif /* VBOX */
+
+ /*
+ * Do it.
+ */
+ RTEXITCODE rcExit;
+ for (;;)
+ {
+ rcExit = HandleVerifyExeWorker(&State, ValueUnion.psz, &StaticErrInfo);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+
+ /*
+ * Next file
+ */
+ ch = RTGetOpt(&GetState, &ValueUnion);
+ if (ch == 0)
+ break;
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ {
+ rcExit = RTGetOptPrintError(ch, &ValueUnion);
+ break;
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ uint32_t cRefs;
+ cRefs = RTCrStoreRelease(State.hRootStore); Assert(cRefs == 0);
+ cRefs = RTCrStoreRelease(State.hKernelRootStore); Assert(cRefs == 0);
+ cRefs = RTCrStoreRelease(State.hAdditionalStore); Assert(cRefs == 0);
+
+ return rcExit;
+}
+
+#endif /* !IPRT_IN_BUILD_TOOL */
+
+/*
+ * common code for show-exe and show-cat:
+ */
+
+/**
+ * Display an object ID.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pObjId The object ID to display.
+ * @param pszLabel The field label (prefixed by szPrefix).
+ * @param pszPost What to print after the ID (typically newline).
+ */
+static void HandleShowExeWorkerDisplayObjId(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszLabel, const char *pszPost)
+{
+ int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp));
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cVerbosity > 1)
+ RTPrintf("%s%s%s (%s)%s", pThis->szPrefix, pszLabel, pThis->szTmp, pObjId->szObjId, pszPost);
+ else
+ RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pThis->szTmp, pszPost);
+ }
+ else
+ RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pObjId->szObjId, pszPost);
+}
+
+
+/**
+ * Display an object ID, without prefix and label
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pObjId The object ID to display.
+ * @param pszPost What to print after the ID (typically newline).
+ */
+static void HandleShowExeWorkerDisplayObjIdSimple(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszPost)
+{
+ int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp));
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cVerbosity > 1)
+ RTPrintf("%s (%s)%s", pThis->szTmp, pObjId->szObjId, pszPost);
+ else
+ RTPrintf("%s%s", pThis->szTmp, pszPost);
+ }
+ else
+ RTPrintf("%s%s", pObjId->szObjId, pszPost);
+}
+
+
+/**
+ * Display a signer info attribute.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param offPrefix The current prefix offset.
+ * @param pAttr The attribute to display.
+ */
+static int HandleShowExeWorkerPkcs7DisplayAttrib(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7ATTRIBUTE pAttr)
+{
+ HandleShowExeWorkerDisplayObjId(pThis, &pAttr->Type, "", ":\n");
+
+ int rc = VINF_SUCCESS;
+ switch (pAttr->enmType)
+ {
+ case RTCRPKCS7ATTRIBUTETYPE_UNKNOWN:
+ if (pAttr->uValues.pCores->cItems <= 1)
+ RTPrintf("%s %u bytes\n", pThis->szPrefix,pAttr->uValues.pCores->SetCore.Asn1Core.cb);
+ else
+ RTPrintf("%s %u bytes divided by %u items\n", pThis->szPrefix, pAttr->uValues.pCores->SetCore.Asn1Core.cb, pAttr->uValues.pCores->cItems);
+ break;
+
+ /* Object IDs, use pObjIds. */
+ case RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS:
+ if (pAttr->uValues.pObjIds->cItems != 1)
+ RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIds->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pObjIds->cItems; i++)
+ {
+ if (pAttr->uValues.pObjIds->cItems == 1)
+ RTPrintf("%s ", pThis->szPrefix);
+ else
+ RTPrintf("%s ObjId[%u]: ", pThis->szPrefix, i);
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIds->papItems[i], "\n");
+ }
+ break;
+
+ /* Sequence of object IDs, use pObjIdSeqs. */
+ case RTCRPKCS7ATTRIBUTETYPE_MS_STATEMENT_TYPE:
+ if (pAttr->uValues.pObjIdSeqs->cItems != 1)
+ RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIdSeqs->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pObjIdSeqs->cItems; i++)
+ {
+ uint32_t const cObjIds = pAttr->uValues.pObjIdSeqs->papItems[i]->cItems;
+ for (unsigned j = 0; j < cObjIds; j++)
+ {
+ if (pAttr->uValues.pObjIdSeqs->cItems == 1)
+ RTPrintf("%s ", pThis->szPrefix);
+ else
+ RTPrintf("%s ObjIdSeq[%u]: ", pThis->szPrefix, i);
+ if (cObjIds != 1)
+ RTPrintf(" ObjId[%u]: ", j);
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIdSeqs->papItems[i]->papItems[i], "\n");
+ }
+ }
+ break;
+
+ /* Octet strings, use pOctetStrings. */
+ case RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS:
+ if (pAttr->uValues.pOctetStrings->cItems != 1)
+ RTPrintf("%s%u octet strings:", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++)
+ {
+ PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i];
+ uint32_t cbContent = pOctetString->Asn1Core.cb;
+ if (cbContent > 0 && (cbContent <= 128 || pThis->cVerbosity >= 2))
+ {
+ uint8_t const *pbContent = pOctetString->Asn1Core.uData.pu8;
+ uint32_t off = 0;
+ while (off < cbContent)
+ {
+ uint32_t cbNow = RT_MIN(cbContent - off, 16);
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pbContent[off]);
+ else
+ RTPrintf("%s OctetString[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pbContent[off]);
+ off += cbNow;
+ }
+ }
+ else
+ RTPrintf("%s: OctetString[%u]: %u bytes\n", pThis->szPrefix, i, pOctetString->Asn1Core.cb);
+ }
+ break;
+
+ /* Counter signatures (PKCS \#9), use pCounterSignatures. */
+ case RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES:
+ RTPrintf("%sTODO: RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES! %u bytes\n",
+ pThis->szPrefix, pAttr->uValues.pCounterSignatures->SetCore.Asn1Core.cb);
+ break;
+
+ /* Signing time (PKCS \#9), use pSigningTime. */
+ case RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME:
+ for (uint32_t i = 0; i < pAttr->uValues.pSigningTime->cItems; i++)
+ {
+ PCRTASN1TIME pTime = pAttr->uValues.pSigningTime->papItems[i];
+ char szTS[RTTIME_STR_LEN];
+ RTTimeToString(&pTime->Time, szTS, sizeof(szTS));
+ if (pAttr->uValues.pSigningTime->cItems == 1)
+ RTPrintf("%s %s (%.*s)\n", pThis->szPrefix, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch);
+ else
+ RTPrintf("%s #%u: %s (%.*s)\n", pThis->szPrefix, i, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch);
+ }
+ break;
+
+ /* Microsoft timestamp info (RFC-3161) signed data, use pContentInfo. */
+ case RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP:
+ case RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE:
+ if (pAttr->uValues.pContentInfos->cItems > 1)
+ RTPrintf("%s%u nested signatures, %u bytes in total\n", pThis->szPrefix,
+ pAttr->uValues.pContentInfos->cItems, pAttr->uValues.pContentInfos->SetCore.Asn1Core.cb);
+ for (unsigned i = 0; i < pAttr->uValues.pContentInfos->cItems; i++)
+ {
+ size_t offPrefix2 = offPrefix;
+ if (pAttr->uValues.pContentInfos->cItems > 1)
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig[%u]: ", i);
+ else
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " ");
+ // offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig: ", i);
+ PCRTCRPKCS7CONTENTINFO pContentInfo = pAttr->uValues.pContentInfos->papItems[i];
+ int rc2;
+ if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ rc2 = HandleShowExeWorkerPkcs7Display(pThis, pContentInfo->u.pSignedData, offPrefix2, pContentInfo);
+ else
+ rc2 = RTMsgErrorRc(VERR_ASN1_UNEXPECTED_OBJ_ID, "%sPKCS#7 content in nested signature is not 'signedData': %s",
+ pThis->szPrefix, pContentInfo->ContentType.szObjId);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ break;
+
+ case RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST:
+ if (pAttr->uValues.pContentInfos->cItems != 1)
+ RTPrintf("%s%u plists, expected only 1.\n", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems);
+ for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++)
+ {
+ PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i];
+ size_t cbContent = pOctetString->Asn1Core.cb;
+ char const *pchContent = pOctetString->Asn1Core.uData.pch;
+ rc = RTStrValidateEncodingEx(pchContent, cbContent, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ while (cbContent > 0)
+ {
+ const char *pchNewLine = (const char *)memchr(pchContent, '\n', cbContent);
+ size_t cchToWrite = pchNewLine ? pchNewLine - pchContent : cbContent;
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %.*s\n", pThis->szPrefix, cchToWrite, pchContent);
+ else
+ RTPrintf("%s plist[%u]: %.*s\n", pThis->szPrefix, i, cchToWrite, pchContent);
+ if (!pchNewLine)
+ break;
+ pchContent = pchNewLine + 1;
+ cbContent -= cchToWrite + 1;
+ }
+ }
+ else
+ {
+ if (pAttr->uValues.pContentInfos->cItems != 1)
+ RTPrintf("%s: plist[%u]: Invalid UTF-8: %Rrc\n", pThis->szPrefix, i, rc);
+ else
+ RTPrintf("%s: Invalid UTF-8: %Rrc\n", pThis->szPrefix, rc);
+ for (uint32_t off = 0; off < cbContent; off += 16)
+ {
+ size_t cbNow = RT_MIN(cbContent - off, 16);
+ if (pAttr->uValues.pOctetStrings->cItems == 1)
+ RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pchContent[off]);
+ else
+ RTPrintf("%s plist[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pchContent[off]);
+ }
+ }
+ }
+ break;
+
+ case RTCRPKCS7ATTRIBUTETYPE_INVALID:
+ RTPrintf("%sINVALID!\n", pThis->szPrefix);
+ break;
+ case RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT:
+ RTPrintf("%sNOT PRESENT!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%senmType=%d!\n", pThis->szPrefix, pAttr->enmType);
+ break;
+ }
+ return rc;
+}
+
+
+/**
+ * Displays a Microsoft SPC indirect data structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param offPrefix The current prefix offset.
+ * @param pIndData The indirect data to display.
+ */
+static int HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(PSHOWEXEPKCS7 pThis, size_t offPrefix,
+ PCRTCRSPCINDIRECTDATACONTENT pIndData)
+{
+ /*
+ * The image hash.
+ */
+ RTDIGESTTYPE const enmDigestType = RTCrX509AlgorithmIdentifier_QueryDigestType(&pIndData->DigestInfo.DigestAlgorithm);
+ const char *pszDigestType = RTCrDigestTypeToName(enmDigestType);
+ RTPrintf("%s Digest Type: %s", pThis->szPrefix, pszDigestType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)\n", pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId);
+ else
+ RTPrintf("\n");
+ RTPrintf("%s Digest: %.*Rhxs\n",
+ pThis->szPrefix, pIndData->DigestInfo.Digest.Asn1Core.cb, pIndData->DigestInfo.Digest.Asn1Core.uData.pu8);
+
+ /*
+ * The data/file/url.
+ */
+ switch (pIndData->Data.enmType)
+ {
+ case RTCRSPCAAOVTYPE_PE_IMAGE_DATA:
+ {
+ RTPrintf("%s Data Type: PE Image Data\n", pThis->szPrefix);
+ PRTCRSPCPEIMAGEDATA pPeImage = pIndData->Data.uValue.pPeImage;
+ /** @todo display "Flags". */
+
+ switch (pPeImage->T0.File.enmChoice)
+ {
+ case RTCRSPCLINKCHOICE_MONIKER:
+ {
+ PRTCRSPCSERIALIZEDOBJECT pMoniker = pPeImage->T0.File.u.pMoniker;
+ if (RTCrSpcSerializedObject_IsPresent(pMoniker))
+ {
+ if (RTUuidCompareStr(pMoniker->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0)
+ {
+ RTPrintf("%s Moniker: SpcSerializedObject (%RTuuid)\n",
+ pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid);
+
+ PCRTCRSPCSERIALIZEDOBJECTATTRIBUTES pData = pMoniker->u.pData;
+ if (pData)
+ for (uint32_t i = 0; i < pData->cItems; i++)
+ {
+ RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix,
+ "MonikerAttrib[%u]: ", i);
+
+ switch (pData->papItems[i]->enmType)
+ {
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2:
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1:
+ {
+ PCRTCRSPCSERIALIZEDPAGEHASHES pPgHashes = pData->papItems[i]->u.pPageHashes;
+ uint32_t const cbHash = pData->papItems[i]->enmType
+ == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1
+ ? 160/8 /*SHA-1*/ : 256/8 /*SHA-256*/;
+ uint32_t const cPages = pPgHashes->RawData.Asn1Core.cb / (cbHash + sizeof(uint32_t));
+
+ RTPrintf("%sPage Hashes version %u - %u pages (%u bytes total)\n", pThis->szPrefix,
+ pData->papItems[i]->enmType
+ == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1 ? 1 : 2,
+ cPages, pPgHashes->RawData.Asn1Core.cb);
+ if (pThis->cVerbosity > 0)
+ {
+ PCRTCRSPCPEIMAGEPAGEHASHES pPg = pPgHashes->pData;
+ for (unsigned iPg = 0; iPg < cPages; iPg++)
+ {
+ uint32_t offHash = 0;
+ do
+ {
+ if (offHash == 0)
+ RTPrintf("%.*s Page#%04u/%#08x: ",
+ offPrefix, pThis->szPrefix, iPg, pPg->Generic.offFile);
+ else
+ RTPrintf("%.*s ", offPrefix, pThis->szPrefix);
+ uint32_t cbLeft = cbHash - offHash;
+ if (cbLeft > 24)
+ cbLeft = 16;
+ RTPrintf("%.*Rhxs\n", cbLeft, &pPg->Generic.abHash[offHash]);
+ offHash += cbLeft;
+ } while (offHash < cbHash);
+ pPg = (PCRTCRSPCPEIMAGEPAGEHASHES)&pPg->Generic.abHash[cbHash];
+ }
+
+ if (pThis->cVerbosity > 3)
+ RTPrintf("%.*Rhxd\n",
+ pPgHashes->RawData.Asn1Core.cb,
+ pPgHashes->RawData.Asn1Core.uData.pu8);
+ }
+ break;
+ }
+
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_UNKNOWN:
+ HandleShowExeWorkerDisplayObjIdSimple(pThis, &pData->papItems[i]->Type, "\n");
+ break;
+ case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_NOT_PRESENT:
+ RTPrintf("%sNot present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%senmType=%d!\n", pThis->szPrefix, pData->papItems[i]->enmType);
+ break;
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+ else
+ RTPrintf("%s pData is NULL!\n", pThis->szPrefix);
+ }
+ else
+ RTPrintf("%s Moniker: Unknown UUID: %RTuuid\n",
+ pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid);
+ }
+ else
+ RTPrintf("%s Moniker: not present\n", pThis->szPrefix);
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_URL:
+ {
+ const char *pszUrl = NULL;
+ int rc = pPeImage->T0.File.u.pUrl
+ ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pUrl, &pszUrl, NULL)
+ : VERR_NOT_FOUND;
+ if (RT_SUCCESS(rc))
+ RTPrintf("%s URL: '%s'\n", pThis->szPrefix, pszUrl);
+ else
+ RTPrintf("%s URL: rc=%Rrc\n", pThis->szPrefix, rc);
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_FILE:
+ {
+ const char *pszFile = NULL;
+ int rc = pPeImage->T0.File.u.pT2 && pPeImage->T0.File.u.pT2->File.u.pAscii
+ ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pT2->File.u.pAscii, &pszFile, NULL)
+ : VERR_NOT_FOUND;
+ if (RT_SUCCESS(rc))
+ RTPrintf("%s File: '%s'\n", pThis->szPrefix, pszFile);
+ else
+ RTPrintf("%s File: rc=%Rrc\n", pThis->szPrefix, rc);
+ break;
+ }
+
+ case RTCRSPCLINKCHOICE_NOT_PRESENT:
+ RTPrintf("%s File not present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%s enmChoice=%d!\n", pThis->szPrefix, pPeImage->T0.File.enmChoice);
+ break;
+ }
+ break;
+ }
+
+ case RTCRSPCAAOVTYPE_UNKNOWN:
+ HandleShowExeWorkerDisplayObjId(pThis, &pIndData->Data.Type, " Data Type: ", "\n");
+ break;
+ case RTCRSPCAAOVTYPE_NOT_PRESENT:
+ RTPrintf("%s Data Type: Not present!\n", pThis->szPrefix);
+ break;
+ default:
+ RTPrintf("%s Data Type: enmType=%d!\n", pThis->szPrefix, pIndData->Data.enmType);
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Display an PKCS#7 signed data instance.
+ *
+ * @returns IPRT status code.
+ * @param pThis The show exe instance data.
+ * @param pSignedData The signed data to display.
+ * @param offPrefix The current prefix offset.
+ * @param pContentInfo The content info structure (for the size).
+ */
+static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix,
+ PCRTCRPKCS7CONTENTINFO pContentInfo)
+{
+ pThis->szPrefix[offPrefix] = '\0';
+ RTPrintf("%sPKCS#7 signature: %u (%#x) bytes\n", pThis->szPrefix,
+ RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core),
+ RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core));
+
+ /*
+ * Display list of signing algorithms.
+ */
+ RTPrintf("%sDigestAlgorithms: ", pThis->szPrefix);
+ if (pSignedData->DigestAlgorithms.cItems == 0)
+ RTPrintf("none");
+ for (unsigned i = 0; i < pSignedData->DigestAlgorithms.cItems; i++)
+ {
+ PCRTCRX509ALGORITHMIDENTIFIER pAlgoId = pSignedData->DigestAlgorithms.papItems[i];
+ const char *pszDigestType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_QueryDigestType(pAlgoId));
+ if (!pszDigestType)
+ pszDigestType = pAlgoId->Algorithm.szObjId;
+ RTPrintf(i == 0 ? "%s" : ", %s", pszDigestType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)", pAlgoId->Algorithm.szObjId);
+ }
+ RTPrintf("\n");
+
+ /*
+ * Display the signed data content.
+ */
+ if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCRSPCINDIRECTDATACONTENT_OID) == 0)
+ {
+ RTPrintf("%s ContentType: SpcIndirectDataContent (" RTCRSPCINDIRECTDATACONTENT_OID ")\n", pThis->szPrefix);
+ size_t offPrefix2 = RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " SPC Ind Data: ");
+ HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(pThis, offPrefix2 + offPrefix,
+ pSignedData->ContentInfo.u.pIndirectDataContent);
+ pThis->szPrefix[offPrefix] = '\0';
+ }
+ else
+ HandleShowExeWorkerDisplayObjId(pThis, &pSignedData->ContentInfo.ContentType, " ContentType: ", " - not implemented.\n");
+
+ /*
+ * Display certificates (Certificates).
+ */
+ if (pSignedData->Certificates.cItems > 0)
+ {
+ RTPrintf("%s Certificates: %u\n", pThis->szPrefix, pSignedData->Certificates.cItems);
+ if (pThis->cVerbosity >= 2)
+ {
+ for (uint32_t i = 0; i < pSignedData->Certificates.cItems; i++)
+ {
+ if (i != 0)
+ RTPrintf("\n");
+ RTPrintf("%s Certificate #%u:\n", pThis->szPrefix, i);
+ RTAsn1Dump(RTCrPkcs7Cert_GetAsn1Core(pSignedData->Certificates.papItems[i]), 0,
+ ((uint32_t)offPrefix + 9) / 2, RTStrmDumpPrintfV, g_pStdOut);
+ }
+ }
+ /** @todo display certificates properly. */
+ }
+
+ if (pSignedData->Crls.cb > 0)
+ RTPrintf("%s CRLs: %u bytes\n", pThis->szPrefix, pSignedData->Crls.cb);
+
+ /*
+ * Show signatures (SignerInfos).
+ */
+ unsigned const cSigInfos = pSignedData->SignerInfos.cItems;
+ if (cSigInfos != 1)
+ RTPrintf("%s SignerInfos: %u signers\n", pThis->szPrefix, cSigInfos);
+ else
+ RTPrintf("%s SignerInfos:\n", pThis->szPrefix);
+ for (unsigned i = 0; i < cSigInfos; i++)
+ {
+ PRTCRPKCS7SIGNERINFO pSigInfo = pSignedData->SignerInfos.papItems[i];
+ size_t offPrefix2 = offPrefix;
+ if (cSigInfos != 1)
+ offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "SignerInfo[%u]: ", i);
+
+ int rc = RTAsn1Integer_ToString(&pSigInfo->IssuerAndSerialNumber.SerialNumber,
+ pThis->szTmp, sizeof(pThis->szTmp), 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc);
+ RTPrintf("%s Serial No: %s\n", pThis->szPrefix, pThis->szTmp);
+
+ rc = RTCrX509Name_FormatAsString(&pSigInfo->IssuerAndSerialNumber.Name, pThis->szTmp, sizeof(pThis->szTmp), NULL);
+ if (RT_FAILURE(rc))
+ RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc);
+ RTPrintf("%s Issuer: %s\n", pThis->szPrefix, pThis->szTmp);
+
+ const char *pszType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_QueryDigestType(&pSigInfo->DigestAlgorithm));
+ if (!pszType)
+ pszType = pSigInfo->DigestAlgorithm.Algorithm.szObjId;
+ RTPrintf("%s Digest Algorithm: %s", pThis->szPrefix, pszType);
+ if (pThis->cVerbosity > 1)
+ RTPrintf(" (%s)\n", pSigInfo->DigestAlgorithm.Algorithm.szObjId);
+ else
+ RTPrintf("\n");
+
+ HandleShowExeWorkerDisplayObjId(pThis, &pSigInfo->DigestEncryptionAlgorithm.Algorithm,
+ "Digest Encryption Algorithm: ", "\n");
+
+ if (pSigInfo->AuthenticatedAttributes.cItems == 0)
+ RTPrintf("%s Authenticated Attributes: none\n", pThis->szPrefix);
+ else
+ {
+ RTPrintf("%s Authenticated Attributes: %u item%s\n", pThis->szPrefix,
+ pSigInfo->AuthenticatedAttributes.cItems, pSigInfo->AuthenticatedAttributes.cItems > 1 ? "s" : "");
+ for (unsigned j = 0; j < pSigInfo->AuthenticatedAttributes.cItems; j++)
+ {
+ PRTCRPKCS7ATTRIBUTE pAttr = pSigInfo->AuthenticatedAttributes.papItems[j];
+ size_t offPrefix3 = offPrefix2 + RTStrPrintf(&pThis->szPrefix[offPrefix2], sizeof(pThis->szPrefix) - offPrefix2,
+ " AuthAttrib[%u]: ", j);
+ HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr);
+ }
+ pThis->szPrefix[offPrefix2] = '\0';
+ }
+
+ if (pSigInfo->UnauthenticatedAttributes.cItems == 0)
+ RTPrintf("%s Unauthenticated Attributes: none\n", pThis->szPrefix);
+ else
+ {
+ RTPrintf("%s Unauthenticated Attributes: %u item%s\n", pThis->szPrefix,
+ pSigInfo->UnauthenticatedAttributes.cItems, pSigInfo->UnauthenticatedAttributes.cItems > 1 ? "s" : "");
+ for (unsigned j = 0; j < pSigInfo->UnauthenticatedAttributes.cItems; j++)
+ {
+ PRTCRPKCS7ATTRIBUTE pAttr = pSigInfo->UnauthenticatedAttributes.papItems[j];
+ size_t offPrefix3 = offPrefix2 + RTStrPrintf(&pThis->szPrefix[offPrefix2], sizeof(pThis->szPrefix) - offPrefix2,
+ " UnauthAttrib[%u]: ", j);
+ HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr);
+ }
+ pThis->szPrefix[offPrefix2] = '\0';
+ }
+
+ /** @todo show the encrypted stuff (EncryptedDigest)? */
+ }
+ pThis->szPrefix[offPrefix] = '\0';
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * The 'show-exe' command.
+ */
+static RTEXITCODE HelpShowExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm,
+ "show-exe [--verbose|-v] [--quiet|-q] <exe1> [exe2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleShowExe(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+ RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'q': cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpShowExe(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Do it.
+ */
+ unsigned iFile = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ do
+ {
+ RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz);
+
+ SHOWEXEPKCS7 This;
+ RT_ZERO(This);
+ This.cVerbosity = cVerbosity;
+
+ RTEXITCODE rcExitThis = SignToolPkcs7Exe_InitFromFile(&This, ValueUnion.psz, cVerbosity, enmLdrArch);
+ if (rcExitThis == RTEXITCODE_SUCCESS)
+ {
+ rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExitThis;
+
+ iFile++;
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION);
+ if (ch != 0)
+ return RTGetOptPrintError(ch, &ValueUnion);
+
+ return rcExit;
+}
+
+
+/*
+ * The 'show-cat' command.
+ */
+static RTEXITCODE HelpShowCat(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm,
+ "show-cat [--verbose|-v] [--quiet|-q] <cat1> [cat2 [..]]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE HandleShowCat(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ unsigned cVerbosity = 0;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (ch)
+ {
+ case 'v': cVerbosity++; break;
+ case 'q': cVerbosity = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpShowCat(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (ch != VINF_GETOPT_NOT_OPTION)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given.");
+
+ /*
+ * Do it.
+ */
+ unsigned iFile = 0;
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ do
+ {
+ RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz);
+
+ SHOWEXEPKCS7 This;
+ RT_ZERO(This);
+ This.cVerbosity = cVerbosity;
+
+ RTEXITCODE rcExitThis = SignToolPkcs7_InitFromFile(&This, ValueUnion.psz, cVerbosity);
+ if (rcExitThis == RTEXITCODE_SUCCESS)
+ {
+ This.hLdrMod = NIL_RTLDRMOD;
+
+ rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+ SignToolPkcs7Exe_Delete(&This);
+ }
+ if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExitThis;
+
+ iFile++;
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION);
+ if (ch != 0)
+ return RTGetOptPrintError(ch, &ValueUnion);
+
+ return rcExit;
+}
+
+
+/*
+ * The 'make-tainfo' command.
+ */
+static RTEXITCODE HelpMakeTaInfo(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm,
+ "make-tainfo [--verbose|--quiet] [--cert <cert.der>] [-o|--output] <tainfo.der>\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+
+typedef struct MAKETAINFOSTATE
+{
+ int cVerbose;
+ const char *pszCert;
+ const char *pszOutput;
+} MAKETAINFOSTATE;
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER} */
+static DECLCALLBACK(int) handleMakeTaInfoWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(pErrInfo);
+ return RTStrmWrite((PRTSTREAM)pvUser, pvBuf, cbToWrite);
+}
+
+
+static RTEXITCODE HandleMakeTaInfo(int cArgs, char **papszArgs)
+{
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--cert", 'c', RTGETOPT_REQ_STRING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ };
+
+ MAKETAINFOSTATE State = { 0, NULL, NULL };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'c':
+ if (State.pszCert)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --cert option can only be used once.");
+ State.pszCert = ValueUnion.psz;
+ break;
+
+ case 'o':
+ case VINF_GETOPT_NOT_OPTION:
+ if (State.pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Multiple output files specified.");
+ State.pszOutput = ValueUnion.psz;
+ break;
+
+ case 'v': State.cVerbose++; break;
+ case 'q': State.cVerbose = 0; break;
+ case 'V': return HandleVersion(cArgs, papszArgs);
+ case 'h': return HelpMakeTaInfo(g_pStdOut, RTSIGNTOOLHELP_FULL);
+ default: return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ if (!State.pszCert)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input certificate was specified.");
+ if (!State.pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file was specified.");
+
+ /*
+ * Read the certificate.
+ */
+ RTERRINFOSTATIC StaticErrInfo;
+ RTCRX509CERTIFICATE Certificate;
+ rc = RTCrX509Certificate_ReadFromFile(&Certificate, State.pszCert, 0, &g_RTAsn1DefaultAllocator,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error reading certificate from %s: %Rrc - %s",
+ State.pszCert, rc, StaticErrInfo.szMsg);
+ /*
+ * Construct the trust anchor information.
+ */
+ RTCRTAFTRUSTANCHORINFO TrustAnchor;
+ rc = RTCrTafTrustAnchorInfo_Init(&TrustAnchor, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Public key. */
+ Assert(RTCrX509SubjectPublicKeyInfo_IsPresent(&TrustAnchor.PubKey));
+ RTCrX509SubjectPublicKeyInfo_Delete(&TrustAnchor.PubKey);
+ rc = RTCrX509SubjectPublicKeyInfo_Clone(&TrustAnchor.PubKey, &Certificate.TbsCertificate.SubjectPublicKeyInfo,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrX509SubjectPublicKeyInfo_Clone failed: %Rrc", rc);
+ RTAsn1Core_ResetImplict(RTCrX509SubjectPublicKeyInfo_GetAsn1Core(&TrustAnchor.PubKey)); /* temporary hack. */
+
+ /* Key Identifier. */
+ PCRTASN1OCTETSTRING pKeyIdentifier = NULL;
+ if (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_KEY_IDENTIFIER)
+ pKeyIdentifier = Certificate.TbsCertificate.T3.pSubjectKeyIdentifier;
+ else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_AUTHORITY_KEY_IDENTIFIER)
+ && RTCrX509Certificate_IsSelfSigned(&Certificate)
+ && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier) )
+ pKeyIdentifier = &Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier;
+ else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_OLD_AUTHORITY_KEY_IDENTIFIER)
+ && RTCrX509Certificate_IsSelfSigned(&Certificate)
+ && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier) )
+ pKeyIdentifier = &Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier;
+ if (pKeyIdentifier && pKeyIdentifier->Asn1Core.cb > 0)
+ {
+ Assert(RTAsn1OctetString_IsPresent(&TrustAnchor.KeyIdentifier));
+ RTAsn1OctetString_Delete(&TrustAnchor.KeyIdentifier);
+ rc = RTAsn1OctetString_Clone(&TrustAnchor.KeyIdentifier, pKeyIdentifier, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTAsn1OctetString_Clone failed: %Rrc", rc);
+ RTAsn1Core_ResetImplict(RTAsn1OctetString_GetAsn1Core(&TrustAnchor.KeyIdentifier)); /* temporary hack. */
+ }
+ else
+ RTMsgWarning("No key identifier found or has zero length.");
+
+ /* Subject */
+ if (RT_SUCCESS(rc))
+ {
+ Assert(!RTCrTafCertPathControls_IsPresent(&TrustAnchor.CertPath));
+ rc = RTCrTafCertPathControls_Init(&TrustAnchor.CertPath, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(RTCrX509Name_IsPresent(&TrustAnchor.CertPath.TaName));
+ RTCrX509Name_Delete(&TrustAnchor.CertPath.TaName);
+ rc = RTCrX509Name_Clone(&TrustAnchor.CertPath.TaName, &Certificate.TbsCertificate.Subject,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1Core_ResetImplict(RTCrX509Name_GetAsn1Core(&TrustAnchor.CertPath.TaName)); /* temporary hack. */
+ rc = RTCrX509Name_RecodeAsUtf8(&TrustAnchor.CertPath.TaName, &g_RTAsn1DefaultAllocator);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrX509Name_RecodeAsUtf8 failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrX509Name_Clone failed: %Rrc", rc);
+ }
+ else
+ RTMsgError("RTCrTafCertPathControls_Init failed: %Rrc", rc);
+ }
+
+ /* Check that what we've constructed makes some sense. */
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrTafTrustAnchorInfo_CheckSanity(&TrustAnchor, 0, RTErrInfoInitStatic(&StaticErrInfo), "TAI");
+ if (RT_FAILURE(rc))
+ RTMsgError("RTCrTafTrustAnchorInfo_CheckSanity failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Encode it and write it to the output file.
+ */
+ uint32_t cbEncoded;
+ rc = RTAsn1EncodePrepare(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER, &cbEncoded,
+ RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ if (State.cVerbose >= 1)
+ RTAsn1Dump(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), 0, 0, RTStrmDumpPrintfV, g_pStdOut);
+
+ PRTSTREAM pStrm;
+ rc = RTStrmOpen(State.pszOutput, "wb", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1EncodeWrite(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER,
+ handleMakeTaInfoWriter, pStrm, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrmClose(pStrm);
+ if (RT_SUCCESS(rc))
+ RTMsgInfo("Successfully wrote TrustedAnchorInfo to '%s'.", State.pszOutput);
+ else
+ RTMsgError("RTStrmClose failed: %Rrc", rc);
+ }
+ else
+ {
+ RTMsgError("RTAsn1EncodeWrite failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ RTStrmClose(pStrm);
+ }
+ }
+ else
+ RTMsgError("Error opening '%s' for writing: %Rrcs", State.pszOutput, rc);
+ }
+ else
+ RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg);
+ }
+
+ RTCrTafTrustAnchorInfo_Delete(&TrustAnchor);
+ }
+ else
+ RTMsgError("RTCrTafTrustAnchorInfo_Init failed: %Rrc", rc);
+
+ RTCrX509Certificate_Delete(&Certificate);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+
+/*
+ * The 'version' command.
+ */
+static RTEXITCODE HelpVersion(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "version\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleVersion(int cArgs, char **papszArgs)
+{
+ RT_NOREF_PV(cArgs); RT_NOREF_PV(papszArgs);
+#ifndef IN_BLD_PROG /* RTBldCfgVersion or RTBldCfgRevision in build time IPRT lib. */
+ RTPrintf("%s\n", RTBldCfgVersion());
+ return RTEXITCODE_SUCCESS;
+#else
+ return RTEXITCODE_FAILURE;
+#endif
+}
+
+
+
+/**
+ * Command mapping.
+ */
+static struct
+{
+ /** The command. */
+ const char *pszCmd;
+ /**
+ * Handle the command.
+ * @returns Program exit code.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The argument vector, starting with the command name.
+ */
+ RTEXITCODE (*pfnHandler)(int cArgs, char **papszArgs);
+ /**
+ * Produce help.
+ * @returns RTEXITCODE_SUCCESS to simplify handling '--help' in the handler.
+ * @param pStrm Where to send help text.
+ * @param enmLevel The level of the help information.
+ */
+ RTEXITCODE (*pfnHelp)(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel);
+}
+/** Mapping commands to handler and helper functions. */
+const g_aCommands[] =
+{
+ { "extract-exe-signer-cert", HandleExtractExeSignerCert, HelpExtractExeSignerCert },
+ { "add-nested-exe-signature", HandleAddNestedExeSignature, HelpAddNestedExeSignature },
+ { "add-nested-cat-signature", HandleAddNestedCatSignature, HelpAddNestedCatSignature },
+#ifndef IPRT_IN_BUILD_TOOL
+ { "verify-exe", HandleVerifyExe, HelpVerifyExe },
+#endif
+ { "show-exe", HandleShowExe, HelpShowExe },
+ { "show-cat", HandleShowCat, HelpShowCat },
+ { "make-tainfo", HandleMakeTaInfo, HelpMakeTaInfo },
+ { "help", HandleHelp, HelpHelp },
+ { "--help", HandleHelp, NULL },
+ { "-h", HandleHelp, NULL },
+ { "version", HandleVersion, HelpVersion },
+ { "--version", HandleVersion, NULL },
+ { "-V", HandleVersion, NULL },
+};
+
+
+/*
+ * The 'help' command.
+ */
+static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel)
+{
+ RT_NOREF_PV(enmLevel);
+ RTStrmPrintf(pStrm, "help [cmd-patterns]\n");
+ return RTEXITCODE_SUCCESS;
+}
+
+static RTEXITCODE HandleHelp(int cArgs, char **papszArgs)
+{
+ RTSIGNTOOLHELP enmLevel = cArgs <= 1 ? RTSIGNTOOLHELP_USAGE : RTSIGNTOOLHELP_FULL;
+ uint32_t cShowed = 0;
+ for (uint32_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
+ {
+ if (g_aCommands[iCmd].pfnHelp)
+ {
+ bool fShow = false;
+ if (cArgs <= 1)
+ fShow = true;
+ else
+ {
+ for (int iArg = 1; iArg < cArgs; iArg++)
+ if (RTStrSimplePatternMultiMatch(papszArgs[iArg], RTSTR_MAX, g_aCommands[iCmd].pszCmd, RTSTR_MAX, NULL))
+ {
+ fShow = true;
+ break;
+ }
+ }
+ if (fShow)
+ {
+ g_aCommands[iCmd].pfnHelp(g_pStdOut, enmLevel);
+ cShowed++;
+ }
+ }
+ }
+ return cShowed ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse global arguments.
+ */
+ int iArg = 1;
+ /* none presently. */
+
+ /*
+ * Command dispatcher.
+ */
+ if (iArg < argc)
+ {
+ const char *pszCmd = argv[iArg];
+ uint32_t i = RT_ELEMENTS(g_aCommands);
+ while (i-- > 0)
+ if (!strcmp(g_aCommands[i].pszCmd, pszCmd))
+ return g_aCommands[i].pfnHandler(argc - iArg, &argv[iArg]);
+ RTMsgError("Unknown command '%s'.", pszCmd);
+ }
+ else
+ RTMsgError("No command given. (try --help)");
+
+ return RTEXITCODE_SYNTAX;
+}
+
diff --git a/src/VBox/Runtime/tools/RTTar.cpp b/src/VBox/Runtime/tools/RTTar.cpp
new file mode 100644
index 00000000..f9c961b8
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTTar.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTTar.cpp $ */
+/** @file
+ * IPRT - TAR Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipTarCmd(argc, argv);
+}
+
diff --git a/src/VBox/Runtime/tools/RTTraceLogTool.cpp b/src/VBox/Runtime/tools/RTTraceLogTool.cpp
new file mode 100644
index 00000000..4d92b5ba
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTTraceLogTool.cpp
@@ -0,0 +1,330 @@
+/* $Id: RTTraceLogTool.cpp $ */
+/** @file
+ * IPRT - Utility for reading/receiving and dissecting trace logs.
+ */
+
+/*
+ * Copyright (C) 2018-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/tracelog.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+
+
+/**
+ * The tracelog tool TCP server/client state.
+ */
+typedef struct RTTRACELOGTOOLTCP
+{
+ /** Flag whether this is a server. */
+ bool fIsServer;
+ /** The TCP socket handle for the connection. */
+ RTSOCKET hSock;
+ /** The TCP server. */
+ PRTTCPSERVER pTcpSrv;
+} RTTRACELOGTOOLTCP;
+/** Pointer to the TCP server/client state. */
+typedef RTTRACELOGTOOLTCP *PRTTRACELOGTOOLTCP;
+
+
+static void rtTraceLogTcpDestroy(PRTTRACELOGTOOLTCP pTrcLogTcp)
+{
+ if (pTrcLogTcp->fIsServer)
+ RTTcpServerDestroy(pTrcLogTcp->pTcpSrv);
+ if (pTrcLogTcp->hSock != NIL_RTSOCKET)
+ {
+ if (pTrcLogTcp->fIsServer)
+ RTTcpServerDisconnectClient2(pTrcLogTcp->hSock);
+ else
+ RTTcpClientClose(pTrcLogTcp->hSock);
+ }
+ RTMemFree(pTrcLogTcp);
+}
+
+
+static DECLCALLBACK(int) rtTraceLogToolTcpInput(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbRead,
+ RTMSINTERVAL cMsTimeout)
+{
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
+ if ( pTrcLogTcp->fIsServer
+ && pTrcLogTcp->hSock == NIL_RTSOCKET)
+ {
+ int rc = RTTcpServerListen2(pTrcLogTcp->pTcpSrv, &pTrcLogTcp->hSock);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ int rc = RTTcpSelectOne(pTrcLogTcp->hSock, cMsTimeout);
+ if (RT_SUCCESS(rc))
+ rc = RTTcpReadNB(pTrcLogTcp->hSock, pvBuf, cbBuf, pcbRead);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtTraceLogToolTcpClose(void *pvUser)
+{
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
+ rtTraceLogTcpDestroy(pTrcLogTcp);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to create a new trace log reader using the given input.
+ *
+ * @returns IPRT status code.
+ * @param phTraceLogRdr Where to store the handle to the trace log reader instance on success.
+ * @param pszInput The input path.
+ * @param pszSave The optional path to save
+ */
+static int rtTraceLogToolReaderCreate(PRTTRACELOGRDR phTraceLogRdr, const char *pszInput, const char *pszSave)
+{
+ RT_NOREF(pszSave);
+
+ /* Try treating the input as a file first. */
+ int rc = RTTraceLogRdrCreateFromFile(phTraceLogRdr, pszInput);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Check whether the input looks like a port number or an address:port pair.
+ * The former will create a server listening on the port while the latter tries
+ * to connect to the given address:port combination.
+ */
+ uint32_t uPort = 0;
+ bool fIsServer = false;
+ PRTTCPSERVER pTcpSrv = NULL;
+ RTSOCKET hSock = NIL_RTSOCKET;
+ rc = RTStrToUInt32Full(pszInput, 10, &uPort);
+ if (rc == VINF_SUCCESS)
+ {
+ fIsServer = true;
+ rc = RTTcpServerCreateEx(NULL, uPort, &pTcpSrv);
+ }
+ else
+ {
+ /* Try treating the input as an address:port pair. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize structure and reader. */
+ PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)RTMemAllocZ(sizeof(*pTrcLogTcp));
+ if (pTrcLogTcp)
+ {
+ pTrcLogTcp->fIsServer = fIsServer;
+ pTrcLogTcp->hSock = hSock;
+ pTrcLogTcp->pTcpSrv = pTcpSrv;
+ rc = RTTraceLogRdrCreate(phTraceLogRdr, rtTraceLogToolTcpInput, rtTraceLogToolTcpClose, pTrcLogTcp);
+ if (RT_FAILURE(rc))
+ rtTraceLogTcpDestroy(pTrcLogTcp);
+ }
+ else
+ {
+ if (fIsServer)
+ RTTcpServerDestroy(pTcpSrv);
+ else
+ RTSocketClose(hSock);
+ }
+ }
+ }
+ return rc;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Parse arguments.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--input", 'i', RTGETOPT_REQ_STRING },
+ { "--save", 's', RTGETOPT_REQ_STRING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszInput = NULL;
+ const char *pszSave = NULL;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'h':
+ RTPrintf("Usage: %s [options]\n"
+ "\n"
+ "Options:\n"
+ " -i,--input=<file|port|address:port>\n"
+ " Input path, can be a file a port to start listening on for incoming connections or an address:port to connect to\n"
+ " -s,--save=file\n"
+ " Save the input to a file for later use\n"
+ " -h, -?, --help\n"
+ " Display this help text and exit successfully.\n"
+ " -V, --version\n"
+ " Display the revision and exit successfully.\n"
+ , RTPathFilename(argv[0]));
+ return RTEXITCODE_SUCCESS;
+ case 'V':
+ RTPrintf("$Revision: 135976 $\n");
+ return RTEXITCODE_SUCCESS;
+
+ case 'i':
+ pszInput = ValueUnion.psz;
+ break;
+ case 's':
+ pszSave = ValueUnion.psz;
+ break;
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (!pszInput)
+ {
+ RTPrintf("An input path must be given\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Create trace log reader instance.
+ */
+ RTTRACELOGRDR hTraceLogRdr = NIL_RTTRACELOGRDR;
+ rc = rtTraceLogToolReaderCreate(&hTraceLogRdr, pszInput, pszSave);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTTRACELOGRDRPOLLEVT enmEvt = RTTRACELOGRDRPOLLEVT_INVALID;
+ rc = RTTraceLogRdrEvtPoll(hTraceLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmEvt)
+ {
+ case RTTRACELOGRDRPOLLEVT_HDR_RECVD:
+ RTMsgInfo("A valid header was received\n");
+ break;
+ case RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD:
+ {
+ RTTRACELOGRDREVT hTraceLogEvt;
+ rc = RTTraceLogRdrQueryLastEvt(hTraceLogRdr, &hTraceLogEvt);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTTRACELOGEVTDESC pEvtDesc = RTTraceLogRdrEvtGetDesc(hTraceLogEvt);
+ RTMsgInfo("%llu %llu %s\n",
+ RTTraceLogRdrEvtGetSeqNo(hTraceLogEvt),
+ RTTraceLogRdrEvtGetTs(hTraceLogEvt),
+ pEvtDesc->pszId);
+ for (unsigned i = 0; i < pEvtDesc->cEvtItems; i++)
+ {
+ RTTRACELOGEVTVAL Val;
+ unsigned cVals = 0;
+ rc = RTTraceLogRdrEvtFillVals(hTraceLogEvt, i, &Val, 1, &cVals);
+ if (RT_SUCCESS(rc))
+ {
+ switch (Val.pItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ RTMsgInfo(" %s: %s\n", Val.pItemDesc->pszName, Val.u.f ? "true" : "false");
+ break;
+ case RTTRACELOGTYPE_UINT8:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u8);
+ break;
+ case RTTRACELOGTYPE_INT8:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i8);
+ break;
+ case RTTRACELOGTYPE_UINT16:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u16);
+ break;
+ case RTTRACELOGTYPE_INT16:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i16);
+ break;
+ case RTTRACELOGTYPE_UINT32:
+ RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u32);
+ break;
+ case RTTRACELOGTYPE_INT32:
+ RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i32);
+ break;
+ case RTTRACELOGTYPE_UINT64:
+ RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.u64);
+ break;
+ case RTTRACELOGTYPE_INT64:
+ RTMsgInfo(" %s: %lld\n", Val.pItemDesc->pszName, Val.u.i64);
+ break;
+ case RTTRACELOGTYPE_FLOAT32:
+ case RTTRACELOGTYPE_FLOAT64:
+ case RTTRACELOGTYPE_RAWDATA:
+ RTMsgInfo(" %s: Float32, Float64 and raw data not supported yet\n", Val.pItemDesc->pszName);
+ break;
+ case RTTRACELOGTYPE_POINTER:
+ RTMsgInfo(" %s: %#llx\n", Val.pItemDesc->pszName, Val.u.uPtr);
+ break;
+ case RTTRACELOGTYPE_SIZE:
+ RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.sz);
+ break;
+ default:
+ RTMsgError(" %s: Invalid type given %d\n", Val.pItemDesc->pszName, Val.pItemDesc->enmType);
+ }
+ }
+ else
+ RTMsgInfo(" Failed to retrieve event data with %Rrc\n", rc);
+ }
+ }
+ break;
+ }
+ default:
+ RTMsgInfo("Invalid event received: %d\n", enmEvt);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Polling for an event failed with %Rrc\n", rc);
+ } while (RT_SUCCESS(rc));
+
+ RTTraceLogRdrDestroy(hTraceLogRdr);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create trace log reader with %Rrc\n", rc);
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/tools/RTUnzip.cpp b/src/VBox/Runtime/tools/RTUnzip.cpp
new file mode 100644
index 00000000..851b2428
--- /dev/null
+++ b/src/VBox/Runtime/tools/RTUnzip.cpp
@@ -0,0 +1,44 @@
+/* $Id: RTUnzip.cpp $ */
+/** @file
+ * IPRT - TAR Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ return RTZipUnzipCmd(argc, argv);
+}
+