/*
 * 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-nil, this computes a CRC32 as it goes, using the zlib
 * library function.
 */
/*static*/ DIError
GenericFD::CopyFile(GenericFD* pDst, GenericFD* pSrc, di_off_t length,
	unsigned long* pCRC)
{
	DIError dierr = kDIErrNone;
	const int kCopyBufSize = 32768;
	unsigned char* copyBuf = nil;
	int copySize;

	WMSG1("+++ CopyFile: %ld bytes\n", (long) length);

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

	copyBuf = new unsigned char[kCopyBufSize];
	if (copyBuf == nil)
		return kDIErrMalloc;

	if (pCRC != nil)
		*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 != nil)
			*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.
 */
#ifdef HAVE_FSEEKO

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

	if (fFp != nil)
		return kDIErrAlreadyOpen;
	if (filename == nil)
		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 == nil) {
		if (errno == EACCES)
			dierr = kDIErrAccessDenied;
		else
			dierr = ErrnoOrGeneric();
		WMSG3("  GDFile Open failed opening '%s', ro=%d (err=%d)\n",
			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 == nil)
		return kDIErrNotReady;
	actual = ::fread(buf, 1, length, fFp);
    if (actual == 0) {
        if (feof(fFp))
            return kDIErrEOF;
        if (ferror(fFp)) {
            dierr = ErrnoOrGeneric();
            return dierr;
        }
        WMSG0("MYSTERY FREAD RESULT\n");
        return kDIErrInternal;
    }

    if (pActual == nil) {
        if (actual != length) {
            dierr = ErrnoOrGeneric();
            WMSG3("  GDFile Read failed on %d bytes (actual=%d, err=%d)\n",
                length, 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 == nil)
		return kDIErrNotReady;
	if (fReadOnly)
		return kDIErrAccessDenied;
	assert(pActual == nil);		// not handling this yet
	if (::fwrite(buf, length, 1, fFp) != 1) {
		dierr = ErrnoOrGeneric();
		WMSG2("  GDFile Write failed on %d bytes (err=%d)\n", 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 == nil)
		return kDIErrNotReady;
	//assert(offset <= kAlmostTwoGB);
	//if (::fseek(fFp, (long) offset, whence) != 0) {
	if (::fseeko(fFp, offset, whence) != 0) {
		dierr = ErrnoOrGeneric();
		WMSG1("  GDFile Seek failed (err=%d)\n", dierr);
		return dierr;
	}
	return dierr;
}

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

	if (fFp == nil)
		return kDIErrNotReady;
	//result = ::ftell(fFp);
	result = ::ftello(fFp);
	if (result == -1) {
		dierr = ErrnoOrGeneric();
		WMSG1("  GDFile Tell failed (err=%d)\n", 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 == nil)
		return kDIErrNotReady;

	WMSG1("  GFDFile closing '%s'\n", fPathName);
	fclose(fFp);
	fFp = nil;
	return kDIErrNone;
}

#else /*HAVE_FSEEKO*/

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

	if (fFd >= 0)
		return kDIErrAlreadyOpen;
	if (filename == nil)
		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
			dierr = ErrnoOrGeneric();
		WMSG3("  GDFile Open failed opening '%s', ro=%d (err=%d)\n",
			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();
        WMSG3("  GDFile Read failed on %d bytes (actual=%d, err=%d)\n",
            length, actual, dierr);
        return dierr;
	}

    if (pActual == nil) {
        if (actual != (ssize_t) length) {
            WMSG2("  GDFile Read partial (wanted=%d actual=%d)\n",
                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 == nil);		// not handling partial writes yet
	actual = ::write(fFd, buf, length);
	if (actual != (ssize_t) length) {
		dierr = ErrnoOrGeneric();
		WMSG3("  GDFile Write failed on %d bytes (actual=%d err=%d)\n",
			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((unsigned long) offset != 0xccccccccUL);	// uninitialized data!
		dierr = ErrnoOrGeneric();
		WMSG3("  GDFile Seek %ld-%lu failed (err=%d)\n",
			(long) (offset >> 32), (unsigned long) 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();
		WMSG1("  GDFile Tell failed (err=%d)\n", 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;

	WMSG1("  GFDFile closing '%s'\n", 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 != nil)
		return kDIErrAlreadyOpen;
	if (length <= 0)
		return kDIErrInvalidArg;
	if (length > kMaxReasonableSize) {
		// be reasonable
		WMSG1(" GFDBuffer refusing to allocate buffer size(long)=%ld bytes\n",
			(long) length);
		return kDIErrInvalidArg;
	}

    /* if buffer is nil, allocate it ourselves */
    if (buffer == nil) {
        fBuffer = (void*) new char[(int) length];
        if (fBuffer == nil)
            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 == nil)
		return kDIErrNotReady;
    if (length == 0)
        return kDIErrInvalidArg;

	if (fCurrentOffset + (long)length > fLength) {
        if (pActual == nil) {
            WMSG3("  GFDBuffer underrrun off=%ld len=%d flen=%ld\n",
                (long) fCurrentOffset, 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 != nil)
        *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 == nil)
		return kDIErrNotReady;
	assert(pActual == nil);		// not handling this yet
	if (fCurrentOffset + (long)length > fLength) {
        if (!fDoExpand) {
            WMSG3("  GFDBuffer overrun off=%ld len=%d flen=%ld\n",
                (long) fCurrentOffset, 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;
            WMSG1("Reallocating buffer (new size = %ld)\n", fAllocLength);
			assert(fAllocLength < kMaxReasonableSize);
            char* newBuf = new char[(int) fAllocLength];
            if (newBuf == nil)
                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 == nil)
		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 == nil)
		return (di_off_t) -1;
	return fCurrentOffset;
}

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

	if (fDoDelete) {
		WMSG0("  GFDBuffer closing and deleting\n");
		delete[] (char*) fBuffer;
	} else {
		WMSG0("  GFDBuffer closing\n");
	}
	fBuffer = nil;

	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 = nil;
	//unsigned long kTwoGBBlocks;
	
	if (fVolAccess.Ready())
		return kDIErrAlreadyOpen;
	if (deviceName == nil)
		return kDIErrInvalidArg;
    if (deviceName[0] == '\0')
        return kDIErrInvalidArg;

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

	dierr = fVolAccess.Open(deviceName, readOnly);
	if (dierr != kDIErrNone)
		goto bail;

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

	unsigned 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;
	unsigned char* blkBuf = nil;

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

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

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

	long advanceLen = length;

	blkBuf = new unsigned char[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;

			//WMSG2("    Copying %d bytes from block %d\n",
			//	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;
	unsigned char* blkBuf = nil;

	//WMSG2(" GFDWinVolume: writing %ld bytes at offset %ld\n", 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 == nil)
			return kDIErrDataOverrun;
		length = (size_t) (fVolumeEOF - fCurrentOffset);
	}
	if (pActual != nil)
		*pActual = length;
	if (length == 0)
		return kDIErrNone;

	long advanceLen = length;

	blkBuf = new unsigned char[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;

			//WMSG3("    Copying %d bytes into block %d (off=%d)\n",
			//	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;

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