ciderpress/diskimg/GenericFD.cpp
Andy McFadden edb7c13120 Fix diskimg OpenImage
The OpenImage method had an overload that took void*.  This turns out
to be a bad idea, because void* matches any pointer type that didn't
match something else.  So the WCHAR* filenames were going to the "open
from buffer" method rather than the "open from file" variant.

A less important issue is whether open-from-buffer should take a const
or non-const pointer.  If the "readOnly" boolean flag is not set, then
the contents can be altered and const is inappropriate.  The best course
seems to be to drop the boolean flag as an argument, and just have two
different methods.
2014-11-23 10:34:57 -08:00

885 lines
23 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,
unsigned long* pCRC)
{
DIError dierr = kDIErrNone;
const int kCopyBufSize = 32768;
unsigned char* copyBuf = NULL;
int copySize;
LOGI("+++ CopyFile: %ld bytes", (long) length);
if (pDst == NULL || pSrc == NULL || length < 0)
return kDIErrInvalidArg;
if (length == 0)
return kDIErrNone;
copyBuf = new unsigned char[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.
*/
#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();
LOGI(" GDFile Read failed on %d bytes (actual=%d, err=%d)",
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 == NULL)
return kDIErrNotReady;
if (fReadOnly)
return kDIErrAccessDenied;
assert(pActual == NULL); // not handling this yet
if (::fwrite(buf, length, 1, fFp) != 1) {
dierr = ErrnoOrGeneric();
LOGI(" GDFile Write failed on %d bytes (err=%d)", 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
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;
ssize_t actual;
if (fFd < 0)
return kDIErrNotReady;
actual = ::read(fFd, buf, length);
if (actual == 0)
return kDIErrEOF;
if (actual < 0) {
dierr = ErrnoOrGeneric();
LOGI(" 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((unsigned long) offset != 0xccccccccUL); // uninitialized data!
dierr = ErrnoOrGeneric();
LOGI(" GDFile Seek %ld-%lu failed (err=%d)",
(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();
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) {
LOGI(" GFDBuffer underrrun off=%ld len=%d flen=%ld",
(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 != 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=%d flen=%ld",
(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;
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;
//unsigned long 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;
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 = 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 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;
//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;
unsigned char* 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 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;
//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*/