/**
 * Does one free space wipe, using the given filename.
 *
 * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE on failure (fully
 *          bitched).
 * @param   pszFilename     The filename to use for wiping free space.  Will be
 *                          replaced and afterwards deleted.
 * @param   pvFiller        The filler block buffer.
 * @param   cbFiller        The size of the filler block buffer.
 * @param   cbMinLeftOpt    When to stop wiping.
 */
static RTEXITCODE doOneFreeSpaceWipe(const char *pszFilename, void const *pvFiller, size_t cbFiller, uint64_t cbMinLeftOpt)
{
    /*
     * Open the file.
     */
    RTEXITCODE  rcExit = RTEXITCODE_SUCCESS;
    RTFILE      hFile  = NIL_RTFILE;
    int rc = RTFileOpen(&hFile, pszFilename,
                        RTFILE_O_WRITE | RTFILE_O_DENY_NONE | RTFILE_O_CREATE_REPLACE | (0775 << RTFILE_O_CREATE_MODE_SHIFT));
    if (RT_SUCCESS(rc))
    {
        /*
         * Query the amount of available free space.  Figure out which API we should use.
         */
        RTFOFF cbTotal = 0;
        RTFOFF cbFree = 0;
        rc = RTFileQueryFsSizes(hFile, &cbTotal, &cbFree, NULL, NULL);
        bool const fFileHandleApiSupported = rc != VERR_NOT_SUPPORTED && rc != VERR_NOT_IMPLEMENTED;
        if (!fFileHandleApiSupported)
            rc = RTFsQuerySizes(pszFilename, &cbTotal, &cbFree, NULL, NULL);
        if (RT_SUCCESS(rc))
        {
            RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free\n", pszFilename, cbFree / _1M, cbTotal / _1M);

            /*
             * Start filling up the free space, down to the last 32MB.
             */
            uint64_t const  nsStart       = RTTimeNanoTS();     /* for speed calcs */
            uint64_t        nsStat        = nsStart;            /* for speed calcs */
            uint64_t        cbStatWritten = 0;                  /* for speed calcs */
            RTFOFF const    cbMinLeft     = RT_MAX(cbMinLeftOpt, cbFiller * 2);
            RTFOFF          cbLeftToWrite = cbFree - cbMinLeft;
            uint64_t        cbWritten     = 0;
            uint32_t        iLoop         = 0;
            while (cbLeftToWrite >= (RTFOFF)cbFiller)
            {
                rc = RTFileWrite(hFile, pvFiller, cbFiller, NULL);
                if (RT_FAILURE(rc))
                {
                    if (rc == VERR_DISK_FULL)
                        RTPrintf("%s: Disk full after writing %'9RU64 MiB\n", pszFilename, cbWritten / _1M);
                    else
                        rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Write error after %'RU64 bytes: %Rrc\n",
                                                pszFilename, cbWritten, rc);
                    break;
                }

                /* Flush every now and then as we approach a completely full disk. */
                if (cbLeftToWrite <= _1G && (iLoop & (cbLeftToWrite  > _128M ? 15 : 3)) == 0)
                    RTFileFlush(hFile);

                /*
                 * Advance and maybe recheck the amount of free space.
                 */
                cbWritten     += cbFiller;
                cbLeftToWrite -= (ssize_t)cbFiller;
                iLoop++;
                if ((iLoop & (16 - 1)) == 0 || cbLeftToWrite < _256M)
                {
                    RTFOFF cbFreeUpdated;
                    if (fFileHandleApiSupported)
                        rc = RTFileQueryFsSizes(hFile, NULL, &cbFreeUpdated, NULL, NULL);
                    else
                        rc = RTFsQuerySizes(pszFilename, NULL, &cbFreeUpdated, NULL, NULL);
                    if (RT_SUCCESS(rc))
                    {
                        cbFree = cbFreeUpdated;
                        cbLeftToWrite = cbFree - cbMinLeft;
                    }
                    else
                    {
                        rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to query free space after %'RU64 bytes: %Rrc\n",
                                                pszFilename, cbWritten, rc);
                        break;
                    }
                    if ((iLoop & (512 - 1)) == 0)
                    {
                        uint64_t const nsNow = RTTimeNanoTS();
                        uint64_t cNsInterval = nsNow - nsStat;
                        uint64_t cbInterval  = cbWritten - cbStatWritten;
                        uint64_t cbIntervalPerSec = cbInterval ? (uint64_t)(cbInterval / (cNsInterval / (double)RT_NS_1SEC)) : 0;

                        RTPrintf("%s: %'9RTfoff MiB out of %'9RTfoff are free after writing %'9RU64 MiB (%'5RU64 MiB/s)\n",
                                 pszFilename, cbFree / _1M, cbTotal  / _1M, cbWritten  / _1M, cbIntervalPerSec / _1M);
                        nsStat        = nsNow;
                        cbStatWritten = cbWritten;
                    }
                }
            }

            /*
             * Now flush the file and then reduce the size a little before closing
             * it so the system won't entirely run out of space.  The flush should
             * ensure the data has actually hit the disk.
             */
            rc = RTFileFlush(hFile);
            if (RT_FAILURE(rc))
                rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Flush failed at %'RU64 bytes: %Rrc\n", pszFilename, cbWritten, rc);

            uint64_t cbReduced = cbWritten > _512M ? cbWritten - _512M : cbWritten / 2;
            rc = RTFileSetSize(hFile, cbReduced);
            if (RT_FAILURE(rc))
                rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to reduce file size from %'RU64 to %'RU64 bytes: %Rrc\n",
                                        pszFilename, cbWritten, cbReduced, rc);

            /* Issue a summary statements. */
            uint64_t cNsElapsed = RTTimeNanoTS() - nsStart;
            uint64_t cbPerSec   = cbWritten ? (uint64_t)(cbWritten / (cNsElapsed / (double)RT_NS_1SEC)) : 0;
            RTPrintf("%s: Wrote %'RU64 MiB in %'RU64 s, avg %'RU64 MiB/s.\n",
                     pszFilename, cbWritten / _1M, cNsElapsed / RT_NS_1SEC, cbPerSec / _1M);
        }
        else
            rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Initial free space query failed: %Rrc \n", pszFilename, rc);

        RTFileClose(hFile);

        /*
         * Delete the file.
         */
        rc = RTFileDelete(pszFilename);
        if (RT_FAILURE(rc))
            rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Delete failed: %Rrc !!\n", pszFilename, rc);
    }
    else
        rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Open failed: %Rrc\n", pszFilename, rc);
    return rcExit;
}
int main(int argc, char **argv)
{
    RTR3InitExe(argc, &argv, 0);

    /*
     * Process all arguments (including the executable).
     */
    int cErrors = 0;
    for (int i = 0; i < argc; i++)
    {
        RTPrintf("tstRTFsQueries: '%s'...\n", argv[i]);

        uint32_t u32Serial;
        int rc = RTFsQuerySerial(argv[i], &u32Serial);
        if (RT_SUCCESS(rc))
            RTPrintf("tstRTFsQueries: u32Serial=%#010RX32\n", u32Serial);
        else
        {
            RTPrintf("tstRTFsQueries: RTFsQuerySerial failed, rc=%Rrc\n", rc);
            cErrors++;
        }

        RTFOFF cbTotal = 42;
        RTFOFF cbFree  = 42;
        uint32_t cbBlock = 42;
        uint32_t cbSector = 42;
        rc = RTFsQuerySizes(argv[i], &cbTotal, &cbFree, &cbBlock, &cbSector);
        if (RT_SUCCESS(rc))
            RTPrintf("tstRTFsQueries: cbTotal=%RTfoff cbFree=%RTfoff cbBlock=%d cbSector=%d\n",
                     cbTotal, cbFree, cbBlock, cbSector);
        else
        {
            RTPrintf("tstRTFsQueries: RTFsQuerySerial failed, rc=%Rrc\n", rc);
            cErrors++;
        }

        rc = RTFsQuerySizes(argv[i], NULL, NULL, NULL, NULL);
        if (RT_FAILURE(rc))
        {
            RTPrintf("tstRTFsQueries: RTFsQuerySizes(nop) failed, rc=%Rrc\n", rc);
            cErrors++;
        }

        RTFSTYPE enmType;
        rc = RTFsQueryType(argv[i], &enmType);
        if (RT_SUCCESS(rc))
            RTPrintf("tstRTFsQueries: file system type is '%s'\n", RTFsTypeName(enmType));
        else
        {
            RTPrintf("tstRTFsQueries: RTFsQueryType failed, rc=%Rrc\n", rc);
            cErrors++;
        }

        RTFSPROPERTIES Props;
        rc = RTFsQueryProperties(argv[i], &Props);
        if (RT_SUCCESS(rc))
            RTPrintf("tstRTFsQueries: cbMaxComponent=%u %s %s %s %s %s %s\n",
                     Props.cbMaxComponent,
                     Props.fCaseSensitive ? "case" : "not-case",
                     Props.fCompressed ? "compressed" : "not-compressed",
                     Props.fFileCompression ? "file-compression" : "no-file-compression",
                     Props.fReadOnly ? "readonly" : "readwrite",
                     Props.fRemote ? "remote" : "not-remote",
                     Props.fSupportsUnicode ? "supports-unicode" : "doesn't-support-unicode");
        else
        {
            RTPrintf("tstRTFsQueries: RTFsQueryProperties failed, rc=%Rrc\n", rc);
            cErrors++;
        }
    }

    if (!cErrors)
        RTPrintf("tstRTFsQueries: SUCCESS\n");
    else
        RTPrintf("tstRTFsQueries: FAIlURE - %u errors\n", cErrors);
    return !!cErrors;
}