ciderpress/diskimg/GenericFD.cpp

867 lines
24 KiB
C++

/*
* 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*/