ciderpress/diskimg/DiskImg.h
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

1667 lines
68 KiB
C++

/*
* CiderPress
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Public declarations for the DiskImg library.
*
* Everything is wrapped in the "DiskImgLib" namespace. Either prefix
* all references with "DiskImgLib::", or add "using namespace DiskImgLib"
* to all C++ source files that make use of it.
*
* Under Linux, this should be compiled with -D_FILE_OFFSET_BITS=64.
*
* These classes are not thread-safe with respect to access to a single
* disk image. Writing to the same disk image from multiple threads
* simultaneously is bound to end in disaster. Simultaneous access to
* different objects will work, though modifying the same disk image
* file from multiple objects will lead to unpredictable results.
*/
#ifndef DISKIMG_DISKIMG_H
#define DISKIMG_DISKIMG_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
//#define EXCISE_GPL_CODE
/* Windows DLL stuff */
#ifdef _WIN32
# ifdef DISKIMG_EXPORTS
# define DISKIMG_API __declspec(dllexport)
# else
# define DISKIMG_API __declspec(dllimport)
# endif
#else
# define DISKIMG_API
#endif
namespace DiskImgLib {
/* compiled-against versions; call DiskImg::GetVersion for linked-against */
#define kDiskImgVersionMajor 4
#define kDiskImgVersionMinor 6
#define kDiskImgVersionBug 0
/*
* Errors from the various disk image classes.
*/
typedef enum DIError {
kDIErrNone = 0,
/* I/O request errors (should renumber/rename to match GS/OS errors?) */
kDIErrAccessDenied = -10,
kDIErrVWAccessForbidden = -11, // write access to volume forbidden
kDIErrSharingViolation = -12, // file is in use and not shareable
kDIErrNoExclusiveAccess = -13, // couldn't get exclusive access
kDIErrWriteProtected = -14, // disk is write protected
kDIErrCDROMNotSupported = -15, // access to CD-ROM drives not supptd
kDIErrASPIFailure = -16, // generic ASPI failure result
kDIErrSPTIFailure = -17, // generic SPTI failure result
kDIErrSCSIFailure = -18, // generic SCSI failure result
kDIErrDeviceNotReady = -19, // floppy or CD-ROM drive has no media
kDIErrFileNotFound = -20,
kDIErrForkNotFound = -21, // requested fork does not exist
kDIErrAlreadyOpen = -22, // already open, can't open a 2nd time
kDIErrFileOpen = -23, // file is open, can't delete it
kDIErrNotReady = -24,
kDIErrFileExists = -25, // file already exists
kDIErrDirectoryExists = -26, // directory already exists
kDIErrEOF = -30, // end-of-file reached
kDIErrReadFailed = -31,
kDIErrWriteFailed = -32,
kDIErrDataUnderrun = -33, // tried to read off end of the image
kDIErrDataOverrun = -34, // tried to write off end of the image
kDIErrGenericIO = -35, // generic I/O error
kDIErrOddLength = -40, // image size not multiple of sectors
kDIErrUnrecognizedFileFmt = -41, // file format just not recognized
kDIErrBadFileFormat = -42, // filename ext doesn't match contents
kDIErrUnsupportedFileFmt = -43, // recognized but not supported
kDIErrUnsupportedPhysicalFmt = -44, // (same)
kDIErrUnsupportedFSFmt = -45, // (and again)
kDIErrBadOrdering = -46, // requested sector ordering no good
kDIErrFilesystemNotFound = -47, // requested filesystem isn't there
kDIErrUnsupportedAccess = -48, // e.g. read sectors from blocks-only
kDIErrUnsupportedImageFeature = -49, // e.g. FDI image w/Amiga sectors
kDIErrInvalidTrack = -50, // request for invalid track number
kDIErrInvalidSector = -51, // request for invalid sector number
kDIErrInvalidBlock = -52, // request for invalid block number
kDIErrInvalidIndex = -53, // request with an invalid index
kDIErrDirectoryLoop = -60, // directory chain points into itself
kDIErrFileLoop = -61, // file sector or block alloc loops
kDIErrBadDiskImage = -62, // the FS on the disk image is damaged
kDIErrBadFile = -63, // bad file on disk image
kDIErrBadDirectory = -64, // bad dir on disk image
kDIErrBadPartition = -65, // bad partition on multi-part format
kDIErrFileArchive = -70, // file archive, not disk archive
kDIErrUnsupportedCompression = -71, // compression method is not supported
kDIErrBadChecksum = -72, // image file's checksum is bad
kDIErrBadCompressedData = -73, // data can't even be unpacked
kDIErrBadArchiveStruct = -74, // bad archive structure
kDIErrBadNibbleSectors = -80, // can't read sectors from this image
kDIErrSectorUnreadable = -81, // requested sector not readable
kDIErrInvalidDiskByte = -82, // invalid byte for encoding type
kDIErrBadRawData = -83, // couldn't get correct nibbles
kDIErrInvalidFileName = -90, // tried to create file with bad name
kDIErrDiskFull = -91, // no space left on disk
kDIErrVolumeDirFull = -92, // no more entries in volume dir
kDIErrInvalidCreateReq = -93, // CreateImage request was flawed
kDIErrTooBig = -94, // larger than we want to handle
/* higher-level errors */
kDIErrGeneric = -101,
kDIErrInternal = -102,
kDIErrMalloc = -103,
kDIErrInvalidArg = -104,
kDIErrNotSupported = -105, // feature not currently supported
kDIErrCancelled = -106, // an operation was cancelled by user
kDIErrNufxLibInitFailed = -110,
} DIError;
/* return a string describing the error */
DISKIMG_API const char* DIStrError(DIError dierr);
/* exact definition of off_t varies, so just define our own */
#ifdef _ULONGLONG_
typedef LONGLONG di_off_t;
#else
typedef off_t di_off_t;
#endif
/* common definition of "whence" for seeks */
typedef enum DIWhence {
kSeekSet = SEEK_SET,
kSeekCur = SEEK_CUR,
kSeekEnd = SEEK_END
};
/* try to load ASPI under Win2K; if successful, SPTI should be disabled */
const bool kAlwaysTryASPI = false;
/* ASPI device "filenames" look like "ASPI:x:y:z\" */
DISKIMG_API extern const char* kASPIDev;
/* some nibble-encoding constants */
const int kTrackLenNib525 = 6656;
const int kTrackLenNb2525 = 6384;
const int kTrackLenTrackStar525 = 6525; // max len of data in TS image
const int kTrackAllocSize = 6656; // max 5.25 nibble track len; for buffers
const int kTrackCount525 = 35; // expected #of tracks on 5.25 img
const int kMaxNibbleTracks525 = 40; // max #of tracks on 5.25 nibble img
const int kDefaultNibbleVolumeNum = 254;
const int kBlockSize = 512; // block size for DiskImg interfaces
const int kSectorSize = 256; // sector size (1/2 block)
const int kD13Length = 256 * 13 * 35; // length of a .d13 image
/* largest expanse we allow access to on a volume (8GB in 512-byte blocks) */
const long kVolumeMaxBlocks = 8*1024*(1024*1024 / kBlockSize);
/* largest .gz file we'll open (uncompressed size) */
const long kGzipMax = 32*1024*1024;
/* forward and external class definitions */
class DiskFS;
class A2File;
class A2FileDescr;
class GenericFD;
class OuterWrapper;
class ImageWrapper;
class CircularBufferAccess;
class ASPI;
class LinearBitmap;
/*
* Library-global data functions.
*
* This class is just a namespace clumper. Do not instantiate.
*/
class DISKIMG_API Global {
public:
// one-time DLL initialization; use SetDebugMsgHandler first
static DIError AppInit(void);
// one-time DLL cleanup
static DIError AppCleanup(void);
// return the DiskImg version number
static void GetVersion(long* pMajor, long* pMinor, long* pBug);
static bool GetAppInitCalled(void) { return fAppInitCalled; }
static bool GetHasSPTI(void);
static bool GetHasASPI(void);
// return a pointer to our global ASPI instance, or NULL
static ASPI* GetASPI(void) { return fpASPI; }
// shortcut for fpASPI->GetVersion()
static unsigned long GetASPIVersion(void);
// pointer to the debug message handler
typedef void (*DebugMsgHandler)(const char* file, int line, const char* msg);
static DebugMsgHandler gDebugMsgHandler;
static DebugMsgHandler SetDebugMsgHandler(DebugMsgHandler handler);
static void PrintDebugMsg(const char* file, int line, const char* fmt, ...)
#if defined(__GNUC__)
__attribute__ ((format(printf, 3, 4)))
#endif
;
private:
// no instantiation allowed
Global(void) {}
~Global(void) {}
// make sure app calls AppInit
static bool fAppInitCalled;
static ASPI* fpASPI;
};
extern bool gAllowWritePhys0; // ugh -- see Win32BlockIO.cpp
/*
* Disk I/O class, roughly equivalent to a GS/OS disk device driver.
*
* Abstracts away the file's source (file on disk, file in memory) and
* storage format (DOS order, ProDOS order, nibble). Will also cope
* with common disk compression and wrapper formats (Mac DiskCopy, 2MG,
* ShrinkIt, etc) if handed a file on disk.
*
* Images may be embedded within other images, e.g. UNIDOS and storage
* type $04 pascal volumes.
*
* THOUGHT: we need a list(?) of pointers from here back to the DiskFS
* so that any modifications here will "wake" the DiskFS and sub-volumes.
* We also need a "dirty" flag so things like CloseNufx can know not to
* re-do work when Closing after a Flush. Also DiskFS can alert us to
* any locally cached stuff, and we can tell them to flush everything.
* (Possibly useful when doing disk updates, so stuff can be trivially
* un-done. Currently CiderPress checks the filename manually after
* each write, but that's generally less reliable than having the knowledge
* contained in the DiskImg.)
*
* THOUGHT: need a ReadRawTrack that gets raw nibblized data. For a
* nibblized image it returns the data, for a sector image it generates
* the raw data.
*
* THOUGHT: we could reduce the risk of problems and increase performance
* for physical media with a "copy on write" scheme. We'd create a shadow
* array of modified blocks, and write them at Flush time. This would
* provide an instantaneous "revert" feature, and prevent formats like
* DiskCopy42 (which has a CRC in its header) from being inconsistent for
* long stretches.
*/
class DISKIMG_API DiskImg {
public:
// create DiskImg object
DiskImg(void);
virtual ~DiskImg(void);
/*
* Types describing an image file.
*
* The file itself is described by an external parameter ("file source")
* that is either the name of the file, a memory buffer, or an EFD
* (EmbeddedFileDescriptor).
*/
typedef enum { // format of the "wrapper wrapper"
kOuterFormatUnknown = 0,
kOuterFormatNone = 1, // (plain)
kOuterFormatCompress = 2, // .xx.Z
kOuterFormatGzip = 3, // .xx.gz
kOuterFormatBzip2 = 4, // .xx.bz2
kOuterFormatZip = 10, // .zip
} OuterFormat;
typedef enum { // format of the image "wrapper"
kFileFormatUnknown = 0,
kFileFormatUnadorned = 1, // .po, .do, ,nib, .raw, .d13
kFileFormat2MG = 2, // .2mg, .2img, $e0/0130
kFileFormatDiskCopy42 = 3, // .dsk/.disk, maybe .dc
kFileFormatDiskCopy60 = 4, // .dc6 (often just raw format)
kFileFormatDavex = 5, // $e0/8004
kFileFormatSim2eHDV = 6, // .hdv
kFileFormatTrackStar = 7, // .app (40-track or 80-track)
kFileFormatFDI = 8, // .fdi (5.25" or 3.5")
kFileFormatNuFX = 20, // .shk, .sdk, .bxy
kFileFormatDDD = 21, // .ddd
kFileFormatDDDDeluxe = 22, // $DD, .ddd
} FileFormat;
typedef enum { // format of the image data stream
kPhysicalFormatUnknown = 0,
kPhysicalFormatSectors = 1, // sequential 256-byte sectors (13/16/32)
kPhysicalFormatNib525_6656 = 2, // 5.25" disk ".nib" (6656 bytes/track)
kPhysicalFormatNib525_6384 = 3, // 5.25" disk ".nb2" (6384 bytes/track)
kPhysicalFormatNib525_Var = 4, // 5.25" disk (variable len, e.g. ".app")
} PhysicalFormat;
typedef enum { // sector ordering for "sector" format images
kSectorOrderUnknown = 0,
kSectorOrderProDOS = 1, // written as series of ProDOS blocks
kSectorOrderDOS = 2, // written as series of DOS sectors
kSectorOrderCPM = 3, // written as series of 1K CP/M blocks
kSectorOrderPhysical = 4, // written as un-interleaved sectors
kSectorOrderMax, // (used for array sizing)
} SectorOrder;
typedef enum { // main filesystem format (based on NuFX enum)
kFormatUnknown = 0,
kFormatProDOS = 1,
kFormatDOS33 = 2,
kFormatDOS32 = 3,
kFormatPascal = 4,
kFormatMacHFS = 5,
kFormatMacMFS = 6,
kFormatLisa = 7,
kFormatCPM = 8,
//kFormatCharFST
kFormatMSDOS = 10, // any FAT filesystem
//kFormatHighSierra
kFormatISO9660 = 12,
//kFormatAppleShare
kFormatRDOS33 = 20, // 16-sector RDOS disk
kFormatRDOS32 = 21, // 13-sector RDOS disk
kFormatRDOS3 = 22, // 13-sector RDOS disk converted to 16
// "generic" formats *must* be in their own "decade"
kFormatGenericPhysicalOrd = 30, // unknown, but physical-sector-ordered
kFormatGenericProDOSOrd = 31, // unknown, but ProDOS-block-ordered
kFormatGenericDOSOrd = 32, // unknown, but DOS-sector-ordered
kFormatGenericCPMOrd = 33, // unknown, but CP/M-block-ordered
kFormatUNIDOS = 40, // two 400K DOS 3.3 volumes
kFormatOzDOS = 41, // two 400K DOS 3.3 volumes, weird order
kFormatCFFA4 = 42, // CFFA image with 4 or 6 partitions
kFormatCFFA8 = 43, // CFFA image with 8 partitions
kFormatMacPart = 44, // Macintosh-style partitioned disk
kFormatMicroDrive = 45, // ///SHH Systeme's MicroDrive format
kFormatFocusDrive = 46, // Parsons Engineering FocusDrive format
kFormatGutenberg = 47, // Gutenberg word processor format
// try to keep this in an unsigned char, e.g. for CP clipboard
} FSFormat;
/*
* Nibble encode/decode description. Use no pointers here, so we
* store as an array and resize at will.
*
* Should we define an enum to describe whether address and data
* headers are standard or some wacky variant?
*/
typedef enum {
kNibbleAddrPrologLen = 3, // d5 aa 96
kNibbleAddrEpilogLen = 3, // de aa eb
kNibbleDataPrologLen = 3, // d5 aa ad
kNibbleDataEpilogLen = 3, // de aa eb
};
typedef enum {
kNibbleEncUnknown = 0,
kNibbleEnc44,
kNibbleEnc53,
kNibbleEnc62,
} NibbleEnc;
typedef enum {
kNibbleSpecialNone = 0,
kNibbleSpecialMuse, // doubled sector numbers on tracks > 2
kNibbleSpecialSkipFirstAddrByte,
} NibbleSpecial;
typedef struct {
char description[32];
short numSectors; // 13 or 16 (or 18?)
unsigned char addrProlog[kNibbleAddrPrologLen];
unsigned char addrEpilog[kNibbleAddrEpilogLen];
unsigned char addrChecksumSeed;
bool addrVerifyChecksum;
bool addrVerifyTrack;
int addrEpilogVerifyCount;
unsigned char dataProlog[kNibbleDataPrologLen];
unsigned char dataEpilog[kNibbleDataEpilogLen];
unsigned char dataChecksumSeed;
bool dataVerifyChecksum;
int dataEpilogVerifyCount;
NibbleEnc encoding;
NibbleSpecial special;
} NibbleDescr;
static inline bool IsSectorFormat(PhysicalFormat fmt) {
return (fmt == kPhysicalFormatSectors);
}
static inline bool IsNibbleFormat(PhysicalFormat fmt) {
return (fmt == kPhysicalFormatNib525_6656 ||
fmt == kPhysicalFormatNib525_6384 ||
fmt == kPhysicalFormatNib525_Var);
}
// file is on disk; stuff like 2MG headers will be identified and stripped
DIError OpenImage(const char* filename, char fssep, bool readOnly);
// file is in memory; provide a pointer to the data start and buffer size
DIError OpenImageFromBufferRO(const uint8_t* buffer, long length);
// file is in memory; provide a pointer to the data start and buffer size
DIError OpenImageFromBufferRW(uint8_t* buffer, long length);
// file is a range of blocks on an open block-oriented disk image
DIError OpenImage(DiskImg* pParent, long firstBlock, long numBlocks);
// file is a range of tracks/sectors on an open sector-oriented disk image
DIError OpenImage(DiskImg* pParent, long firstTrack, long firstSector,
long numSectors);
// create a new, blank image file
DIError CreateImage(const char* pathName, const char* storageName,
OuterFormat outerFormat, FileFormat fileFormat,
PhysicalFormat physical, const NibbleDescr* pNibbleDescr,
SectorOrder order, FSFormat format,
long numBlocks, bool skipFormat);
DIError CreateImage(const char* pathName, const char* storageName,
OuterFormat outerFormat, FileFormat fileFormat,
PhysicalFormat physical, const NibbleDescr* pNibbleDescr,
SectorOrder order, FSFormat format,
long numTracks, long numSectPerTrack, bool skipFormat);
// flush any changes to disk; slow recompress only for "kFlushAll"
typedef enum { kFlushUnknown=0, kFlushFastOnly=1, kFlushAll=2 } FlushMode;
DIError FlushImage(FlushMode mode);
// close the image, freeing up any resources in use
DIError CloseImage(void);
// raise/lower refCnt (may want to track pointers someday)
void AddDiskFS(DiskFS* pDiskFS) { fDiskFSRefCnt++; }
void RemoveDiskFS(DiskFS* pDiskFS) {
assert(fDiskFSRefCnt > 0);
fDiskFSRefCnt--;
}
// (re-)format this image in the specified FS format
DIError FormatImage(FSFormat format, const char* volName);
// reset all blocks/sectors to zeroes
DIError ZeroImage(void);
// configure for paired sectors (OzDOS)
void SetPairedSectors(bool enable, int idx);
// identify sector ordering and disk format
// (may want a version that takes "hints" for special disks?)
DIError AnalyzeImage(void);
// figure out what FS and sector ordering is on the disk image
void AnalyzeImageFS(void);
bool ShowAsBlocks(void) const;
// overrule the analyzer (generally not recommended) -- does not
// override FileFormat, which is very reliable
DIError OverrideFormat(PhysicalFormat physical, FSFormat format,
SectorOrder order);
// Create a DiskFS that matches this DiskImg. Must be called after
// AnalayzeImage, or you will always get a DiskFSUnknown. The DiskFS
// must be freed with "delete" when no longer needed.
DiskFS* OpenAppropriateDiskFS(bool allowUnknown = false);
// Add information or a warning to the list of notes. Use linefeeds to
// indicate line breaks. This is currently append-only.
typedef enum { kNoteInfo, kNoteWarning } NoteType;
void AddNote(NoteType type, const char* fmt, ...)
#if defined(__GNUC__)
__attribute__ ((format(printf, 3, 4)))
#endif
;
const char* GetNotes(void) const;
// simple accessors
OuterFormat GetOuterFormat(void) const { return fOuterFormat; }
FileFormat GetFileFormat(void) const { return fFileFormat; }
PhysicalFormat GetPhysicalFormat(void) const { return fPhysical; }
SectorOrder GetSectorOrder(void) const { return fOrder; }
FSFormat GetFSFormat(void) const { return fFormat; }
long GetNumTracks(void) const { return fNumTracks; }
int GetNumSectPerTrack(void) const { return fNumSectPerTrack; }
long GetNumBlocks(void) const { return fNumBlocks; }
bool GetReadOnly(void) const { return fReadOnly; }
bool GetDirtyFlag(void) const { return fDirty; }
// set read-only flag; don't use this (open with correct setting;
// this was added as safety hack for the volume copier)
void SetReadOnly(bool val) { fReadOnly = val; }
// read a 256-byte sector
// NOTE to self: this function should not be available for odd-sized
// volumes, e.g. a ProDOS /RAM or /RAM5 stored with Davex. Need some way
// to communicate that to disk editor so it knows to grey-out the
// selection checkbox and/or not use "show as sectors" as default.
virtual DIError ReadTrackSector(long track, int sector, void* buf) {
return ReadTrackSectorSwapped(track, sector, buf, fOrder,
fFileSysOrder);
}
DIError ReadTrackSectorSwapped(long track, int sector,
void* buf, SectorOrder imageOrder, SectorOrder fsOrder);
// write a 256-byte sector
virtual DIError WriteTrackSector(long track, int sector, const void* buf);
// read a 512-byte block
virtual DIError ReadBlock(long block, void* buf) {
return ReadBlockSwapped(block, buf, fOrder, fFileSysOrder);
}
DIError ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder,
SectorOrder fsOrder);
// read multiple blocks
virtual DIError ReadBlocks(long startBlock, int numBlocks, void* buf);
// check our virtual bad block map
bool CheckForBadBlocks(long startBlock, int numBlocks);
// write a 512-byte block
virtual DIError WriteBlock(long block, const void* buf);
// write multiple blocks
virtual DIError WriteBlocks(long startBlock, int numBlocks, const void* buf);
// read an entire nibblized track
virtual DIError ReadNibbleTrack(long track, unsigned char* buf,
long* pTrackLen);
// write a track; trackLen must be <= those in image
virtual DIError WriteNibbleTrack(long track, const unsigned char* buf,
long trackLen);
// save the current image as a 2MG file
//DIError Write2MG(const char* filename);
// need to treat the DOS volume number as meta-data for some disks
short GetDOSVolumeNum(void) const { return fDOSVolumeNum; }
void SetDOSVolumeNum(short val) { fDOSVolumeNum = val; }
enum { kVolumeNumNotSet = -1 };
// some simple getters
bool GetHasSectors(void) const { return fHasSectors; }
bool GetHasBlocks(void) const { return fHasBlocks; }
bool GetHasNibbles(void) const { return fHasNibbles; }
bool GetIsEmbedded(void) const { return fpParentImg != NULL; }
// return the current NibbleDescr
const NibbleDescr* GetNibbleDescr(void) const { return fpNibbleDescr; }
// set the NibbleDescr; we do this by copying the entry into our table
// (could improve by doing memcmp on available entries?)
void SetNibbleDescr(int idx);
void SetCustomNibbleDescr(const NibbleDescr* pDescr);
const NibbleDescr* GetNibbleDescrTable(int* pCount) const {
*pCount = fNumNibbleDescrEntries;
return fpNibbleDescrTable;
}
// set the NuFX compression type, used when compressing or re-compressing;
// must be set before image is opened or created
void SetNuFXCompressionType(int val) { fNuFXCompressType = val; }
/*
* Set up a progress callback to use when scanning a disk volume. Pass
* NULL for "func" to disable.
*
* The callback function is expected to return "true" if all is well.
* If it returns false, kDIErrCancelled will eventually come back.
*/
typedef bool (*ScanProgressCallback)(void* cookie, const char* str,
int count);
void SetScanProgressCallback(ScanProgressCallback func, void* cookie);
/* update status dialog during disk scan; called from DiskFS code */
bool UpdateScanProgress(const char* newStr);
/*
* Static utility functions.
*/
// returns "true" if the files on this image have DOS structure, i.e.
// simple file types and high-ASCII text files
static bool UsesDOSFileStructure(FSFormat format) {
return (format == kFormatDOS33 ||
format == kFormatDOS32 ||
format == kFormatGutenberg ||
format == kFormatUNIDOS ||
format == kFormatOzDOS ||
format == kFormatRDOS33 ||
format == kFormatRDOS32 ||
format == kFormatRDOS3);
}
// returns "true" if we can open files on the specified filesystem
static bool CanOpenFiles(FSFormat format) {
return (format == kFormatProDOS ||
format == kFormatDOS33 ||
format == kFormatDOS32 ||
format == kFormatPascal ||
format == kFormatCPM ||
format == kFormatRDOS33 ||
format == kFormatRDOS32 ||
format == kFormatRDOS3);
}
// returns "true" if we can create subdirectories on this filesystem
static bool IsHierarchical(FSFormat format) {
return (format == kFormatProDOS ||
format == kFormatMacHFS ||
format == kFormatMSDOS);
}
// returns "true" if we can create resource forks on this filesystem
static bool HasResourceForks(FSFormat format) {
return (format == kFormatProDOS ||
format == kFormatMacHFS);
}
// returns "true" if the format is one of the "generics"
static bool IsGenericFormat(FSFormat format) {
return (format / 10 == DiskImg::kFormatGenericDOSOrd / 10);
}
/* this must match DiskImg::kStdNibbleDescrs table */
typedef enum StdNibbleDescr {
kNibbleDescrDOS33Std = 0,
kNibbleDescrDOS33Patched,
kNibbleDescrDOS33IgnoreChecksum,
kNibbleDescrDOS32Std,
kNibbleDescrDOS32Patched,
kNibbleDescrMuse32,
kNibbleDescrRDOS33,
kNibbleDescrRDOS32,
kNibbleDescrCustom, // slot for custom entry
kNibbleDescrMAX // must be last
};
static const NibbleDescr* GetStdNibbleDescr(StdNibbleDescr idx);
// call this once, at DLL initialization time
static void CalcNibbleInvTables(void);
// calculate block number from cyl/head/sect on 3.5" disk
static int CylHeadSect35ToBlock(int cyl, int head, int sect);
// unpack nibble data from a 3.5" disk track
static DIError UnpackNibbleTrack35(const unsigned char* nibbleBuf,
long nibbleLen, unsigned char* outputBuf, int cyl, int head,
LinearBitmap* pBadBlockMap);
// compute the #of sectors per track for cylinder N (0-79)
static int SectorsPerTrack35(int cylinder);
// get the order in which we test for sector ordering
static void GetSectorOrderArray(SectorOrder* orderArray, SectorOrder first);
// utility function used by HFS filename normalizer; available to apps
static inline unsigned char MacToASCII(unsigned char uch) {
if (uch < 0x20)
return '?';
else if (uch < 0x80)
return uch;
else
return kMacHighASCII[uch - 0x80];
}
// Allow write access to physical disk 0. This is usually the boot disk,
// but with some BIOS the first IDE drive is always physical 0 even if
// you're booting from SATA. This only has meaning under Win32.
static void SetAllowWritePhys0(bool val);
/*
* Get string constants for enumerated values.
*/
typedef struct { int format; const char* str; } ToStringLookup;
static const char* ToStringCommon(int format, const ToStringLookup* pTable,
int tableSize);
static const char* ToString(OuterFormat format);
static const char* ToString(FileFormat format);
static const char* ToString(PhysicalFormat format);
static const char* ToString(SectorOrder format);
static const char* ToString(FSFormat format);
private:
/*
* Fundamental disk image identification.
*/
OuterFormat fOuterFormat; // e.g. gzip
FileFormat fFileFormat;
PhysicalFormat fPhysical;
const NibbleDescr* fpNibbleDescr; // only used for "nibble" images
SectorOrder fOrder; // only used for "sector" images
FSFormat fFormat;
/*
* This affects how the DiskImg responds to requests for reading
* a track or sector.
*
* "fFileSysOrder", together with with "fOrder", determines how
* sector numbers are translated. It describes the order that the
* DiskFS filesystem expects things to be in. If the image isn't
* sector-addressable, then it is assumed to be in linear block order.
*
* If "fSectorPairing" is set, the DiskImg treats the disk as if
* it were in OzDOS format, with one sector from two different
* volumes in a single 512-byte block.
*/
SectorOrder fFileSysOrder;
bool fSectorPairing;
int fSectorPairOffset; // which image (should be 0 or 1)
/*
* Internal state.
*/
GenericFD* fpOuterGFD; // outer wrapper, if any (.gz only)
GenericFD* fpWrapperGFD; // Apple II image file
GenericFD* fpDataGFD; // raw Apple II data
OuterWrapper* fpOuterWrapper; // needed for outer .gz wrapper
ImageWrapper* fpImageWrapper; // disk image wrapper (2MG, SHK, etc)
DiskImg* fpParentImg; // set for embedded volumes
short fDOSVolumeNum; // specified by some wrapper formats
di_off_t fOuterLength; // total len of file
di_off_t fWrappedLength; // len of file after Outer wrapper removed
di_off_t fLength; // len of disk image (w/o wrappers)
bool fExpandable; // ProDOS .hdv can expand
bool fReadOnly; // allow writes to this image?
bool fDirty; // have we modified this image?
//bool fIsEmbedded; // is this image embedded in another?
bool fHasSectors; // image is sector-addressable
bool fHasBlocks; // image is block-addressable
bool fHasNibbles; // image is nibble-addressable
long fNumTracks; // for sector-addressable images
int fNumSectPerTrack; // (ditto)
long fNumBlocks; // for 512-byte block-addressable images
unsigned char* fNibbleTrackBuf; // allocated on heap
int fNibbleTrackLoaded; // track currently in buffer
int fNuFXCompressType; // used when compressing a NuFX image
char* fNotes; // warnings and FYIs about DiskImg/DiskFS
LinearBitmap* fpBadBlockMap; // used for 3.5" nibble images
int fDiskFSRefCnt; // #of DiskFS objects pointing at us
/*
* NibbleDescr entries. There are several standard ones, and we want
* to allow applications to define additional ones.
*/
NibbleDescr* fpNibbleDescrTable;
int fNumNibbleDescrEntries;
/* static table of default values */
static const NibbleDescr kStdNibbleDescrs[];
DIError OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly);
DIError CreateImageCommon(const char* pathName, const char* storageName,
bool skipFormat);
DIError ValidateCreateFormat(void) const;
DIError FormatSectors(GenericFD* pGFD, bool quickFormat) const;
//DIError FormatBlocks(GenericFD* pGFD) const;
DIError CopyBytesOut(void* buf, di_off_t offset, int size) const;
DIError CopyBytesIn(const void* buf, di_off_t offset, int size);
DIError AnalyzeImageFile(const char* pathName, char fssep);
// Figure out the sector ordering for this filesystem, so we can decide
// how the sectors need to be re-arranged when we're reading them.
SectorOrder CalcFSSectorOrder(void) const;
// Handle sector order calculations.
DIError CalcSectorAndOffset(long track, int sector, SectorOrder ImageOrder,
SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector);
inline bool IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder);
/*
* Progress update during the filesystem scan. This only exists in the
* topmost DiskImg. (This is arguably more appropriate in DiskFS, but
* DiskFS objects don't have a notion of "parent" and are somewhat more
* ephemeral.)
*/
ScanProgressCallback fpScanProgressCallback;
void* fScanProgressCookie;
int fScanCount;
char fScanMsg[128];
time_t fScanLastMsgWhen;
/*
* 5.25" nibble image access.
*/
enum {
kDataSize62 = 343, // 342 bytes + checksum byte
kChunkSize62 = 86, // (0x56)
kDataSize53 = 411, // 410 bytes + checksum byte
kChunkSize53 = 51, // (0x33)
kThreeSize = (kChunkSize53 * 3) + 1, // same as 410 - 256
};
DIError ReadNibbleSector(long track, int sector, void* buf,
const NibbleDescr* pNibbleDescr);
DIError WriteNibbleSector(long track, int sector, const void* buf,
const NibbleDescr* pNibbleDescr);
void DumpNibbleDescr(const NibbleDescr* pNibDescr) const;
int GetNibbleTrackLength(long track) const;
int GetNibbleTrackOffset(long track) const;
int GetNibbleTrackFormatLength(void) const {
/* return length to use when formatting for 16 sectors */
if (fPhysical == kPhysicalFormatNib525_6656)
return kTrackLenNib525;
else if (fPhysical == kPhysicalFormatNib525_6384)
return kTrackLenNb2525;
else if (fPhysical == kPhysicalFormatNib525_Var) {
if (fFileFormat == kFileFormatTrackStar ||
fFileFormat == kFileFormatFDI)
{
return kTrackLenNb2525; // use minimum possible
}
}
assert(false);
return -1;
}
int GetNibbleTrackAllocLength(void) const {
/* return length to allocate when creating an image */
if (fPhysical == kPhysicalFormatNib525_Var &&
(fFileFormat == kFileFormatTrackStar ||
fFileFormat == kFileFormatFDI))
{
// use maximum possible
return kTrackLenTrackStar525;
}
return GetNibbleTrackFormatLength();
}
DIError LoadNibbleTrack(long track, long* pTrackLen);
DIError SaveNibbleTrack(void);
int FindNibbleSectorStart(const CircularBufferAccess& buffer,
int track, int sector, const NibbleDescr* pNibbleDescr, int* pVol);
void DecodeAddr(const CircularBufferAccess& buffer, int offset,
short* pVol, short* pTrack, short* pSector, short* pChksum);
inline unsigned int ConvFrom44(unsigned char val1, unsigned char val2) {
return ((val1 << 1) | 0x01) & val2;
}
DIError DecodeNibbleData(const CircularBufferAccess& buffer, int idx,
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibbleData(const CircularBufferAccess& buffer, int idx,
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
DIError DecodeNibble62(const CircularBufferAccess& buffer, int idx,
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibble62(const CircularBufferAccess& buffer, int idx,
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
DIError DecodeNibble53(const CircularBufferAccess& buffer, int idx,
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibble53(const CircularBufferAccess& buffer, int idx,
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
int TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, int* pVol);
DIError AnalyzeNibbleData(void);
inline unsigned char Conv44(unsigned short val, bool first) const {
if (first)
return (val >> 1) | 0xaa;
else
return val | 0xaa;
}
DIError FormatNibbles(GenericFD* pGFD) const;
static const unsigned char kMacHighASCII[];
/*
* 3.5" nibble access
*/
static int FindNextSector35(const CircularBufferAccess& buffer, int start,
int cyl, int head, int* pSector);
static bool DecodeNibbleSector35(const CircularBufferAccess& buffer,
int start, unsigned char* sectorBuf, unsigned char* readChecksum,
unsigned char* calcChecksum);
static bool UnpackChecksum35(const CircularBufferAccess& buffer,
int offset, unsigned char* checksumBuf);
static void EncodeNibbleSector35(const unsigned char* sectorData,
unsigned char* outBuf);
/* static data tables */
static unsigned char kDiskBytes53[32];
static unsigned char kDiskBytes62[64];
static unsigned char kInvDiskBytes53[256];
static unsigned char kInvDiskBytes62[256];
enum { kInvInvalidValue = 0xff };
private: // some C++ stuff to block behavior we don't support
DiskImg& operator=(const DiskImg&);
DiskImg(const DiskImg&);
};
/*
* Disk filesystem class, roughly equivalent to a GS/OS FST. This is an
* abstract base class.
*
* Static functions know how to access a DiskImg and figure out what kind
* of image we have. Once known, the appropriate sub-class can be
* instantiated.
*
* We maintain a linear list of files to make it easy for applications to
* traverse the full set of files. Sometimes this makes it hard for us to
* update internally (especially HFS). With some minor cleverness it
* should be possible to switch to a tree structure while retaining the
* linear "get next" API. This would be a big help for ProDOS and HFS.
*
* NEED: some notification mechanism for changes to files and/or block
* editing of the disk (especially with regard to open sub-volumes). If
* a disk volume open for file-by-file viewing is modified with the disk
* editor, we should close the file when the disk editor exits.
*
* NEED? disk utilities, such as "disk crunch", for Pascal volumes. Could
* be written externally, but might as well keep fs knowledge embedded.
*
* MISSING: there is no way to override the image analyzer when working
* with sub-volumes. Actually, there is; it just has to happen *after*
* the DiskFS has been created. We should provide an approach that either
* stifles the DiskFS creation, or allows us to override and replace the
* internal DiskFS so we can pop up a sub-volume list, show what we *think*
* is there, and then let the user pick a volume and pick overrides (mainly
* for use in the disk editor). Not sure if we want the changes to "stick";
* we probably do. Q: does the "scan for sub-volumes" attribute propagate
* recursively to each sub-sub-volume? Probably.
*
* NOTE to self: should make "test" functions more lax when called
* from here, on the assumption that the caller is knowledgeable. Perhaps
* an independent "strictness" variable that can be cranked down through
* multiple calls to AnalyzeImage??
*/
class DISKIMG_API DiskFS {
public:
/*
* Information about volume usage.
*
* Each "chunk" is a track/sector on a DOS disk or a block on a ProDOS
* or Pascal disk. CP/M really ought to use 1K blocks, but for
* convenience we're just using 512-byte blocks (it's up to the CP/M
* code to set two "chunks" per block).
*
* NOTE: the current DOS/ProDOS/Pascal code is sloppy when it comes to
* keeping this structure up to date. HFS doesn't use it at all. This
* has always been a low-priority feature.
*/
class DISKIMG_API VolumeUsage {
public:
VolumeUsage(void) {
fByBlocks = false;
fTotalChunks = -1;
fNumSectors = -1;
//fFreeChunks = -1;
fList = NULL;
fListSize = -1;
}
~VolumeUsage(void) {
delete[] fList;
}
/*
* These values MUST fit in five bits.
*
* Suggested disk map colors:
* 0 = unknown (color-key pink)
* 1 = conflict (medium-red)
* 2 = boot loader (dark-blue)
* 3 = volume dir (light-blue)
* 4 = subdir (medium-blue)
* 5 = user data (medium-green)
* 6 = user index blocks (light-green)
* 7 = embedded filesys (yellow)
*
* THOUGHT: Add flag for I/O error (nibble images) -- requires
* automatic disk verify pass. (Or maybe could be done manually
* on request?)
*
* unused --> black
* marked-used-but-not-used --> dark-red
* used-but-not-marked-used --> orange
*/
typedef enum {
kChunkPurposeUnknown = 0,
kChunkPurposeConflict = 1, // two or more different things
kChunkPurposeSystem = 2, // boot blocks, volume bitmap
kChunkPurposeVolumeDir = 3, // volume dir (or only dir)
kChunkPurposeSubdir = 4, // ProDOS sub-directory
kChunkPurposeUserData = 5, // file on this filesystem
kChunkPurposeFileStruct = 6, // index blocks, T/S lists
kChunkPurposeEmbedded = 7, // embedded filesystem
// how about: outside range claimed by disk, e.g. fTotalBlocks on
// 800K ProDOS disk in a 32MB CFFA volume?
} ChunkPurpose;
typedef struct ChunkState {
bool isUsed;
bool isMarkedUsed;
ChunkPurpose purpose; // only valid if isUsed is set
} ChunkState;
// initialize, configuring for either blocks or sectors
DIError Create(long numBlocks);
DIError Create(long numTracks, long numSectors);
bool GetInitialized(void) const { return (fList != NULL); }
// return the number of chunks on this disk
long GetNumChunks(void) const { return fTotalChunks; }
// return the number of unallocated chunks, taking into account
// both the free-chunk bitmap (if any) and the actual usage
long GetActualFreeChunks(void) const;
// return the state of the specified chunk
DIError GetChunkState(long block, ChunkState* pState) const;
DIError GetChunkState(long track, long sector,
ChunkState* pState) const;
// set the state of a particular chunk (should only be done by
// the DiskFS sub-classes)
DIError SetChunkState(long block, const ChunkState* pState);
DIError SetChunkState(long track, long sector,
const ChunkState* pState);
void Dump(void) const; // debugging
private:
DIError GetChunkStateIdx(int idx, ChunkState* pState) const;
DIError SetChunkStateIdx(int idx, const ChunkState* pState);
inline char StateToChar(ChunkState* pState) const;
/*
* Chunk state is stored as a set of bits in one byte:
*
* 0-4: how is block used (only has meaning if bit 6 is set)
* 5: for nibble images, indicates the block or sector is unreadable
* 6: is block used by something (0=no, 1=yes)
* 7: is block marked "in use" by system map (0=no, 1=yes)
*
* [ Consider reducing "purpose" to 0-3 and adding bad block bit for
* nibble images and physical media.]
*/
enum {
kChunkPurposeMask = 0x1f, // ChunkPurpose enum
kChunkDamagedFlag = 0x20,
kChunkUsedFlag = 0x40,
kChunkMarkedUsedFlag = 0x80,
};
bool fByBlocks;
long fTotalChunks;
long fNumSectors; // only valid if !fByBlocks
//long fFreeChunks;
unsigned char* fList;
int fListSize;
}; // end of VolumeUsage class
/*
* List of sub-volumes. The SubVolume owns the DiskImg and DiskFS
* that are handed to it, so they can be deleted when the SubVolume
* is deleted as part of destroying the parent.
*/
class SubVolume {
public:
SubVolume(void) : fpDiskImg(NULL), fpDiskFS(NULL),
fpPrev(NULL), fpNext(NULL) {}
~SubVolume(void) {
delete fpDiskFS; // must close first; may need flush to DiskImg
delete fpDiskImg;
}
void Create(DiskImg* pDiskImg, DiskFS* pDiskFS) {
assert(pDiskImg != NULL);
assert(pDiskFS != NULL);
fpDiskImg = pDiskImg;
fpDiskFS = pDiskFS;
}
DiskImg* GetDiskImg(void) const { return fpDiskImg; }
DiskFS* GetDiskFS(void) const { return fpDiskFS; }
SubVolume* GetPrev(void) const { return fpPrev; }
void SetPrev(SubVolume* pSubVol) { fpPrev = pSubVol; }
SubVolume* GetNext(void) const { return fpNext; }
void SetNext(SubVolume* pSubVol) { fpNext = pSubVol; }
private:
DiskImg* fpDiskImg;
DiskFS* fpDiskFS;
SubVolume* fpPrev;
SubVolume* fpNext;
}; // end of SubVolume class
/*
* Start of DiskFS declarations.
*/
public:
typedef enum SubScanMode {
kScanSubUnknown = 0,
kScanSubDisabled,
kScanSubEnabled,
kScanSubContainerOnly,
} SubScanMode;
DiskFS(void) {
fpA2Head = fpA2Tail = NULL;
fpSubVolumeHead = fpSubVolumeTail = NULL;
fpImg = NULL;
fScanForSubVolumes = kScanSubDisabled;
fParmTable[kParm_CreateUnique] = 0;
fParmTable[kParmProDOS_AllowLowerCase] = 1;
fParmTable[kParmProDOS_AllocSparse] = 1;
}
virtual ~DiskFS(void) {
DeleteSubVolumeList();
DeleteFileList();
SetDiskImg(NULL);
}
/*
* Static FSFormat-analysis functions, called by the DiskImg AnalyzeImage
* function. Capable of determining with a high degree of accuracy
* what format the disk is in, yet remaining flexible enough to
* function correctly with variations (like DOS3.3 disks with
* truncated TOCs and ProDOS images from hard drives).
*
* The "pOrder" and "pFormat" arguments are in/out. Set them to the
* appropriate "unknown" enum value on entry. If something is known
* of the order or format, put that in instead; in some cases this will
* bias the proceedings, which is useful for efficiency and for making
* overrides work correctly.
*
* On success, these return kDIErrNone and set "*pOrder". On failure,
* they return nonzero and leave "*pOrder" unmodified.
*
* Each DiskFS sub-class should declare a TestFS function. It's not
* virtual void here because, since it's called before the DiskFS is
* created, it must be static.
*/
typedef enum FSLeniency { kLeniencyNot, kLeniencyVery } FSLeniency;
//static DIError TestFS(const DiskImg* pImg, DiskImg::SectorOrder* pOrder,
// DiskImg::FSFormat* pFormat, FSLeniency leniency);
/*
* Load the disk contents and (if enabled) scan for sub-volumes.
*
* If "headerOnly" is set, we just do a quick scan of the volume header
* to get basic information. The deep file scan is skipped (but can
* be done later). (Sub-classes can choose to ignore the flag and
* always do the full scan; this is an optimization.) Guaranteed to
* set the volume name and volume block/sector count.
*
* If a progress callback is set up, this can return with a "cancelled"
* result, which should not be treated as a failure.
*/
typedef enum { kInitUnknown = 0, kInitHeaderOnly, kInitFull } InitMode;
virtual DIError Initialize(DiskImg* pImg, InitMode initMode) = 0;
/*
* Format the disk with the appropriate filesystem, creating all filesystem
* structures and (when appropriate) boot blocks.
*/
virtual DIError Format(DiskImg* pDiskImg, const char* volName)
{ return kDIErrNotSupported; }
/*
* Pass in a full path to normalize, and a buffer to copy the output
* into. On entry "pNormalizedBufLen" holds the length of the buffer.
* On exit, it holds the size of the buffer required to hold the
* normalized string. If the buffer is NULL or isn't big enough, no part
* of the normalized path will be copied into the buffer, and a specific
* error (kDIErrDataOverrun) will be returned.
*/
virtual DIError NormalizePath(const char* path, char fssep,
char* normalizedBuf, int* pNormalizedBufLen)
{ return kDIErrNotSupported; }
/*
* Create a file. The CreateParms struct specifies the full set of file
* details. To remain FS-agnostic, use the NufxLib constants
* (kNuStorageDirectory, kNuAccessUnlocked, etc). They match up with
* their ProDOS equivalents, and I promise to make them work right.
*
* On success, the file exists as a fully-formed, zero-length file. A
* pointer to the relevant A2File structure is returned.
*/
enum {
/* valid values for CreateParms; must match ProDOS enum */
kStorageSeedling = 1,
kStorageExtended = 5,
kStorageDirectory = 13,
};
typedef struct CreateParms {
const char* pathName; // full pathname for file on disk image
char fssep;
int storageType; // determines normal, subdir, or forked
long fileType;
long auxType;
int access;
time_t createWhen;
time_t modWhen;
} CreateParms;
virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile)
{ return kDIErrNotSupported; }
/*
* Delete a file from the disk.
*/
virtual DIError DeleteFile(A2File* pFile)
{ return kDIErrNotSupported; }
/*
* Rename a file.
*/
virtual DIError RenameFile(A2File* pFile, const char* newName)
{ return kDIErrNotSupported; }
/*
* Alter file attributes.
*/
virtual DIError SetFileInfo(A2File* pFile, long fileType, long auxType,
long accessFlags)
{ return kDIErrNotSupported; }
/*
* Rename a volume. Also works for changing the disk volume number.
*/
virtual DIError RenameVolume(const char* newName)
{ return kDIErrNotSupported; }
// Accessor
DiskImg* GetDiskImg(void) const { return fpImg; }
// Test file and volume names (and volume numbers)
// [these need to be static functions for some things... hmm]
//virtual bool IsValidFileName(const char* name) const { return false; }
//virtual bool IsValidVolumeName(const char* name) const { return false; }
// Return the disk volume name, or NULL if there isn't one.
virtual const char* GetVolumeName(void) const = 0;
// Return a printable string identifying the FS type and volume
virtual const char* GetVolumeID(void) const = 0;
// Return the "bare" volume name. For formats where the volume name
// is actually a number (e.g. DOS 3.3), this returns just the number.
// For formats without a volume name or number (e.g. CP/M), this returns
// NULL, indicating that any attempt to change the volume name will fail.
virtual const char* GetBareVolumeName(void) const = 0;
// Returns "false" if we only support read-only access to this FS type
virtual bool GetReadWriteSupported(void) const = 0;
// Returns "true" if the filesystem shows evidence of damage.
virtual bool GetFSDamaged(void) const = 0;
// Returns number of blocks recognized by the filesystem, or -1 if the
// FS isn't block-oriented (e.g. DOS 3.2/3.3)
virtual long GetFSNumBlocks(void) const { return -1; }
// Get the next file in the list. Start by passing in NULL to get the
// head of the list. Returns NULL when the end of the list is reached.
A2File* GetNextFile(A2File* pCurrent) const;
// Get a count of the files and directories on this disk.
long GetFileCount(void) const;
/*
* Find a file by case-insensitive pathname. Assumes fssep=':'. The
* compare function can be overridden for systems like HFS, where "case
* insensitive" has a different meaning because of the native
* character set.
*
* The A2File* returned should not be deleted.
*/
typedef int (*StringCompareFunc)(const char* str1, const char* str2);
A2File* GetFileByName(const char* pathName, StringCompareFunc func = NULL);
// This controls scanning for sub-volumes; must be set before Initialize().
SubScanMode GetScanForSubVolumes(void) const { return fScanForSubVolumes; }
void SetScanForSubVolumes(SubScanMode val) { fScanForSubVolumes = val; }
// some constants for non-ProDOS filesystems to use
enum { kFileAccessLocked = 0x01, kFileAccessUnlocked = 0xc3 };
/*
* We use this as a filename separator character (i.e. between pathname
* components) in all filenames. It's useful to standardize on this
* so that behavior is consistent across all disk and archive formats.
*
* The choice of ':' is good because it's invalid in filenames on
* Windows, Mac OS, GS/OS, and pretty much anywhere else we could be
* running except for UNIX. It's valid under DOS 3.3, but since you
* can't have subdirectories under DOS there's no risk of confusion.
*/
enum { kDIFssep = ':' };
/*
* Return the volume use map. This is a non-const function because
* it might need to do a "just-in-time" usage map update. It returns
* const to keep non-DiskFS classes from altering the map.
*/
const VolumeUsage* GetVolumeUsageMap(void) {
if (fVolumeUsage.GetInitialized())
return &fVolumeUsage;
else
return NULL;
}
/*
* Return the total space and free space, in either blocks or sectors
* as appropriate. "*pUnitSize" will be kBlockSize or kSectorSize.
*/
virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
int* pUnitSize) const = 0;
/*
* Get the next volume in the list. Start by passing in NULL to get the
* head of the list. Returns NULL when the end of the list is reached.
*/
SubVolume* GetNextSubVolume(const SubVolume* pCurrent) const;
/*
* Set some parameters to tell the DiskFS how to operate. Some of
* these are FS-specific, some may be general.
*
* Parameters are set in the current object and all sub-volume objects.
*
* The enum is part of the interface and must be rigidly defined, but
* it is also used to size an array so it can't be too sparse.
*/
typedef enum DiskFSParameter {
kParmUnknown = 0,
kParm_CreateUnique = 1, // make new filenames unique
kParmProDOS_AllowLowerCase = 10, // allow lower case and spaces
kParmProDOS_AllocSparse = 11, // don't store empty blocks
kParmMax // must be last entry
} DiskFSParameter;
long GetParameter(DiskFSParameter parm);
void SetParameter(DiskFSParameter parm, long val);
/*
* Flush changed data.
*
* The individual filesystems shouldn't generally do any caching; if
* they do, we would want a virtual "FlushFS()" that gets called by
* Flush. The better answer is to cache in DiskImg, which works for
* everything, and knows if the underlying storage is already in RAM.
*
* For the most part this just needs to recursively flush the DiskImg
* objects in all of the sub-volumes and then the current volume. This
* is a no-op in most cases, but if the archive is compressed this will
* cause a new, compressed archive to be created.
*
* The "mode" value determines whether or not we do "heavy" flushes. It's
* very handy to be able to do "slow" flushes for anything that is being
* written directly to disk (as opposed to being run through Deflate),
* so that the UI doesn't indicate that they're partially written when
* in fact they're fully written.
*/
DIError Flush(DiskImg::FlushMode mode);
/*
* Set the read-only flag on our DiskImg and those of our subvolumes.
* Used to ensure that a DiskFS with un-flushed data can be deleted
* without corrupting the volume.
*/
void SetAllReadOnly(bool val);
// debug dump
void DumpFileList(void);
protected:
/*
* Set the DiskImg pointer. Updates the reference count in DiskImg.
*/
void SetDiskImg(DiskImg* pImg);
// once added, we own the pDiskImg and the pDiskFS (DO NOT pass the
// same DiskImg or DiskFS in more than once!). Note this copies the
// fParmTable and other stuff (fScanForSubVolumes) from parent to child.
void AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS);
// add files to fpA2Head/fpA2Tail
void AddFileToList(A2File* pFile);
// only need for hierarchical filesystems; insert file after pPrev
void InsertFileInList(A2File* pFile, A2File* pPrev);
// delete an entry
void DeleteFileFromList(A2File* pFile);
// scan for damaged or suspicious files
void ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious);
// pointer to the DiskImg structure underlying this filesystem
DiskImg* fpImg;
VolumeUsage fVolumeUsage;
SubScanMode fScanForSubVolumes;
private:
A2File* SkipSubdir(A2File* pSubdir);
void CopyInheritables(DiskFS* pNewFS);
void DeleteFileList(void);
void DeleteSubVolumeList(void);
long fParmTable[kParmMax]; // for DiskFSParameter
A2File* fpA2Head;
A2File* fpA2Tail;
SubVolume* fpSubVolumeHead;
SubVolume* fpSubVolumeTail;
private:
DiskFS& operator=(const DiskFS&);
DiskFS(const DiskFS&);
};
/*
* Apple II file class, representing a file on an Apple II volume. This is an
* abstract base class.
*
* There is a different sub-class for each filesystem type. The A2File object
* encapsulates all of the knowledge required to read a file from a disk
* image.
*
* The prev/next pointers, used to maintain a linked list of files, are only
* accessible from DiskFS functions. At some point we may want to rearrange
* the way this is handled, e.g. by not maintaining a list at all, so it's
* important that everything go through DiskFS requests.
*
* The FSFormat is made an explicit member, because sub-classes may not
* understand exactly where the file came from (e.g. was it DOS3.2 or
* DOS 3.3). Somebody might care.
*
*
* NOTE TO SELF: open files need to be generalized. Right now the internal
* implementations only allow a single open, which is okay for our purposes
* but bad for a general FS implementation. As it stands, you can't even
* open both forks at the same time on ProDOS/HFS.
*
* UMMM: The handling of "damaged" files could be more consistent.
*/
class DISKIMG_API A2File {
public:
friend class DiskFS;
A2File(DiskFS* pDiskFS) : fpDiskFS(pDiskFS) {
fpPrev = fpNext = NULL;
fFileQuality = kQualityGood;
}
virtual ~A2File(void) {}
/*
* All Apple II files have certain characteristics, of which ProDOS
* is roughly a superset. (Yes, you can have HFS on a IIgs, but
* all that fancy creator type stuff is decidedly Mac-centric. Still,
* we want to assume 4-byte file and aux types.)
*
* NEED: something distinguishing between files and disk images?
*
* NOTE: there is no guarantee that GetPathName will return unique values
* (duplicates are possible). We don't guarantee that you won't get an
* empty string back (it's valid to have an empty filename in the dir in
* DOS 3.3, and it's possible for other filesystems to be damaged). The
* pathname may receive some minor sanitizing, e.g. removal or conversion
* of high ASCII and control characters, but some filesystems (like HFS)
* make use of them.
*
* We do guarantee that the contents of subdirectories are grouped
* together. This makes it much easier to construct a hierarchy out of
* the linear list. This becomes important when dynamically adding
* files to a disk.
*/
virtual const char* GetFileName(void) const = 0; // name of this file
virtual const char* GetPathName(void) const = 0; // full path
virtual char GetFssep(void) const = 0; // '\0' if none
virtual long GetFileType(void) const = 0;
virtual long GetAuxType(void) const = 0;
virtual long GetAccess(void) const = 0; // ProDOS-style perms
virtual time_t GetCreateWhen(void) const = 0;
virtual time_t GetModWhen(void) const = 0;
virtual di_off_t GetDataLength(void) const = 0; // len of data fork
virtual di_off_t GetDataSparseLength(void) const = 0; // len w/o sparse areas
virtual di_off_t GetRsrcLength(void) const = 0; // len or -1 if no rsrc
virtual di_off_t GetRsrcSparseLength(void) const = 0; // len or -1 if no rsrc
virtual DiskImg::FSFormat GetFSFormat(void) const {
return fpDiskFS->GetDiskImg()->GetFSFormat();
}
virtual bool IsDirectory(void) const { return false; }
virtual bool IsVolumeDirectory(void) const { return false; }
/*
* Open a file. Treat the A2FileDescr like an fd.
*/
virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork = false) = 0;
/*
* This is called by the A2FileDescr object when somebody invokes Close().
* The A2File object should remove the A2FileDescr from its list of open
* files and delete the storage. The implementation must not call the
* A2FileDescr's Close function, since that would cause a recursive loop.
*
* This should not be called by an application.
*/
virtual void CloseDescr(A2FileDescr* pOpenFile) = 0;
/*
* This is only useful for hierarchical filesystems like ProDOS,
* where the order of items in the linear list is significant. It
* allows an unambiguous determination of which subdir a file resides
* in, even if somebody has sector-edited the filesystem so that two
* subdirs have the same name. (It's also a bit speedier to compare
* than pathname substrings would be.)
*/
virtual A2File* GetParent(void) const { return NULL; }
/*
* Returns "true" if either fork of the file is open, "false" if not.
*/
virtual bool IsFileOpen(void) const = 0;
virtual void Dump(void) const = 0; // debugging
typedef enum FileQuality {
kQualityUnknown = 0,
kQualityGood,
kQualitySuspicious,
kQualityDamaged,
} FileQuality;
virtual FileQuality GetQuality(void) const { return fFileQuality; }
virtual void SetQuality(FileQuality quality);
virtual void ResetQuality(void);
DiskFS* GetDiskFS(void) const { return fpDiskFS; }
protected:
DiskFS* fpDiskFS;
virtual void SetParent(A2File* pParent) { /* do nothing */ }
private:
A2File* GetPrev(void) const { return fpPrev; }
void SetPrev(A2File* pFile) { fpPrev = pFile; }
A2File* GetNext(void) const { return fpNext; }
void SetNext(A2File* pFile) { fpNext = pFile; }
// Set when file structure is damaged and application should not try
// to open the file.
FileQuality fFileQuality;
A2File* fpPrev;
A2File* fpNext;
private:
A2File& operator=(const A2File&);
A2File(const A2File&);
};
/*
* Abstract representation of an open file. This contains all active state
* and all information required to read and write a file.
*
* Do not delete these objects; instead, invoke the Close method, so that they
* can be removed from the parents' list of open files.
* TODO: consider making the destructor "protected"
*/
class DISKIMG_API A2FileDescr {
public:
A2FileDescr(A2File* pFile) : fpFile(pFile), fProgressUpdateFunc(NULL) {}
virtual ~A2FileDescr(void) { fpFile = NULL; /*paranoia*/ }
virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) = 0;
virtual DIError Write(const void* buf, size_t len, size_t* pActual = NULL) = 0;
virtual DIError Seek(di_off_t offset, DIWhence whence) = 0;
virtual di_off_t Tell(void) = 0;
virtual DIError Close(void) = 0;
virtual DIError GetStorage(long sectorIdx, long* pTrack, long* pSector)
const = 0;
virtual DIError GetStorage(long blockIdx, long* pBlock)
const = 0;
virtual long GetSectorCount(void) const = 0;
virtual long GetBlockCount(void) const = 0;
virtual DIError Rewind(void) { return Seek(0, kSeekSet); }
A2File* GetFile(void) const { return fpFile; }
/*
* Progress update callback mechanism. Pass in the length or (for writes)
* expected length of the file. This invokes the callback with the
* lengths and some pointers.
*
* If the progress callback returns "true", progress continues. If it
* returns "false", the read or write function will return with
* kDIErrCancelled.
*/
typedef bool (*ProgressUpdater)(A2FileDescr* pFile, di_off_t max,
di_off_t current, void* vState);
void SetProgressUpdater(ProgressUpdater func, di_off_t max, void* state) {
fProgressUpdateFunc = func;
fProgressUpdateMax = max;
fProgressUpdateState = state;
}
void ClearProgressUpdater(void) {
fProgressUpdateFunc = NULL;
}
protected:
A2File* fpFile;
/*
* Internal utility functions for mapping blocks to sectors and vice-versa.
*/
virtual void TrackSectorToBlock(long track, long sector, long* pBlock,
bool* pSecondHalf) const
{
int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack();
assert(track < fpFile->GetDiskFS()->GetDiskImg()->GetNumTracks());
assert(sector < numSectPerTrack);
long dblBlock = track * numSectPerTrack + sector;
*pBlock = dblBlock / 2;
*pSecondHalf = (dblBlock & 0x01) != 0;
}
virtual void BlockToTrackSector(long block, bool secondHalf, long* pTrack,
long* pSector) const
{
assert(block < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack();
int dblBlock = block * 2;
if (secondHalf)
dblBlock++;
*pTrack = dblBlock / numSectPerTrack;
*pSector = dblBlock % numSectPerTrack;
}
/*
* Call this from FS-specific read/write functions on successful
* completion (and perhaps more often for filesystems with potentially
* large files, e.g. ProDOS/HFS).
*
* Test the return value; if "false", user wishes to cancel the op, and
* long read or write calls should return immediately.
*/
inline bool UpdateProgress(di_off_t current) {
if (fProgressUpdateFunc != NULL) {
return (*fProgressUpdateFunc)(this, fProgressUpdateMax, current,
fProgressUpdateState);
} else {
return true;
}
}
private:
A2FileDescr& operator=(const A2FileDescr&);
A2FileDescr(const A2FileDescr&);
/* storage for progress update goodies */
ProgressUpdater fProgressUpdateFunc;
di_off_t fProgressUpdateMax;
void* fProgressUpdateState;
};
}; // namespace DiskImgLib
#endif /*DISKIMG_DISKIMG_H*/