/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Generic file descriptor class.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"

/*
 * ===========================================================================
 *      GenericFD utility functions
 * ===========================================================================
 */

/*
 * Copy "length" bytes from "pSrc" to "pDst".  Both GenericFDs should be
 * seeked to their initial positions.
 *
 * If "pCRC" is non-NULL, this computes a CRC32 as it goes, using the zlib
 * library function.
 */
/*static*/ DIError GenericFD::CopyFile(GenericFD* pDst, GenericFD* pSrc,
    di_off_t length, uint32_t* pCRC)
{
    DIError dierr = kDIErrNone;
    const int kCopyBufSize = 32768;
    uint8_t* copyBuf = NULL;
    int copySize;

    LOGD("+++ CopyFile: %ld bytes", (long) length);

    if (pDst == NULL || pSrc == NULL || length < 0)
        return kDIErrInvalidArg;
    if (length == 0)
        return kDIErrNone;

    copyBuf = new uint8_t[kCopyBufSize];
    if (copyBuf == NULL)
        return kDIErrMalloc;

    if (pCRC != NULL)
        *pCRC = crc32(0L, Z_NULL, 0);

    while (length != 0) {
        copySize = kCopyBufSize;
        if (copySize > length)
            copySize = (int) length;

        dierr = pSrc->Read(copyBuf, copySize);
        if (dierr != kDIErrNone)
            goto bail;

        if (pCRC != NULL)
            *pCRC = crc32(*pCRC, copyBuf, copySize);

        dierr = pDst->Write(copyBuf, copySize);
        if (dierr != kDIErrNone)
            goto bail;

        length -= copySize;
    }

bail:
    delete[] copyBuf;
    return dierr;
}


/*
 * ===========================================================================
 *      GFDFile
 * ===========================================================================
 */

/*
 * The stdio functions (fopen/fread/fwrite/fseek/ftell) are buffered and,
 * therefore, faster for small operations.  Unfortunately we need 64-bit
 * file offsets, and it doesn't look like the Windows stdio stuff will
 * support it cleanly (e.g. even the _FPOSOFF macro returns a "long").
 *
 * Recent versions of Linux have "fseeko", which is like fseek but takes
 * an off_t, so we can continue to use the FILE* functions there.  Under
 * Windows "lseek" takes a long, so we have to use their specific 64-bit
 * variant.
 *
 * TODO: Visual Studio 2005 added _fseeki64.  We should be able to merge
 * the bulk of the implementation now.
 */
#ifdef HAVE_FSEEKO

DIError GFDFile::Open(const char* filename, bool readOnly)
{
    DIError dierr = kDIErrNone;

    if (fFp != NULL)
        return kDIErrAlreadyOpen;
    if (filename == NULL)
        return kDIErrInvalidArg;
    if (filename[0] == '\0')
        return kDIErrInvalidArg;

    delete[] fPathName;
    fPathName = new char[strlen(filename) +1];
    strcpy(fPathName, filename);

    fFp = fopen(filename, readOnly ? "rb" : "r+b");
    if (fFp == NULL) {
        if (errno == EACCES)
            dierr = kDIErrAccessDenied;
        else
            dierr = ErrnoOrGeneric();
        LOGI("  GDFile Open failed opening '%s', ro=%d (err=%d)",
            filename, readOnly, dierr);
        return dierr;
    }
    fReadOnly = readOnly;
    return dierr;
}

DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
{
    DIError dierr = kDIErrNone;
    size_t actual;

    if (fFp == NULL)
        return kDIErrNotReady;
    actual = ::fread(buf, 1, length, fFp);
    if (actual == 0) {
        if (feof(fFp))
            return kDIErrEOF;
        if (ferror(fFp)) {
            dierr = ErrnoOrGeneric();
            return dierr;
        }
        LOGI("MYSTERY FREAD RESULT");
        return kDIErrInternal;
    }

    if (pActual == NULL) {
        if (actual != length) {
            dierr = ErrnoOrGeneric();
            LOGW("  GDFile Read failed on %lu bytes (actual=%lu, err=%d)",
                (unsigned long) length, (unsigned long) actual, dierr);
            return dierr;
        }
    } else {
        *pActual = actual;
    }
    return dierr;
}

DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
{
    DIError dierr = kDIErrNone;

    if (fFp == NULL)
        return kDIErrNotReady;
    if (fReadOnly)
        return kDIErrAccessDenied;
    assert(pActual == NULL);     // not handling this yet
    if (::fwrite(buf, length, 1, fFp) != 1) {
        dierr = ErrnoOrGeneric();
        LOGW("  GDFile Write failed on %lu bytes (err=%d)",
            (unsigned long) length, dierr);
        return dierr;
    }
    return dierr;
}

DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
{
    DIError dierr = kDIErrNone;
    //static const long kOneGB = 1024*1024*1024;
    //static const long kAlmostTwoGB = kOneGB + (kOneGB -1);

    if (fFp == NULL)
        return kDIErrNotReady;
    //assert(offset <= kAlmostTwoGB);
    //if (::fseek(fFp, (long) offset, whence) != 0) {
    if (::fseeko(fFp, offset, whence) != 0) {
        dierr = ErrnoOrGeneric();
        LOGI("  GDFile Seek failed (err=%d)", dierr);
        return dierr;
    }
    return dierr;
}

di_off_t GFDFile::Tell(void)
{
    DIError dierr = kDIErrNone;
    di_off_t result;

    if (fFp == NULL)
        return kDIErrNotReady;
    //result = ::ftell(fFp);
    result = ::ftello(fFp);
    if (result == -1) {
        dierr = ErrnoOrGeneric();
        LOGI("  GDFile Tell failed (err=%d)", dierr);
        return result;
    }
    return result;
}

DIError GFDFile::Truncate(void)
{
#if defined(HAVE_FTRUNCATE)
    int cc;
    cc = ::ftruncate(fileno(fFp), (long) Tell());
    if (cc != 0)
        return kDIErrWriteFailed;
#elif defined(HAVE_CHSIZE)
    assert(false);      // not tested
    int cc;
    cc = ::chsize(fFd, (long) Tell());
    if (cc != 0)
        return kDIErrWriteFailed;
#else
# error "missing truncate"
#endif
    return kDIErrNone;
}

DIError GFDFile::Close(void)
{
    if (fFp == NULL)
        return kDIErrNotReady;

    LOGI("  GFDFile closing '%s'", fPathName);
    fclose(fFp);
    fFp = NULL;
    return kDIErrNone;
}

#else /*HAVE_FSEEKO*/

DIError GFDFile::Open(const char* filename, bool readOnly)
{
    DIError dierr = kDIErrNone;

    if (fFd >= 0)
        return kDIErrAlreadyOpen;
    if (filename == NULL)
        return kDIErrInvalidArg;
    if (filename[0] == '\0')
        return kDIErrInvalidArg;

    delete[] fPathName;
    fPathName = new char[strlen(filename) +1];
    strcpy(fPathName, filename);

    fFd = open(filename, readOnly ? O_RDONLY|O_BINARY : O_RDWR|O_BINARY, 0);
    if (fFd < 0) {
        if (errno == EACCES) {
            dierr = kDIErrAccessDenied;
        } else if (errno == EINVAL) {
            // Happens on Win32 if a Unicode filename conversion failed,
            // because non-converting chars turn into '?', which is illegal.
            dierr = kDIErrInvalidArg;
        } else {
            dierr = ErrnoOrGeneric();
        }
        LOGW("  GDFile Open failed opening '%s', ro=%d (err=%d)",
            filename, readOnly, dierr);
        return dierr;
    }
    fReadOnly = readOnly;
    return dierr;
}

DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
{
    DIError dierr;
    ssize_t actual;

    if (fFd < 0)
        return kDIErrNotReady;
    actual = ::read(fFd, buf, length);
    if (actual == 0)
        return kDIErrEOF;
    if (actual < 0) {
        dierr = ErrnoOrGeneric();
        LOGW("  GDFile Read failed on %d bytes (actual=%d, err=%d)",
            length, actual, dierr);
        return dierr;
    }

    if (pActual == NULL) {
        if (actual != (ssize_t) length) {
            LOGI("  GDFile Read partial (wanted=%d actual=%d)",
                length, actual);
            return kDIErrReadFailed;
        }
    } else {
        *pActual = actual;
    }
    return kDIErrNone;
}

DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
{
    DIError dierr;
    ssize_t actual;

    if (fFd < 0)
        return kDIErrNotReady;
    if (fReadOnly)
        return kDIErrAccessDenied;
    assert(pActual == NULL);     // not handling partial writes yet
    actual = ::write(fFd, buf, length);
    if (actual != (ssize_t) length) {
        dierr = ErrnoOrGeneric();
        LOGI("  GDFile Write failed on %d bytes (actual=%d err=%d)",
            length, actual, dierr);
        return dierr;
    }
    return kDIErrNone;
}

DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
{
    DIError dierr = kDIErrNone;
    if (fFd < 0)
        return kDIErrNotReady;

#ifdef WIN32
    __int64 newPosn;
    const __int64 kFailure = (__int64) -1;
    newPosn = ::_lseeki64(fFd, (__int64) offset, whence);
#else
    di_off_t newPosn;
    const di_off_t kFailure = (di_off_t) -1;
    newPosn = lseek(fFd, offset, whence);
#endif

    if (newPosn == kFailure) {
        assert((uint32_t) offset != 0xccccccccUL); // uninitialized data!
        dierr = ErrnoOrGeneric();
        LOGI("  GDFile Seek %ld-%lu failed (err=%d)",
            (long) (offset >> 32), (uint32_t) offset, dierr);
    }
    return dierr;
}

di_off_t GFDFile::Tell(void)
{
    DIError dierr = kDIErrNone;
    di_off_t result;

    if (fFd < 0)
        return kDIErrNotReady;

#ifdef WIN32
    result = ::_lseeki64(fFd, 0, SEEK_CUR);
#else
    result = lseek(fFd, 0, SEEK_CUR);
#endif

    if (result == -1) {
        dierr = ErrnoOrGeneric();
        LOGI("  GDFile Tell failed (err=%d)", dierr);
        return result;
    }
    return result;
}

DIError GFDFile::Truncate(void)
{
#if defined(HAVE_FTRUNCATE)
    int cc;
    cc = ::ftruncate(fFd, (long) Tell());
    if (cc != 0)
        return kDIErrWriteFailed;
#elif defined(HAVE_CHSIZE)
    int cc;
    cc = ::chsize(fFd, (long) Tell());
    if (cc != 0)
        return kDIErrWriteFailed;
#else
# error "missing truncate"
#endif
    return kDIErrNone;
}

DIError GFDFile::Close(void)
{
    if (fFd < 0)
        return kDIErrNotReady;

    LOGI("  GFDFile closing '%s'", fPathName);
    ::close(fFd);
    fFd = -1;
    return kDIErrNone;
}
#endif /*HAVE_FSEEKO else*/


/*
 * ===========================================================================
 *      GFDBuffer
 * ===========================================================================
 */

DIError GFDBuffer::Open(void* buffer, di_off_t length, bool doDelete,
    bool doExpand, bool readOnly)
{
    if (fBuffer != NULL)
        return kDIErrAlreadyOpen;
    if (length <= 0)
        return kDIErrInvalidArg;
    if (length > kMaxReasonableSize) {
        // be reasonable
        LOGI(" GFDBuffer refusing to allocate buffer size(long)=%ld bytes",
            (long) length);
        return kDIErrInvalidArg;
    }

    /* if buffer is NULL, allocate it ourselves */
    if (buffer == NULL) {
        fBuffer = (void*) new uint8_t[(int) length];
        if (fBuffer == NULL)
            return kDIErrMalloc;
    } else
        fBuffer = buffer;

    fLength = (long) length;
    fAllocLength = (long) length;
    fDoDelete = doDelete;
    fDoExpand = doExpand;
    fReadOnly = readOnly;

    fCurrentOffset = 0;

    return kDIErrNone;
}

DIError GFDBuffer::Read(void* buf, size_t length, size_t* pActual)
{
    if (fBuffer == NULL)
        return kDIErrNotReady;
    if (length == 0)
        return kDIErrInvalidArg;

    if (fCurrentOffset + (long)length > fLength) {
        if (pActual == NULL) {
            LOGW("  GFDBuffer underrrun off=%ld len=%lu flen=%ld",
                (long) fCurrentOffset, (unsigned long) length, (long) fLength);
            return kDIErrDataUnderrun;
        } else {
            /* set *pActual and adjust "length" */
            assert(fLength >= fCurrentOffset);
            length = (size_t) (fLength - fCurrentOffset);
            *pActual = length;

            if (length == 0)
                return kDIErrEOF;
        }
    }
    if (pActual != NULL)
        *pActual = length;

    memcpy(buf, (const char*)fBuffer + fCurrentOffset, length);
    fCurrentOffset += length;

    return kDIErrNone;
}

DIError GFDBuffer::Write(const void* buf, size_t length, size_t* pActual)
{
    if (fBuffer == NULL)
        return kDIErrNotReady;
    assert(pActual == NULL);     // not handling this yet
    if (fCurrentOffset + (long)length > fLength) {
        if (!fDoExpand) {
            LOGI("  GFDBuffer overrun off=%ld len=%lu flen=%ld",
                (long) fCurrentOffset, (unsigned long) length, (long) fLength);
            return kDIErrDataOverrun;
        }

        /*
         * Expand the buffer as needed.
         *
         * We delete the old buffer unless "doDelete" is not set, in
         * which case we just drop the pointer.  Anything we allocate
         * here can and will be deleted; "doDelete" only applies to the
         * pointer initially passed in.
         */
        if (fCurrentOffset + (long)length <= fAllocLength) {
            /* fits inside allocated space, so just extend length */
            fLength = (long) fCurrentOffset + (long)length;
        } else {
            /* does not fit, realloc buffer */
            fAllocLength = (long) fCurrentOffset + (long)length + 8*1024;
            LOGI("Reallocating buffer (new size = %ld)", fAllocLength);
            assert(fAllocLength < kMaxReasonableSize);
            char* newBuf = new char[(int) fAllocLength];
            if (newBuf == NULL)
                return kDIErrMalloc;

            memcpy(newBuf, fBuffer, fLength);

            if (fDoDelete)
                delete[] (char*)fBuffer;
            else
                fDoDelete = true;       // future deletions are okay

            fBuffer = newBuf;
            fLength = (long) fCurrentOffset + (long)length;
        }
    }

    memcpy((char*)fBuffer + fCurrentOffset, buf, length);
    fCurrentOffset += length;

    return kDIErrNone;
}

DIError GFDBuffer::Seek(di_off_t offset, DIWhence whence)
{
    if (fBuffer == NULL)
        return kDIErrNotReady;

    switch (whence) {
    case kSeekSet:
        if (offset < 0 || offset >= fLength)
            return kDIErrInvalidArg;
        fCurrentOffset = offset;
        break;
    case kSeekEnd:
        if (offset > 0 || offset < -fLength)
            return kDIErrInvalidArg;
        fCurrentOffset = fLength + offset;
        break;
    case kSeekCur:
        if (offset < -fCurrentOffset ||
            offset >= (fLength - fCurrentOffset))
        {
            return kDIErrInvalidArg;
        }
        fCurrentOffset += offset;
        break;
    default:
        assert(false);
        return kDIErrInvalidArg;
    }

    assert(fCurrentOffset >= 0 && fCurrentOffset <= fLength);
    return kDIErrNone;
}

di_off_t GFDBuffer::Tell(void)
{
    if (fBuffer == NULL)
        return (di_off_t) -1;
    return fCurrentOffset;
}

DIError GFDBuffer::Close(void)
{
    if (fBuffer == NULL)
        return kDIErrNone;

    if (fDoDelete) {
        LOGI("  GFDBuffer closing and deleting");
        delete[] (char*) fBuffer;
    } else {
        LOGI("  GFDBuffer closing");
    }
    fBuffer = NULL;

    return kDIErrNone;
}


#ifdef _WIN32
/*
 * ===========================================================================
 *      GFDWinVolume
 * ===========================================================================
 */

/*
 * This class is intended for use with logical volumes under Win32.  Such
 * devices must be accessed on 512-byte boundaries, which means no arbitrary
 * seeks or reads.  The device driver doesn't seem too adept at figuring
 * out how large the device is, either, so we need to work that out for
 * ourselves.  (The standard approach appears to involve examining the
 * partition map for the logical or physical volume, but we don't have a
 * partition map to look at.)
 */

/*
 * Prepare a logical volume device for reading or writing.  "deviceName"
 * must have the form "N:\" for a logical volume or "80:\" for a physical
 * volume.
 */
DIError GFDWinVolume::Open(const char* deviceName, bool readOnly)
{
    DIError dierr = kDIErrNone;
    HANDLE handle = NULL;
    //uint32_t kTwoGBBlocks;
    
    if (fVolAccess.Ready())
        return kDIErrAlreadyOpen;
    if (deviceName == NULL)
        return kDIErrInvalidArg;
    if (deviceName[0] == '\0')
        return kDIErrInvalidArg;

    delete[] fPathName;
    fPathName = new char[strlen(deviceName) +1];
    strcpy(fPathName, deviceName);

    // Create a UNICODE representation of the device name.  We may want
    // to make the argument UNICODE instead, but most of diskimg is 8-bit
    // character oriented.
    size_t srcLen = strlen(deviceName) + 1;
    WCHAR* wdeviceName = new WCHAR[srcLen];
    size_t convertedChars;
    mbstowcs_s(&convertedChars, wdeviceName, srcLen, deviceName, _TRUNCATE);
    dierr = fVolAccess.Open(wdeviceName, readOnly);
    delete[] wdeviceName;
    if (dierr != kDIErrNone)
        goto bail;

    fBlockSize = fVolAccess.GetBlockSize(); // must be power of 2
    assert(fBlockSize > 0);
    //kTwoGBBlocks = kTwoGB / fBlockSize;

    long totalBlocks;
    totalBlocks = fVolAccess.GetTotalBlocks();
    fVolumeEOF = (di_off_t)totalBlocks * fBlockSize;

    assert(fVolumeEOF > 0);

    fReadOnly = readOnly;

bail:
    return dierr;
}

DIError GFDWinVolume::Read(void* buf, size_t length, size_t* pActual)
{
    DIError dierr = kDIErrNone;
    uint8_t* blkBuf = NULL;

    //LOGI(" GFDWinVolume: reading %ld bytes from offset %ld", length,
    //  fCurrentOffset);

    if (!fVolAccess.Ready())
        return kDIErrNotReady;

    // don't allow reading past the end of file
    if (fCurrentOffset + (long) length > fVolumeEOF) {
        if (pActual == NULL)
            return kDIErrDataUnderrun;
        length = (size_t) (fVolumeEOF - fCurrentOffset);
    }
    if (pActual != NULL)
        *pActual = length;
    if (length == 0)
        return kDIErrNone;

    long advanceLen = length;

    blkBuf = new uint8_t[fBlockSize];     // get this off the heap??
    long blockIndex = (long) (fCurrentOffset / fBlockSize);
    int bufOffset = (int) (fCurrentOffset % fBlockSize);    // req power of 2
    assert(blockIndex >= 0);

    /*
     * When possible, do multi-block reads directly into "buf".  The first
     * and last block may require special handling.
     */
    while (length) {
        assert(length > 0);

        if (bufOffset != 0 || length < (size_t) fBlockSize) {
            assert(bufOffset >= 0 && bufOffset < fBlockSize);

            size_t thisCount;

            dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
            if (dierr != kDIErrNone)
                goto bail;

            thisCount = fBlockSize - bufOffset;
            if (thisCount > length)
                thisCount = length;

            //LOGI("    Copying %d bytes from block %d",
            //  thisCount, blockIndex);

            memcpy(buf, blkBuf + bufOffset, thisCount);
            length -= thisCount;
            buf = (char*) buf + thisCount;

            bufOffset = 0;
            blockIndex++;
        } else {
            assert(bufOffset == 0);

            long blockCount = length / fBlockSize;
            assert(blockCount < 32768);

            dierr = fVolAccess.ReadBlocks(blockIndex, (short) blockCount, buf);
            if (dierr != kDIErrNone)
                goto bail;

            length -= blockCount * fBlockSize;
            buf = (char*) buf + blockCount * fBlockSize;

            blockIndex += blockCount;
        }

    }

    fCurrentOffset += advanceLen;

bail:
    delete[] blkBuf;
    return dierr;
}

DIError GFDWinVolume::Write(const void* buf, size_t length, size_t* pActual)
{
    DIError dierr = kDIErrNone;
    uint8_t* blkBuf = NULL;

    //LOGI(" GFDWinVolume: writing %ld bytes at offset %ld", length,
    //  fCurrentOffset);

    if (!fVolAccess.Ready())
        return kDIErrNotReady;
    if (fReadOnly)
        return kDIErrAccessDenied;

    // don't allow writing past the end of the volume
    if (fCurrentOffset + (long) length > fVolumeEOF) {
        if (pActual == NULL)
            return kDIErrDataOverrun;
        length = (size_t) (fVolumeEOF - fCurrentOffset);
    }
    if (pActual != NULL)
        *pActual = length;
    if (length == 0)
        return kDIErrNone;

    long advanceLen = length;

    blkBuf = new uint8_t[fBlockSize];     // get this out of the heap??
    long blockIndex = (long) (fCurrentOffset / fBlockSize);
    int bufOffset = (int) (fCurrentOffset % fBlockSize);    // req power of 2
    assert(blockIndex >= 0);

    /*
     * When possible, do multi-block writes directly from "buf".  The first
     * and last block may require special handling.
     */
    while (length) {
        assert(length > 0);

        if (bufOffset != 0 || length < (size_t) fBlockSize) {
            assert(bufOffset >= 0 && bufOffset < fBlockSize);

            size_t thisCount;

            dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
            if (dierr != kDIErrNone)
                goto bail;

            thisCount = fBlockSize - bufOffset;
            if (thisCount > length)
                thisCount = length;

            //LOGI("    Copying %d bytes into block %d (off=%d)",
            //  thisCount, blockIndex, bufOffset);

            memcpy(blkBuf + bufOffset, buf, thisCount);
            length -= thisCount;
            buf = (char*) buf + thisCount;

            dierr = fVolAccess.WriteBlocks(blockIndex, 1, blkBuf);
            if (dierr != kDIErrNone)
                goto bail;

            bufOffset = 0;
            blockIndex++;
        } else {
            assert(bufOffset == 0);

            long blockCount = length / fBlockSize;
            assert(blockCount < 32768);

            dierr = fVolAccess.WriteBlocks(blockIndex, (short) blockCount, buf);
            if (dierr != kDIErrNone)
                goto bail;

            length -= blockCount * fBlockSize;
            buf = (char*) buf + blockCount * fBlockSize;

            blockIndex += blockCount;
        }

    }

    fCurrentOffset += advanceLen;

bail:
    delete[] blkBuf;
    return dierr;
}

DIError GFDWinVolume::Seek(di_off_t offset, DIWhence whence)
{
    if (!fVolAccess.Ready())
        return kDIErrNotReady;

    switch (whence) {
    case kSeekSet:
        if (offset < 0 || offset >= fVolumeEOF)
            return kDIErrInvalidArg;
        fCurrentOffset = offset;
        break;
    case kSeekEnd:
        if (offset > 0 || offset < -fVolumeEOF)
            return kDIErrInvalidArg;
        fCurrentOffset = fVolumeEOF + offset;
        break;
    case kSeekCur:
        if (offset < -fCurrentOffset ||
            offset >= (fVolumeEOF - fCurrentOffset))
        {
            return kDIErrInvalidArg;
        }
        fCurrentOffset += offset;
        break;
    default:
        assert(false);
        return kDIErrInvalidArg;
    }

    assert(fCurrentOffset >= 0 && fCurrentOffset <= fVolumeEOF);
    return kDIErrNone;
}

di_off_t GFDWinVolume::Tell(void)
{
    if (!fVolAccess.Ready())
        return (di_off_t) -1;
    return fCurrentOffset;
}

DIError GFDWinVolume::Close(void)
{
    if (!fVolAccess.Ready())
        return kDIErrNotReady;

    LOGI("  GFDWinVolume closing");
    fVolAccess.Close();
    return kDIErrNone;
}
#endif /*_WIN32*/
