Index: /trunk/include/iprt/crypto/pem.h
===================================================================
--- /trunk/include/iprt/crypto/pem.h	(revision 84162)
+++ /trunk/include/iprt/crypto/pem.h	(revision 84163)
@@ -31,4 +31,6 @@
 
 #include <iprt/types.h>
+#include <iprt/asn1.h>   /* PRTASN1CORE */
+#include <iprt/string.h> /* PFNRTSTROUTPUT */
 
 
@@ -205,4 +207,77 @@
                                                       PCRTCRPEMMARKER paMarkers, size_t cMarkers);
 
+
+/**
+ * PEM formatter for a binary data blob.
+ *
+ * @returns Number of output bytes (sum of @a pfnOutput return values).
+ * @param   pfnOutput       The output callback function.
+ * @param   pvUser          The user argument to the output callback.
+ * @param   pvContent       The binary blob to output.
+ * @param   cbContent       Size of the binary blob.
+ * @param   pszMarker       The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or
+ *                          similar.
+ * @sa      RTCrPemWriteAsn1, RTCrPemWriteAsn1ToVfsFile,
+ *          RTCrPemWriteAsn1ToVfsFile
+ */
+RTDECL(size_t) RTCrPemWriteBlob(PFNRTSTROUTPUT pfnOutput, void *pvUser,
+                                const void *pvContent, size_t cbContent, const char *pszMarker);
+
+/**
+ * PEM formatter for a generic ASN.1 structure.
+ *
+ * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on
+ * @a pRoot.  Uses DER encoding.
+ *
+ * @returns Number of outputted chars (sum of @a pfnOutput return values),
+ *          negative values are error status codes from the ASN.1 encoding.
+ * @param   pfnOutput       The output callback function.
+ * @param   pvUser          The user argument to the output callback.
+ * @param   fFlags          Reserved, MBZ.
+ * @param   pRoot           The root of the ASN.1 to encode and format as PEM.
+ * @param   pszMarker       The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or
+ *                          similar.
+ * @sa      RTCrPemWriteAsn1ToVfsFile, RTCrPemWriteAsn1ToVfsFile,
+ *          RTCrPemWriteBlob
+ */
+RTDECL(ssize_t) RTCrPemWriteAsn1(PFNRTSTROUTPUT pfnOutput, void *pvUser, PRTASN1CORE pRoot,
+                                 uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo);
+
+/**
+ * PEM formatter for a generic ASN.1 structure and output it to @a hVfsIos.
+ *
+ * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on
+ * @a pRoot.  Uses DER encoding.
+ *
+ * @returns Number of chars written, negative values are error status codes from
+ *          the ASN.1 encoding or from RTVfsIoStrmWrite().
+ * @param   hVfsIos         Handle to the I/O stream to write it to.
+ * @param   fFlags          Reserved, MBZ.
+ * @param   pRoot           The root of the ASN.1 to encode and format as PEM.
+ * @param   pszMarker       The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or
+ *                          similar.
+ * @sa      RTCrPemWriteAsn1ToVfsFile, RTCrPemWriteAsn1, RTCrPemWriteBlob
+ */
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTASN1CORE pRoot,
+                                            uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo);
+
+/**
+ * PEM formatter for a generic ASN.1 structure and output it to @a hVfsFile.
+ *
+ * This will call both RTAsn1EncodePrepare() and RTAsn1EncodeWrite() on
+ * @a pRoot.  Uses DER encoding.
+ *
+ * @returns Number of chars written, negative values are error status codes from
+ *          the ASN.1 encoding or from RTVfsIoStrmWrite().
+ * @param   hVfsFile        Handle to the file to write it to.
+ * @param   fFlags          Reserved, MBZ.
+ * @param   pRoot           The root of the ASN.1 to encode and format as PEM.
+ * @param   pszMarker       The PEM marker, .e.g "PRIVATE KEY", "CERTIFICATE" or
+ *                          similar.
+ * @sa      RTCrPemWriteAsn1ToVfsIoStrm, RTCrPemWriteAsn1, RTCrPemWriteBlob
+ */
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsFile(RTVFSFILE hVfsFile, PRTASN1CORE pRoot,
+                                          uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo);
+
 /** @} */
 
Index: /trunk/include/iprt/crypto/x509.h
===================================================================
--- /trunk/include/iprt/crypto/x509.h	(revision 84162)
+++ /trunk/include/iprt/crypto/x509.h	(revision 84163)
@@ -1034,4 +1034,17 @@
 
 
+/** Wrapper around RTCrPemWriteAsn1ToVfsIoStrm().  */
+DECLINLINE(int) RTCrX509Certificate_WriteToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTCRX509CERTIFICATE pCertificate, PRTERRINFO pErrInfo)
+{
+    return RTCrPemWriteAsn1ToVfsIoStrm(hVfsIos, &pCertificate->SeqCore.Asn1Core, 0 /*fFlags*/,
+                                       g_aRTCrX509CertificateMarkers[0].paWords[0].pszWord, pErrInfo);
+}
+
+/** Wrapper around RTCrPemWriteAsn1ToVfsFile().  */
+DECLINLINE(int) RTCrX509Certificate_WriteToVfsFile(RTVFSFILE hVfsFile, PRTCRX509CERTIFICATE pCertificate, PRTERRINFO pErrInfo)
+{
+    return RTCrPemWriteAsn1ToVfsFile(hVfsFile, &pCertificate->SeqCore.Asn1Core, 0 /*fFlags*/,
+                                     g_aRTCrX509CertificateMarkers[0].paWords[0].pszWord, pErrInfo);
+}
 
 /** @name X.509 Certificate Extensions
Index: /trunk/include/iprt/mangling.h
===================================================================
--- /trunk/include/iprt/mangling.h	(revision 84162)
+++ /trunk/include/iprt/mangling.h	(revision 84163)
@@ -2764,4 +2764,5 @@
 # define RTVfsIoStrmSgWrite                             RT_MANGLER(RTVfsIoStrmSgWrite)
 # define RTVfsIoStrmSkip                                RT_MANGLER(RTVfsIoStrmSkip)
+# define RTVfsIoStrmStrOutputCallback                   RT_MANGLER(RTVfsIoStrmStrOutputCallback)
 # define RTVfsIoStrmTell                                RT_MANGLER(RTVfsIoStrmTell)
 # define RTVfsIoStrmToFile                              RT_MANGLER(RTVfsIoStrmToFile)
@@ -3341,4 +3342,8 @@
 # define RTCrPemParseContent                            RT_MANGLER(RTCrPemParseContent)
 # define RTCrPemReadFile                                RT_MANGLER(RTCrPemReadFile)
+# define RTCrPemWriteBlob                               RT_MANGLER(RTCrPemWriteBlob)
+# define RTCrPemWriteAsn1                               RT_MANGLER(RTCrPemWriteAsn1)
+# define RTCrPemWriteAsn1ToVfsIoStrm                    RT_MANGLER(RTCrPemWriteAsn1ToVfsIoStrm)
+# define RTCrPemWriteAsn1ToVfsFile                      RT_MANGLER(RTCrPemWriteAsn1ToVfsFile)
 # define RTCrPkcs5Pbkdf2Hmac                            RT_MANGLER(RTCrPkcs5Pbkdf2Hmac)
 # define RTCrPkcs7Attribute_DecodeAsn1                  RT_MANGLER(RTCrPkcs7Attribute_DecodeAsn1)
Index: /trunk/include/iprt/vfs.h
===================================================================
--- /trunk/include/iprt/vfs.h	(revision 84162)
+++ /trunk/include/iprt/vfs.h	(revision 84163)
@@ -1229,4 +1229,44 @@
 RTDECL(ssize_t)     RTVfsIoStrmPrintfV(RTVFSIOSTREAM hVfsIos, const char *pszFormat, va_list va);
 
+/**
+ * VFS I/O stream output buffer structure to use with
+ * RTVfsIoStrmStrOutputCallback().
+ */
+typedef struct VFSIOSTRMOUTBUF
+{
+    /** The I/O stream handle. */
+    RTVFSIOSTREAM   hVfsIos;
+    /** Size of this structure (for sanity). */
+    size_t          cbSelf;
+    /** Status code of the operation. */
+    int             rc;
+    /** Current offset into szBuf (number of output bytes pending). */
+    size_t          offBuf;
+    /** Modest output buffer. */
+    char            szBuf[256];
+} VFSIOSTRMOUTBUF;
+/** Pointer to an VFS I/O stream output buffer for use with
+ *  RTVfsIoStrmStrOutputCallback() */
+typedef VFSIOSTRMOUTBUF *PVFSIOSTRMOUTBUF;
+
+/** Initializer for a VFS I/O stream output buffer. */
+#define VFSIOSTRMOUTBUF_INIT(a_pOutBuf, a_hVfsIos) \
+    do { \
+        (a_pOutBuf)->hVfsIos  = a_hVfsIos; \
+        (a_pOutBuf)->cbSelf   = sizeof(*(a_pOutBuf)); \
+        (a_pOutBuf)->rc       = VINF_SUCCESS; \
+        (a_pOutBuf)->offBuf   = 0; \
+        (a_pOutBuf)->szBuf[0] = '\0'; \
+    } while (0)
+
+/**
+ * @callback_method_impl{FNRTSTROUTPUT,
+ * For use with VFSIOSTRMOUTBUF.
+ *
+ * Users must use VFSIOSTRMOUTBUF_INIT to initialize a VFSIOSTRMOUTBUF and pass
+ * that as the outputter argument to the function this callback is handed to.}
+ */
+RTDECL(size_t) RTVfsIoStrmStrOutputCallback(void *pvArg, const char *pachChars, size_t cbChars);
+
 /** @} */
 
Index: /trunk/src/VBox/Runtime/Makefile.kmk
===================================================================
--- /trunk/src/VBox/Runtime/Makefile.kmk	(revision 84162)
+++ /trunk/src/VBox/Runtime/Makefile.kmk	(revision 84163)
@@ -393,5 +393,6 @@
 	common/crypto/rsa-init.cpp \
 	common/crypto/rsa-sanity.cpp \
-	common/crypto/pemfile.cpp \
+	common/crypto/pemfile-read.cpp \
+	common/crypto/pemfile-write.cpp \
 	common/crypto/pkcs7-asn1-decoder.cpp \
 	common/crypto/pkcs7-core.cpp \
@@ -1682,5 +1683,6 @@
 	common/checksum/sha512str.cpp \
 	common/crypto/digest-core.cpp \
-	common/crypto/pemfile.cpp \
+	common/crypto/pemfile-read.cpp \
+	common/crypto/pemfile-write.cpp \
 	common/crypto/pkcs7-asn1-decoder.cpp \
 	common/crypto/pkcs7-core.cpp \
Index: /trunk/src/VBox/Runtime/common/crypto/pemfile-read.cpp
===================================================================
--- /trunk/src/VBox/Runtime/common/crypto/pemfile-read.cpp	(revision 84163)
+++ /trunk/src/VBox/Runtime/common/crypto/pemfile-read.cpp	(revision 84163)
@@ -0,0 +1,653 @@
+/* $Id$ */
+/** @file
+ * IPRT - Crypto - PEM file reader.
+ *
+ * See RFC-1341 for the original ideas for the format, but keep in mind
+ * that the format was hijacked and put to different uses.  We're aiming at
+ * dealing with the different uses rather than anything email related here.
+ */
+
+/*
+ * 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 "internal/iprt.h"
+#include <iprt/crypto/pem.h>
+
+#include <iprt/asm.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+
+
+/**
+ * Looks for a PEM-like marker.
+ *
+ * @returns true if found, false if not.
+ * @param   pbContent           Start of the content to search thru.
+ * @param   cbContent           The size of the content to search.
+ * @param   offStart            The offset into pbContent to start searching.
+ * @param   pszLeadWord         The lead word (BEGIN/END).
+ * @param   cchLeadWord         The length of the lead word.
+ * @param   paMarkers           Pointer to an array of markers.
+ * @param   cMarkers            Number of markers in the array.
+ * @param   ppMatch             Where to return the pointer to the matching
+ *                              marker. Optional.
+ * @param   poffBegin           Where to return the start offset of the marker.
+ *                              Optional.
+ * @param   poffEnd             Where to return the end offset of the marker
+ *                              (trailing whitespace and newlines will be
+ *                              skipped).  Optional.
+ */
+static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart,
+                              const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+                              PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd)
+{
+    /* Remember the start of the content for the purpose of calculating offsets. */
+    uint8_t const * const pbStart = pbContent;
+
+    /* Skip adhead by offStart */
+    if (offStart >= cbContent)
+        return false;
+    pbContent += offStart;
+    cbContent -= offStart;
+
+    /*
+     * Search the content.
+     */
+    while (cbContent > 6)
+    {
+        /*
+         * Look for dashes.
+         */
+        uint8_t const *pbStartSearch = pbContent;
+        pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent);
+        if (!pbContent)
+            break;
+
+        cbContent -= pbContent - pbStartSearch;
+        if (cbContent < 6)
+            break;
+
+        /*
+         * There must be at least three to interest us.
+         */
+        if (   pbContent[1] == '-'
+            && pbContent[2] == '-')
+        {
+            unsigned cDashes = 3;
+            while (cDashes < cbContent && pbContent[cDashes] == '-')
+                cDashes++;
+
+            if (poffBegin)
+                *poffBegin = pbContent - pbStart;
+            cbContent -= cDashes;
+            pbContent += cDashes;
+
+            /*
+             * Match lead word.
+             */
+            if (   cbContent > cchLeadWord
+                && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0
+                && RT_C_IS_BLANK(pbContent[cchLeadWord]) )
+            {
+                pbContent += cchLeadWord;
+                cbContent -= cchLeadWord;
+                while (cbContent > 0 && RT_C_IS_BLANK(*pbContent))
+                {
+                    pbContent++;
+                    cbContent--;
+                }
+
+                /*
+                 * Match one of the specified markers.
+                 */
+                uint8_t const  *pbSavedContent = pbContent;
+                size_t  const   cbSavedContent = cbContent;
+                for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++)
+                {
+                    pbContent = pbSavedContent;
+                    cbContent = cbSavedContent;
+
+                    uint32_t            cWords = paMarkers[iMarker].cWords;
+                    PCRTCRPEMMARKERWORD pWord  = paMarkers[iMarker].paWords;
+                    while (cWords > 0)
+                    {
+                        uint32_t const cchWord = pWord->cchWord;
+                        if (cbContent <= cchWord)
+                            break;
+                        if (memcmp(pbContent, pWord->pszWord, cchWord))
+                            break;
+                        pbContent += cchWord;
+                        cbContent -= cchWord;
+
+                        if (!cbContent)
+                            break;
+                        if (RT_C_IS_BLANK(*pbContent))
+                            do
+                            {
+                                pbContent++;
+                                cbContent--;
+                            } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent));
+                        else if (cWords > 1 || pbContent[0] != '-')
+                            break;
+
+                        cWords--;
+                        if (cWords == 0)
+                        {
+                            /*
+                             * If there are three or more dashes following now, we've got a hit.
+                             */
+                            if (   cbContent > 3
+                                && pbContent[0] == '-'
+                                && pbContent[1] == '-'
+                                && pbContent[2] == '-')
+                            {
+                                cDashes = 3;
+                                while (cDashes < cbContent && pbContent[cDashes] == '-')
+                                    cDashes++;
+                                cbContent -= cDashes;
+                                pbContent += cDashes;
+
+                                /*
+                                 * Skip spaces and newline.
+                                 */
+                                while (cbContent > 0 && RT_C_IS_SPACE(*pbContent))
+                                    pbContent++, cbContent--;
+                                if (poffEnd)
+                                    *poffEnd = pbContent - pbStart;
+                                if (ppMatch)
+                                    *ppMatch = &paMarkers[iMarker];
+                                return true;
+                            }
+                            break;
+                        }
+                        pWord++;
+                    } /* for each word in marker. */
+                } /* for each marker. */
+            }
+        }
+        else
+        {
+            pbContent++;
+            cbContent--;
+        }
+    }
+
+    return false;
+}
+
+
+static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart,
+                                     PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+                                     PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume)
+{
+    /** @todo Detect BEGIN / END mismatch. */
+    PCRTCRPEMMARKER pMatch;
+    if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers,
+                          &pMatch, NULL /*poffStart*/, poffBegin))
+    {
+        if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1,
+                              NULL /*ppMatch*/, poffEnd, poffResume))
+        {
+            *ppMatch = pMatch;
+            return true;
+        }
+    }
+    *ppMatch = NULL;
+    return false;
+}
+
+
+/**
+ * Parses any fields the message may contain.
+ *
+ * @retval  VINF_SUCCESS
+ * @retval  VERR_NO_MEMORY
+ * @retval  VERR_CR_MALFORMED_PEM_HEADER
+ *
+ * @param   pSection        The current section, where we will attach a list of
+ *                          fields to the pFieldHead member.
+ * @param   pbContent       The content of the PEM message being parsed.
+ * @param   cbContent       The length of the PEM message.
+ * @param   pcbFields       Where to return the length of the header fields we found.
+ */
+static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields)
+{
+    uint8_t const * const pbContentStart = pbContent;
+
+    /*
+     * Work the encapulated header protion field by field.
+     *
+     * This is optional, so currently we don't throw errors here but leave that
+     * to when we work the text portion with the base64 decoder.  Also, as a reader
+     * we don't go all pedanic on confirming to specification (RFC-1421), especially
+     * given that it's used for crypto certificates, keys and the like not email. :-)
+     */
+    PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead;
+    while (cbContent > 0)
+    {
+        /* Just look for a colon first. */
+        const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent);
+        if (!pbColon)
+            break;
+        size_t offColon = pbColon - pbContent;
+
+        /* Check that the colon is within the first line. */
+        if (!memchr(pbContent, '\n', cbContent - offColon))
+            return VERR_CR_MALFORMED_PEM_HEADER;
+
+        /* Skip leading spaces (there shouldn't be any, but just in case). */
+        while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0)
+        {
+            offColon--;
+            cbContent--;
+            pbContent++;
+        }
+
+        /* There shouldn't be any spaces before the colon, but just in case */
+        size_t cchName = offColon;
+        while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1]))
+            cchName--;
+
+        /* Skip leading value spaces (there typically is at least one). */
+        size_t offValue = offColon + 1;
+        while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue]))
+            offValue++;
+
+        /* Find the newline the field value ends with and where the next iteration should start later on. */
+        size_t         cbLeft;
+        uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue);
+        while (   pbNext
+               && (cbLeft = pbNext - pbContent) < cbContent
+               && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */)
+            pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1);
+
+        size_t cchValue;
+        if (pbNext)
+        {
+            cchValue = pbNext - &pbContent[offValue];
+            if (cchValue > 0 && pbNext[-1] == '\r')
+                cchValue--;
+            pbNext++;
+        }
+        else
+        {
+            cchValue = cbContent - offValue;
+            pbNext = &pbContent[cbContent];
+        }
+
+        /* Strip trailing spaces. */
+        while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1]))
+            cchValue--;
+
+        /*
+         * Allocate a field instance.
+         *
+         * Note! We don't consider field data sensitive at the moment.  This
+         *       mainly because the fields are chiefly used to indicate the
+         *       encryption parameters to the body.
+         */
+        PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1);
+        if (!pNewField)
+            return VERR_NO_MEMORY;
+        pNewField->cchName         = cchName;
+        pNewField->cchValue        = cchValue;
+        memcpy(pNewField->szName, pbContent, cchName);
+        pNewField->szName[cchName] = '\0';
+        char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue);
+        pNewField->pszValue        = pszDst;
+        pszDst[cchValue]           = '\0';
+        pNewField->pNext           = NULL;
+
+        *ppNext = pNewField;
+        ppNext = &pNewField->pNext;
+
+        /*
+         * Advance past the field.
+         */
+        cbContent -= pbNext - pbContent;
+        pbContent  = pbNext;
+    }
+
+    /*
+     * Skip blank line(s) before the body.
+     */
+    while (cbContent >= 1)
+    {
+        size_t cbSkip;
+        if (pbContent[0] == '\n')
+            cbSkip = 1;
+        else if (   pbContent[0] == '\r'
+                 && cbContent >= 2
+                 && pbContent[1] == '\n')
+            cbSkip = 2;
+        else
+            break;
+        pbContent += cbSkip;
+        cbContent -= cbSkip;
+    }
+
+    *pcbFields = pbContent - pbContentStart;
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Does the decoding of a PEM-like data blob after it has been located.
+ *
+ * @returns IPRT status ocde
+ * @param   pbContent           The start of the PEM-like content (text).
+ * @param   cbContent           The max size of the PEM-like content.
+ * @param   fSensitive          Set if the safer allocator should be used.
+ * @param   ppvDecoded          Where to return a heap block containing the
+ *                              decoded content.
+ * @param   pcbDecoded          Where to return the size of the decoded content.
+ */
+static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive,
+                               void **ppvDecoded, size_t *pcbDecoded)
+{
+    ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL);
+    if (cbDecoded < 0)
+        return VERR_INVALID_BASE64_ENCODING;
+
+    *pcbDecoded = cbDecoded;
+    void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded);
+    if (!pvDecoded)
+        return VERR_NO_MEMORY;
+
+    size_t cbActual;
+    int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL);
+    if (RT_SUCCESS(rc))
+    {
+        if (cbActual == (size_t)cbDecoded)
+        {
+            *ppvDecoded = pvDecoded;
+            return VINF_SUCCESS;
+        }
+
+        rc = VERR_INTERNAL_ERROR_3;
+    }
+    if (!fSensitive)
+        RTMemFree(pvDecoded);
+    else
+        RTMemSaferFree(pvDecoded, cbDecoded);
+    return rc;
+}
+
+
+/**
+ * Checks if the content of a file looks to be binary or not.
+ *
+ * @returns true if likely to be binary, false if not binary.
+ * @param   pbFile              The file bytes to scan.
+ * @param   cbFile              The number of bytes.
+ * @param   fFlags              RTCRPEMREADFILE_F_XXX
+ */
+static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags)
+{
+    if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM)
+        return false;
+
+    /*
+     * Well formed PEM files should probably only contain 7-bit ASCII and
+     * restrict thenselfs to the following control characters:
+     *      tab, newline, return, form feed
+     *
+     * However, if we want to read PEM files which contains human readable
+     * certificate details before or after each base-64 section, we can't stick
+     * to 7-bit ASCII.  We could say it must be UTF-8, but that's probably to
+     * limited as well.  So, we'll settle for detecting binary files by control
+     * characters alone (safe enough for DER encoded stuff, I think).
+     */
+    while (cbFile-- > 0)
+    {
+        uint8_t const b = *pbFile++;
+        if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
+        {
+            /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
+            if (   (b == 4 || b == 26)
+                && (   cbFile == 0
+                    || (   cbFile == 1
+                        && *pbFile == '\0')))
+                return false;
+
+            if (b == 0 && cbFile == 0)
+                return false;
+
+            return true;
+        }
+    }
+    return false;
+}
+
+
+RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
+{
+    while (pSectionHead != NULL)
+    {
+        PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
+        pSectionHead = pSectionHead->pNext;
+        ASMCompilerBarrier(); /* paranoia */
+
+        if (pFree->pbData)
+        {
+            if (!pFree->fSensitive)
+                RTMemFree(pFree->pbData);
+            else
+                RTMemSaferFree(pFree->pbData, pFree->cbData);
+            pFree->pbData = NULL;
+            pFree->cbData = 0;
+        }
+
+        PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead;
+        if (pField)
+        {
+            pFree->pFieldHead = NULL;
+            do
+            {
+                PRTCRPEMFIELD pFreeField = pField;
+                pField = (PRTCRPEMFIELD)pField->pNext;
+                ASMCompilerBarrier(); /* paranoia */
+
+                pFreeField->pszValue = NULL;
+                RTMemFree(pFreeField);
+            } while (pField);
+        }
+
+        RTMemFree(pFree);
+    }
+    return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
+                                PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+                                PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
+{
+    RT_NOREF_PV(pErrInfo);
+
+    /*
+     * Input validation.
+     */
+    AssertPtr(ppSectionHead);
+    *ppSectionHead = NULL;
+    AssertReturn(cbContent, VINF_EOF);
+    AssertPtr(pvContent);
+    AssertPtr(paMarkers);
+    AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+    /*
+     * Pre-allocate a section.
+     */
+    int rc = VINF_SUCCESS;
+    PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
+    if (pSection)
+    {
+        bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE);
+
+        /*
+         * Try locate the first section.
+         */
+        uint8_t const  *pbContent = (uint8_t const *)pvContent;
+        size_t          offBegin, offEnd, offResume;
+        PCRTCRPEMMARKER pMatch;
+        if (   !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags)
+            && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
+                                        &pMatch, &offBegin, &offEnd, &offResume) )
+        {
+            PCRTCRPEMSECTION *ppNext = ppSectionHead;
+            for (;;)
+            {
+                //pSection->pNext       = NULL;
+                pSection->pMarker       = pMatch;
+                //pSection->pbData      = NULL;
+                //pSection->cbData      = 0;
+                //pSection->pFieldHead  = NULL;
+                pSection->fSensitive    = fSensitive;
+
+                *ppNext = pSection;
+                ppNext = &pSection->pNext;
+
+                /*
+                 * Decode the section.
+                 */
+                size_t cbFields = 0;
+                int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields);
+                offBegin += cbFields;
+                if (RT_SUCCESS(rc2))
+                    rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive,
+                                              (void **)&pSection->pbData, &pSection->cbData);
+                if (RT_FAILURE(rc2))
+                {
+                    pSection->pbData = NULL;
+                    pSection->cbData = 0;
+                    if (   rc2 == VERR_INVALID_BASE64_ENCODING
+                        && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
+                        rc = -rc2;
+                    else
+                    {
+                        rc = rc2;
+                        break;
+                    }
+                }
+
+                /*
+                 * More sections?
+                 */
+                if (   offResume + 12 >= cbContent
+                    || offResume      >= cbContent
+                    || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
+                                                 &pMatch, &offBegin, &offEnd, &offResume) )
+                    break; /* No. */
+
+                /* Ok, allocate a new record for it. */
+                pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
+                if (RT_UNLIKELY(!pSection))
+                {
+                    rc = VERR_NO_MEMORY;
+                    break;
+                }
+            }
+            if (RT_SUCCESS(rc))
+                return rc;
+
+            RTCrPemFreeSections(*ppSectionHead);
+        }
+        else
+        {
+            if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM))
+            {
+                /*
+                 * No PEM section found.  Return the whole file as one binary section.
+                 */
+                //pSection->pNext       = NULL;
+                //pSection->pMarker     = NULL;
+                //pSection->pFieldHead  = NULL;
+                pSection->cbData        = cbContent;
+                pSection->fSensitive    = fSensitive;
+                if (!fSensitive)
+                    pSection->pbData    = (uint8_t *)RTMemDup(pbContent, cbContent);
+                else
+                {
+                    pSection->pbData    = (uint8_t *)RTMemSaferAllocZ(cbContent);
+                    if (pSection->pbData)
+                        memcpy(pSection->pbData, pbContent, cbContent);
+                }
+                if (pSection->pbData)
+                {
+                    *ppSectionHead = pSection;
+                    return VINF_SUCCESS;
+                }
+
+                rc = VERR_NO_MEMORY;
+            }
+            else
+                rc = VWRN_NOT_FOUND;
+            RTMemFree(pSection);
+        }
+    }
+    else
+        rc = VERR_NO_MEMORY;
+    *ppSectionHead = NULL;
+    return rc;
+}
+
+
+RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+                            PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
+{
+    *ppSectionHead = NULL;
+    AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+    size_t      cbContent;
+    void        *pvContent;
+    int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
+        if (fFlags & RTCRPEMREADFILE_F_SENSITIVE)
+            RTMemWipeThoroughly(pvContent, cbContent, 3);
+        RTFileReadAllFree(pvContent, cbContent);
+    }
+    else
+        rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
+    return rc;
+}
+
+
+RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent,
+                                                      PCRTCRPEMMARKER paMarkers, size_t cMarkers)
+{
+    size_t offBegin;
+    if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL))
+        return (const char *)pvContent + offBegin;
+    return NULL;
+}
+
Index: /trunk/src/VBox/Runtime/common/crypto/pemfile-write.cpp
===================================================================
--- /trunk/src/VBox/Runtime/common/crypto/pemfile-write.cpp	(revision 84163)
+++ /trunk/src/VBox/Runtime/common/crypto/pemfile-write.cpp	(revision 84163)
@@ -0,0 +1,234 @@
+/* $Id$ */
+/** @file
+ * IPRT - Crypto - PEM file writer.
+ */
+
+/*
+ * 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 "internal/iprt.h"
+#include <iprt/crypto/pem.h>
+
+#include <iprt/asn1.h>
+#include <iprt/base64.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+*   Structures and Typedefs                                                                                                      *
+*********************************************************************************************************************************/
+/**
+ * Used by rtCrPemWriteAsn1Callback to buffer data before outputting it as
+ * BASE64.
+ *
+ * An encoded line is 64 characters long plus a newline, covering 48 bytes
+ * of binary data.  We want about 4KB of output:
+ *       4096 / 65   = 63.015384615384615384615384615385
+ *       64 * 65 + 1 = 4161 (0x1041)
+ */
+typedef struct PEMOUTPUTASN1
+{
+    size_t          cbPending;
+    PFNRTSTROUTPUT  pfnOutput;
+    void           *pvUser;
+    size_t          cchRet;
+    uint8_t         abBlock[0x0c00];
+    char            szBlock[0x1060];
+} PEMOUTPUTASN1;
+typedef PEMOUTPUTASN1 *PPEMOUTPUTASN1;
+
+
+
+RTDECL(size_t) RTCrPemWriteBlob(PFNRTSTROUTPUT pfnOutput, void *pvUser,
+                                const void *pvContent, size_t cbContent, const char *pszMarker)
+{
+    /*
+     * -----BEGIN XXXXX-----
+     */
+    size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN "));
+    size_t const cchMarker = strlen(pszMarker);
+    cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+    /*
+     * base64 - in reasonably sized stack blocks.
+     * An encoded line is 64 characters long plus a newline, covering 48 bytes
+     * of binary data.  We want about 4KB of output:
+     *       4096 / 65   = 63.015384615384615384615384615385
+     *       64 * 65 + 1 = 4161 (0x1041)
+     */
+    const size_t cbMaxBlock = 64 * 48;
+    while (cbContent > 0)
+    {
+        char   szBlock[0x1060];
+        size_t cbBlock = RT_MIN(cbContent, cbMaxBlock);
+        size_t cchBlock = 0;
+        int rc = RTBase64Encode(pvContent, cbBlock, szBlock, sizeof(szBlock), &cchBlock);
+        AssertRC(rc);
+        szBlock[cchBlock++] = '\n';
+        szBlock[cchBlock]   = '\0';
+
+        cchRet += pfnOutput(pvUser, szBlock, cchBlock);
+
+        pvContent  = (uint8_t const *)pvContent + cbBlock;
+        cbContent -= cbBlock;
+    }
+
+    /*
+     * -----END XXXXX-----
+     */
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END "));
+    cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+    /* termination call */
+    cchRet += pfnOutput(pvUser, NULL, 0);
+
+    return cchRet;
+}
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER} */
+static DECLCALLBACK(int) rtCrPemWriteAsn1Callback(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+    PPEMOUTPUTASN1 pThis = (PPEMOUTPUTASN1)pvUser;
+    AssertCompile((sizeof(pThis->abBlock) % 48) == 0);
+
+    while (cbToWrite > 0)
+    {
+        size_t offDst = pThis->cbPending;
+        AssertStmt(offDst <= sizeof(pThis->abBlock), offDst = sizeof(pThis->abBlock));
+        size_t cbDst = sizeof(pThis->abBlock) - offDst;
+        if (cbToWrite < cbDst)
+        {
+            /* Buffer not full: Append and return. */
+            memcpy(&pThis->abBlock[offDst], pvBuf, cbToWrite);
+            pThis->cbPending = offDst + cbToWrite;
+            break;
+        }
+
+        /* Fill the buffer and flush it: */
+        memcpy(&pThis->abBlock[offDst], pvBuf, cbDst);
+        Assert(offDst + cbDst == sizeof(pThis->abBlock));
+
+        size_t cchBlock = 0;
+        int rc = RTBase64Encode(pThis->abBlock, sizeof(pThis->abBlock), pThis->szBlock, sizeof(pThis->szBlock), &cchBlock);
+        AssertRC(rc);
+        pThis->szBlock[cchBlock++] = '\n';
+        pThis->szBlock[cchBlock]   = '\0';
+
+        pThis->cchRet += pThis->pfnOutput(pThis->pvUser, pThis->szBlock, cchBlock);
+        pThis->cbPending = 0;
+
+        /* Advance. */
+        pvBuf      = (uint8_t const *)pvBuf + cbDst;
+        cbToWrite -= cbDst;
+    }
+
+    RT_NOREF(pErrInfo);
+    return VINF_SUCCESS;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1(PFNRTSTROUTPUT pfnOutput, void *pvUser, PRTASN1CORE pRoot,
+                                 uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+    AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+    /*
+     * Prepare the ASN.1 data for DER encoding.
+     */
+    int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, NULL /*pcbEncoded*/, pErrInfo);
+    AssertRCReturn(rc, rc);
+
+    /*
+     * -----BEGIN XXXXX-----
+     */
+    size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN "));
+    size_t const cchMarker = strlen(pszMarker);
+    cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+    /*
+     * BASE64
+     */
+    PEMOUTPUTASN1 This;
+    This.pfnOutput = pfnOutput;
+    This.pvUser    = pvUser;
+    This.cchRet    = 0;
+    This.cbPending = 0;
+    rc = RTAsn1EncodeWrite(pRoot, RTASN1ENCODE_F_DER, rtCrPemWriteAsn1Callback, &This, pErrInfo);
+    AssertRCReturn(rc, rc);
+    cchRet += This.cchRet;
+
+    Assert(This.cbPending <= sizeof(This.abBlock));
+    if (This.cbPending)
+    {
+        size_t cchBlock = 0;
+        rc = RTBase64Encode(This.abBlock, This.cbPending, This.szBlock, sizeof(This.szBlock), &cchBlock);
+        AssertRC(rc);
+        This.szBlock[cchBlock++] = '\n';
+        This.szBlock[cchBlock]   = '\0';
+
+        cchRet += pfnOutput(pvUser, This.szBlock, cchBlock);
+    }
+
+    /*
+     * -----END XXXXX-----
+     */
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END "));
+    cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+    cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+    /* termination call */
+    cchRet += pfnOutput(pvUser, NULL, 0);
+
+    return cchRet;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTASN1CORE pRoot,
+                                            uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+    VFSIOSTRMOUTBUF Buf;
+    VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos);
+    ssize_t cchRet = RTCrPemWriteAsn1(RTVfsIoStrmStrOutputCallback, &Buf, pRoot, fFlags, pszMarker, pErrInfo);
+    Assert(Buf.offBuf == 0);
+    return RT_SUCCESS(Buf.rc) ? (ssize_t)cchRet : Buf.rc;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsFile(RTVFSFILE hVfsFile, PRTASN1CORE pRoot,
+                                          uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+    RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+    AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+    ssize_t cchRet = RTCrPemWriteAsn1ToVfsIoStrm(hVfsIos, pRoot, fFlags, pszMarker, pErrInfo);
+    RTVfsIoStrmRelease(hVfsIos);
+    return cchRet;
+}
+
Index: unk/src/VBox/Runtime/common/crypto/pemfile.cpp
===================================================================
--- /trunk/src/VBox/Runtime/common/crypto/pemfile.cpp	(revision 84162)
+++ 	(revision )
@@ -1,652 +1,0 @@
-/* $Id$ */
-/** @file
- * IPRT - Crypto - PEM file reader / writer.
- *
- * See RFC-1341 for the original ideas for the format, but keep in mind
- * that the format was hijacked and put to different uses.  We're aiming at
- * dealing with the different uses rather than anything email related here.
- */
-
-/*
- * 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 "internal/iprt.h"
-#include <iprt/crypto/pem.h>
-
-#include <iprt/asm.h>
-#include <iprt/base64.h>
-#include <iprt/ctype.h>
-#include <iprt/err.h>
-#include <iprt/mem.h>
-#include <iprt/memsafer.h>
-#include <iprt/file.h>
-#include <iprt/string.h>
-
-
-
-/**
- * Looks for a PEM-like marker.
- *
- * @returns true if found, false if not.
- * @param   pbContent           Start of the content to search thru.
- * @param   cbContent           The size of the content to search.
- * @param   offStart            The offset into pbContent to start searching.
- * @param   pszLeadWord         The lead word (BEGIN/END).
- * @param   cchLeadWord         The length of the lead word.
- * @param   paMarkers           Pointer to an array of markers.
- * @param   cMarkers            Number of markers in the array.
- * @param   ppMatch             Where to return the pointer to the matching
- *                              marker. Optional.
- * @param   poffBegin           Where to return the start offset of the marker.
- *                              Optional.
- * @param   poffEnd             Where to return the end offset of the marker
- *                              (trailing whitespace and newlines will be
- *                              skipped).  Optional.
- */
-static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart,
-                              const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
-                              PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd)
-{
-    /* Remember the start of the content for the purpose of calculating offsets. */
-    uint8_t const * const pbStart = pbContent;
-
-    /* Skip adhead by offStart */
-    if (offStart >= cbContent)
-        return false;
-    pbContent += offStart;
-    cbContent -= offStart;
-
-    /*
-     * Search the content.
-     */
-    while (cbContent > 6)
-    {
-        /*
-         * Look for dashes.
-         */
-        uint8_t const *pbStartSearch = pbContent;
-        pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent);
-        if (!pbContent)
-            break;
-
-        cbContent -= pbContent - pbStartSearch;
-        if (cbContent < 6)
-            break;
-
-        /*
-         * There must be at least three to interest us.
-         */
-        if (   pbContent[1] == '-'
-            && pbContent[2] == '-')
-        {
-            unsigned cDashes = 3;
-            while (cDashes < cbContent && pbContent[cDashes] == '-')
-                cDashes++;
-
-            if (poffBegin)
-                *poffBegin = pbContent - pbStart;
-            cbContent -= cDashes;
-            pbContent += cDashes;
-
-            /*
-             * Match lead word.
-             */
-            if (   cbContent > cchLeadWord
-                && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0
-                && RT_C_IS_BLANK(pbContent[cchLeadWord]) )
-            {
-                pbContent += cchLeadWord;
-                cbContent -= cchLeadWord;
-                while (cbContent > 0 && RT_C_IS_BLANK(*pbContent))
-                {
-                    pbContent++;
-                    cbContent--;
-                }
-
-                /*
-                 * Match one of the specified markers.
-                 */
-                uint8_t const  *pbSavedContent = pbContent;
-                size_t  const   cbSavedContent = cbContent;
-                for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++)
-                {
-                    pbContent = pbSavedContent;
-                    cbContent = cbSavedContent;
-
-                    uint32_t            cWords = paMarkers[iMarker].cWords;
-                    PCRTCRPEMMARKERWORD pWord  = paMarkers[iMarker].paWords;
-                    while (cWords > 0)
-                    {
-                        uint32_t const cchWord = pWord->cchWord;
-                        if (cbContent <= cchWord)
-                            break;
-                        if (memcmp(pbContent, pWord->pszWord, cchWord))
-                            break;
-                        pbContent += cchWord;
-                        cbContent -= cchWord;
-
-                        if (!cbContent)
-                            break;
-                        if (RT_C_IS_BLANK(*pbContent))
-                            do
-                            {
-                                pbContent++;
-                                cbContent--;
-                            } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent));
-                        else if (cWords > 1 || pbContent[0] != '-')
-                            break;
-
-                        cWords--;
-                        if (cWords == 0)
-                        {
-                            /*
-                             * If there are three or more dashes following now, we've got a hit.
-                             */
-                            if (   cbContent > 3
-                                && pbContent[0] == '-'
-                                && pbContent[1] == '-'
-                                && pbContent[2] == '-')
-                            {
-                                cDashes = 3;
-                                while (cDashes < cbContent && pbContent[cDashes] == '-')
-                                    cDashes++;
-                                cbContent -= cDashes;
-                                pbContent += cDashes;
-
-                                /*
-                                 * Skip spaces and newline.
-                                 */
-                                while (cbContent > 0 && RT_C_IS_SPACE(*pbContent))
-                                    pbContent++, cbContent--;
-                                if (poffEnd)
-                                    *poffEnd = pbContent - pbStart;
-                                if (ppMatch)
-                                    *ppMatch = &paMarkers[iMarker];
-                                return true;
-                            }
-                            break;
-                        }
-                        pWord++;
-                    } /* for each word in marker. */
-                } /* for each marker. */
-            }
-        }
-        else
-        {
-            pbContent++;
-            cbContent--;
-        }
-    }
-
-    return false;
-}
-
-
-static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart,
-                                     PCRTCRPEMMARKER paMarkers, size_t cMarkers,
-                                     PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume)
-{
-    /** @todo Detect BEGIN / END mismatch. */
-    PCRTCRPEMMARKER pMatch;
-    if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers,
-                          &pMatch, NULL /*poffStart*/, poffBegin))
-    {
-        if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1,
-                              NULL /*ppMatch*/, poffEnd, poffResume))
-        {
-            *ppMatch = pMatch;
-            return true;
-        }
-    }
-    *ppMatch = NULL;
-    return false;
-}
-
-
-/**
- * Parses any fields the message may contain.
- *
- * @retval  VINF_SUCCESS
- * @retval  VERR_NO_MEMORY
- * @retval  VERR_CR_MALFORMED_PEM_HEADER
- *
- * @param   pSection        The current section, where we will attach a list of
- *                          fields to the pFieldHead member.
- * @param   pbContent       The content of the PEM message being parsed.
- * @param   cbContent       The length of the PEM message.
- * @param   pcbFields       Where to return the length of the header fields we found.
- */
-static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields)
-{
-    uint8_t const * const pbContentStart = pbContent;
-
-    /*
-     * Work the encapulated header protion field by field.
-     *
-     * This is optional, so currently we don't throw errors here but leave that
-     * to when we work the text portion with the base64 decoder.  Also, as a reader
-     * we don't go all pedanic on confirming to specification (RFC-1421), especially
-     * given that it's used for crypto certificates, keys and the like not email. :-)
-     */
-    PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead;
-    while (cbContent > 0)
-    {
-        /* Just look for a colon first. */
-        const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent);
-        if (!pbColon)
-            break;
-        size_t offColon = pbColon - pbContent;
-
-        /* Check that the colon is within the first line. */
-        if (!memchr(pbContent, '\n', cbContent - offColon))
-            return VERR_CR_MALFORMED_PEM_HEADER;
-
-        /* Skip leading spaces (there shouldn't be any, but just in case). */
-        while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0)
-        {
-            offColon--;
-            cbContent--;
-            pbContent++;
-        }
-
-        /* There shouldn't be any spaces before the colon, but just in case */
-        size_t cchName = offColon;
-        while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1]))
-            cchName--;
-
-        /* Skip leading value spaces (there typically is at least one). */
-        size_t offValue = offColon + 1;
-        while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue]))
-            offValue++;
-
-        /* Find the newline the field value ends with and where the next iteration should start later on. */
-        size_t         cbLeft;
-        uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue);
-        while (   pbNext
-               && (cbLeft = pbNext - pbContent) < cbContent
-               && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */)
-            pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1);
-
-        size_t cchValue;
-        if (pbNext)
-        {
-            cchValue = pbNext - &pbContent[offValue];
-            if (cchValue > 0 && pbNext[-1] == '\r')
-                cchValue--;
-            pbNext++;
-        }
-        else
-        {
-            cchValue = cbContent - offValue;
-            pbNext = &pbContent[cbContent];
-        }
-
-        /* Strip trailing spaces. */
-        while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1]))
-            cchValue--;
-
-        /*
-         * Allocate a field instance.
-         *
-         * Note! We don't consider field data sensitive at the moment.  This
-         *       mainly because the fields are chiefly used to indicate the
-         *       encryption parameters to the body.
-         */
-        PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1);
-        if (!pNewField)
-            return VERR_NO_MEMORY;
-        pNewField->cchName         = cchName;
-        pNewField->cchValue        = cchValue;
-        memcpy(pNewField->szName, pbContent, cchName);
-        pNewField->szName[cchName] = '\0';
-        char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue);
-        pNewField->pszValue        = pszDst;
-        pszDst[cchValue]           = '\0';
-        pNewField->pNext           = NULL;
-
-        *ppNext = pNewField;
-        ppNext = &pNewField->pNext;
-
-        /*
-         * Advance past the field.
-         */
-        cbContent -= pbNext - pbContent;
-        pbContent  = pbNext;
-    }
-
-    /*
-     * Skip blank line(s) before the body.
-     */
-    while (cbContent >= 1)
-    {
-        size_t cbSkip;
-        if (pbContent[0] == '\n')
-            cbSkip = 1;
-        else if (   pbContent[0] == '\r'
-                 && cbContent >= 2
-                 && pbContent[1] == '\n')
-            cbSkip = 2;
-        else
-            break;
-        pbContent += cbSkip;
-        cbContent -= cbSkip;
-    }
-
-    *pcbFields = pbContent - pbContentStart;
-    return VINF_SUCCESS;
-}
-
-
-/**
- * Does the decoding of a PEM-like data blob after it has been located.
- *
- * @returns IPRT status ocde
- * @param   pbContent           The start of the PEM-like content (text).
- * @param   cbContent           The max size of the PEM-like content.
- * @param   fSensitive          Set if the safer allocator should be used.
- * @param   ppvDecoded          Where to return a heap block containing the
- *                              decoded content.
- * @param   pcbDecoded          Where to return the size of the decoded content.
- */
-static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive,
-                               void **ppvDecoded, size_t *pcbDecoded)
-{
-    ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL);
-    if (cbDecoded < 0)
-        return VERR_INVALID_BASE64_ENCODING;
-
-    *pcbDecoded = cbDecoded;
-    void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded);
-    if (!pvDecoded)
-        return VERR_NO_MEMORY;
-
-    size_t cbActual;
-    int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL);
-    if (RT_SUCCESS(rc))
-    {
-        if (cbActual == (size_t)cbDecoded)
-        {
-            *ppvDecoded = pvDecoded;
-            return VINF_SUCCESS;
-        }
-
-        rc = VERR_INTERNAL_ERROR_3;
-    }
-    if (!fSensitive)
-        RTMemFree(pvDecoded);
-    else
-        RTMemSaferFree(pvDecoded, cbDecoded);
-    return rc;
-}
-
-
-/**
- * Checks if the content of a file looks to be binary or not.
- *
- * @returns true if likely to be binary, false if not binary.
- * @param   pbFile              The file bytes to scan.
- * @param   cbFile              The number of bytes.
- * @param   fFlags              RTCRPEMREADFILE_F_XXX
- */
-static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags)
-{
-    if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM)
-        return false;
-
-    /*
-     * Well formed PEM files should probably only contain 7-bit ASCII and
-     * restrict thenselfs to the following control characters:
-     *      tab, newline, return, form feed
-     *
-     * However, if we want to read PEM files which contains human readable
-     * certificate details before or after each base-64 section, we can't stick
-     * to 7-bit ASCII.  We could say it must be UTF-8, but that's probably to
-     * limited as well.  So, we'll settle for detecting binary files by control
-     * characters alone (safe enough for DER encoded stuff, I think).
-     */
-    while (cbFile-- > 0)
-    {
-        uint8_t const b = *pbFile++;
-        if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
-        {
-            /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
-            if (   (b == 4 || b == 26)
-                && (   cbFile == 0
-                    || (   cbFile == 1
-                        && *pbFile == '\0')))
-                return false;
-
-            if (b == 0 && cbFile == 0)
-                return false;
-
-            return true;
-        }
-    }
-    return false;
-}
-
-
-RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
-{
-    while (pSectionHead != NULL)
-    {
-        PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
-        pSectionHead = pSectionHead->pNext;
-        ASMCompilerBarrier(); /* paranoia */
-
-        if (pFree->pbData)
-        {
-            if (!pFree->fSensitive)
-                RTMemFree(pFree->pbData);
-            else
-                RTMemSaferFree(pFree->pbData, pFree->cbData);
-            pFree->pbData = NULL;
-            pFree->cbData = 0;
-        }
-
-        PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead;
-        if (pField)
-        {
-            pFree->pFieldHead = NULL;
-            do
-            {
-                PRTCRPEMFIELD pFreeField = pField;
-                pField = (PRTCRPEMFIELD)pField->pNext;
-                ASMCompilerBarrier(); /* paranoia */
-
-                pFreeField->pszValue = NULL;
-                RTMemFree(pFreeField);
-            } while (pField);
-        }
-
-        RTMemFree(pFree);
-    }
-    return VINF_SUCCESS;
-}
-
-
-RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
-                                PCRTCRPEMMARKER paMarkers, size_t cMarkers,
-                                PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
-{
-    RT_NOREF_PV(pErrInfo);
-
-    /*
-     * Input validation.
-     */
-    AssertPtr(ppSectionHead);
-    *ppSectionHead = NULL;
-    AssertReturn(cbContent, VINF_EOF);
-    AssertPtr(pvContent);
-    AssertPtr(paMarkers);
-    AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
-
-    /*
-     * Pre-allocate a section.
-     */
-    int rc = VINF_SUCCESS;
-    PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
-    if (pSection)
-    {
-        bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE);
-
-        /*
-         * Try locate the first section.
-         */
-        uint8_t const  *pbContent = (uint8_t const *)pvContent;
-        size_t          offBegin, offEnd, offResume;
-        PCRTCRPEMMARKER pMatch;
-        if (   !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags)
-            && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
-                                        &pMatch, &offBegin, &offEnd, &offResume) )
-        {
-            PCRTCRPEMSECTION *ppNext = ppSectionHead;
-            for (;;)
-            {
-                //pSection->pNext       = NULL;
-                pSection->pMarker       = pMatch;
-                //pSection->pbData      = NULL;
-                //pSection->cbData      = 0;
-                //pSection->pFieldHead  = NULL;
-                pSection->fSensitive    = fSensitive;
-
-                *ppNext = pSection;
-                ppNext = &pSection->pNext;
-
-                /*
-                 * Decode the section.
-                 */
-                size_t cbFields = 0;
-                int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields);
-                offBegin += cbFields;
-                if (RT_SUCCESS(rc2))
-                    rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive,
-                                              (void **)&pSection->pbData, &pSection->cbData);
-                if (RT_FAILURE(rc2))
-                {
-                    pSection->pbData = NULL;
-                    pSection->cbData = 0;
-                    if (   rc2 == VERR_INVALID_BASE64_ENCODING
-                        && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
-                        rc = -rc2;
-                    else
-                    {
-                        rc = rc2;
-                        break;
-                    }
-                }
-
-                /*
-                 * More sections?
-                 */
-                if (   offResume + 12 >= cbContent
-                    || offResume      >= cbContent
-                    || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
-                                                 &pMatch, &offBegin, &offEnd, &offResume) )
-                    break; /* No. */
-
-                /* Ok, allocate a new record for it. */
-                pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
-                if (RT_UNLIKELY(!pSection))
-                {
-                    rc = VERR_NO_MEMORY;
-                    break;
-                }
-            }
-            if (RT_SUCCESS(rc))
-                return rc;
-
-            RTCrPemFreeSections(*ppSectionHead);
-        }
-        else
-        {
-            if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM))
-            {
-                /*
-                 * No PEM section found.  Return the whole file as one binary section.
-                 */
-                //pSection->pNext       = NULL;
-                //pSection->pMarker     = NULL;
-                //pSection->pFieldHead  = NULL;
-                pSection->cbData        = cbContent;
-                pSection->fSensitive    = fSensitive;
-                if (!fSensitive)
-                    pSection->pbData    = (uint8_t *)RTMemDup(pbContent, cbContent);
-                else
-                {
-                    pSection->pbData    = (uint8_t *)RTMemSaferAllocZ(cbContent);
-                    if (pSection->pbData)
-                        memcpy(pSection->pbData, pbContent, cbContent);
-                }
-                if (pSection->pbData)
-                {
-                    *ppSectionHead = pSection;
-                    return VINF_SUCCESS;
-                }
-
-                rc = VERR_NO_MEMORY;
-            }
-            else
-                rc = VWRN_NOT_FOUND;
-            RTMemFree(pSection);
-        }
-    }
-    else
-        rc = VERR_NO_MEMORY;
-    *ppSectionHead = NULL;
-    return rc;
-}
-
-
-RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
-                            PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
-{
-    *ppSectionHead = NULL;
-    AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
-
-    size_t      cbContent;
-    void        *pvContent;
-    int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
-    if (RT_SUCCESS(rc))
-    {
-        rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
-        if (fFlags & RTCRPEMREADFILE_F_SENSITIVE)
-            RTMemWipeThoroughly(pvContent, cbContent, 3);
-        RTFileReadAllFree(pvContent, cbContent);
-    }
-    else
-        rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
-    return rc;
-}
-
-
-RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent,
-                                                      PCRTCRPEMMARKER paMarkers, size_t cMarkers)
-{
-    size_t offBegin;
-    if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL))
-        return (const char *)pvContent + offBegin;
-    return NULL;
-}
Index: /trunk/src/VBox/Runtime/common/vfs/vfsprintf.cpp
===================================================================
--- /trunk/src/VBox/Runtime/common/vfs/vfsprintf.cpp	(revision 84162)
+++ /trunk/src/VBox/Runtime/common/vfs/vfsprintf.cpp	(revision 84163)
@@ -35,18 +35,6 @@
 
 
-/*********************************************************************************************************************************
-*   Structures and Typedefs                                                                                                      *
-*********************************************************************************************************************************/
-typedef struct PRINTFBUF
-{
-    RTVFSIOSTREAM   hVfsIos;
-    int             rc;
-    size_t          offBuf;
-    char            szBuf[256];
-} PRINTFBUF;
-
-
 /** Writes the buffer to the VFS file. */
-static void FlushPrintfBuffer(PRINTFBUF *pBuf)
+static void FlushPrintfBuffer(PVFSIOSTRMOUTBUF pBuf)
 {
     if (pBuf->offBuf)
@@ -61,25 +49,47 @@
 
 
-/** @callback_method_impl{FNRTSTROUTPUT} */
-static DECLCALLBACK(size_t) MyPrintfOutputter(void *pvArg, const char *pachChars, size_t cbChars)
+/**
+ * @callback_method_impl{FNRTSTROUTPUT,
+ *      For use with VFSIOSTRMOUTBUF.}
+ */
+RTDECL(size_t) RTVfsIoStrmStrOutputCallback(void *pvArg, const char *pachChars, size_t cbChars)
 {
-    PRINTFBUF *pBuf = (PRINTFBUF *)pvArg;
+    PVFSIOSTRMOUTBUF pBuf = (PVFSIOSTRMOUTBUF)pvArg;
+    AssertReturn(pBuf->cbSelf == sizeof(*pBuf), 0);
+
     if (cbChars != 0)
     {
-        size_t offSrc = 0;
-        while  (offSrc < cbChars)
+        if (cbChars <= sizeof(pBuf->szBuf) * 3 / 2)
         {
-            size_t cbLeft = sizeof(pBuf->szBuf) - pBuf->offBuf - 1;
-            if (cbLeft > 0)
+            /*
+             * Small piece of output: Buffer it.
+             */
+            size_t offSrc = 0;
+            while  (offSrc < cbChars)
             {
-                size_t cbToCopy = RT_MIN(cbChars - offSrc, cbLeft);
-                memcpy(&pBuf->szBuf[pBuf->offBuf], &pachChars[offSrc], cbToCopy);
-                pBuf->offBuf += cbToCopy;
-                pBuf->szBuf[pBuf->offBuf] = '\0';
-                if (cbLeft > cbToCopy)
-                    break;
-                offSrc += cbToCopy;
+                size_t cbLeft = sizeof(pBuf->szBuf) - pBuf->offBuf - 1;
+                if (cbLeft > 0)
+                {
+                    size_t cbToCopy = RT_MIN(cbChars - offSrc, cbLeft);
+                    memcpy(&pBuf->szBuf[pBuf->offBuf], &pachChars[offSrc], cbToCopy);
+                    pBuf->offBuf += cbToCopy;
+                    pBuf->szBuf[pBuf->offBuf] = '\0';
+                    if (cbLeft > cbToCopy)
+                        break;
+                    offSrc += cbToCopy;
+                }
+                FlushPrintfBuffer(pBuf);
             }
+        }
+        else
+        {
+            /*
+             * Large chunk of output: Output it directly.
+            */
             FlushPrintfBuffer(pBuf);
+
+            int rc = RTVfsIoStrmWrite(pBuf->hVfsIos, pachChars, cbChars, true /*fBlocking*/, NULL);
+            if (RT_FAILURE(rc))
+                pBuf->rc = rc;
         }
     }
@@ -90,14 +100,10 @@
 
 
-
 RTDECL(ssize_t) RTVfsIoStrmPrintfV(RTVFSIOSTREAM hVfsIos, const char *pszFormat, va_list va)
 {
-    PRINTFBUF Buf;
-    Buf.hVfsIos  = hVfsIos;
-    Buf.rc       = VINF_SUCCESS;
-    Buf.offBuf   = 0;
-    Buf.szBuf[0] = '\0';
+    VFSIOSTRMOUTBUF Buf;
+    VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos);
 
-    size_t cchRet = RTStrFormatV(MyPrintfOutputter, &Buf, NULL, NULL, pszFormat, va);
+    size_t cchRet = RTStrFormatV(RTVfsIoStrmStrOutputCallback, &Buf, NULL, NULL, pszFormat, va);
     if (RT_SUCCESS(Buf.rc))
         return cchRet;
