ciderpress/diskimg/DiskImg.h
2022-08-07 12:53:29 -07:00

1687 lines
69 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 <sys/types.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 5
#define kDiskImgVersionMinor 0
#define kDiskImgVersionBug 1
/*
* 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 */
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(int32_t* pMajor, int32_t* pMinor, int32_t* 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?
*/
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?)
uint8_t addrProlog[kNibbleAddrPrologLen];
uint8_t addrEpilog[kNibbleAddrEpilogLen];
uint8_t addrChecksumSeed;
bool addrVerifyChecksum;
bool addrVerifyTrack;
int addrEpilogVerifyCount;
uint8_t dataProlog[kNibbleDataPrologLen];
uint8_t dataEpilog[kNibbleDataEpilogLen];
uint8_t 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, uint8_t* buf,
long* pTrackLen);
// write a track; trackLen must be <= those in image
virtual DIError WriteNibbleTrack(long track, const uint8_t* 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 */
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 uint8_t* nibbleBuf,
long nibbleLen, uint8_t* 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 uint8_t MacToASCII(uint8_t 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
uint8_t* 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 uint16_t ConvFrom44(uint8_t val1, uint8_t val2) {
return ((val1 << 1) | 0x01) & val2;
}
DIError DecodeNibbleData(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibbleData(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
DIError DecodeNibble62(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibble62(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
DIError DecodeNibble53(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
void EncodeNibble53(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
int TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, int* pVol);
DIError AnalyzeNibbleData(void);
inline uint8_t Conv44(uint16_t val, bool first) const {
if (first)
return (val >> 1) | 0xaa;
else
return val | 0xaa;
}
DIError FormatNibbles(GenericFD* pGFD) const;
static const uint8_t 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, uint8_t* sectorBuf, uint8_t* readChecksum,
uint8_t* calcChecksum);
static bool UnpackChecksum35(const CircularBufferAccess& buffer,
int offset, uint8_t* checksumBuf);
static void EncodeNibbleSector35(const uint8_t* sectorData,
uint8_t* outBuf);
/* static data tables */
static uint8_t kDiskBytes53[32];
static uint8_t kDiskBytes62[64];
static uint8_t kInvDiskBytes53[256];
static uint8_t 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;
uint8_t* 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.
*
* Generally speaking, the normalization process involves separating
* the pathname into its components, modifying each filename as needed
* to make it legal on the current filesystem, and then reassembling
* the components.
*
* The input and output strings are Mac OS Roman.
*/
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
uint32_t fileType;
uint32_t auxType;
uint32_t 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, uint32_t fileType,
uint32_t auxType, uint32_t 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 filename returned is defined to be null-terminated Mac OS Roman.
* For most filesystems this is automatic, as filenames are restricted
* to ASCII, but for DOS 3.3 it requires stripping high bits. It also
* means that embedded nulls will be lost.
*
* The original unmodified filename is available through GetRawFileName,
* which can be optionally passed a size_t pointer to get the size
* of the raw file name, in case it has embedded nulls.
*
* 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 const char* GetRawFileName(size_t* size = NULL) const; // get unmodified file name
virtual char GetFssep(void) const = 0; // '\0' if none
virtual uint32_t GetFileType(void) const = 0;
virtual uint32_t GetAuxType(void) const = 0;
virtual uint32_t 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),
fProgressUpdateMax(0),
fProgressUpdateState(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*/