building ciderpress libraries with cmake

This commit is contained in:
Shawn Quick 2023-02-04 15:34:50 -08:00
parent e74f463a1e
commit 9c66992c23
164 changed files with 104484 additions and 51 deletions

1
app_config.h Symbolic link
View File

@ -0,0 +1 @@
config.h

View File

@ -862,15 +862,8 @@ static NuError Nu_OpenTempFile(UNICHAR* fileNameUNI, FILE** pFp)
#else
char* result;
#if 1
DBUG(("+++ Using mktemp\n"));
result = mktemp(fileNameUNI);
#else
char fbuff[256];
sprintf(fbuff,"%s%s",fileNameUNI,"XXXXXX");
result=mkstemp();
#endif
if (result == NULL) {
Nu_ReportError(NU_BLOB, kNuErrNone, "mktemp failed on '%s'",
fileNameUNI);

View File

@ -0,0 +1,29 @@
Copyright (C) 2007, Andy McFadden.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of project
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

183
ciderpress/nufxlib/INSTALL Normal file
View File

@ -0,0 +1,183 @@
Basic Installation
==================
These are generic installation instructions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, a file
`config.cache' that saves the results of its tests to speed up
reconfiguring, and a file `config.log' containing compiler output
(useful mainly for debugging `configure').
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If at some point `config.cache'
contains results you don't want to keep, you may remove or edit it.
The file `configure.in' is used to create `configure' by a program
called `autoconf'. You only need `configure.in' if you want to change
it or regenerate `configure' using a newer version of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system. If you're
using `csh' on an old version of System V, you might need to type
`sh ./configure' instead to prevent `csh' from trying to execute
`configure' itself.
Running `configure' takes awhile. While running, it prints some
messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package.
4. Type `make install' to install the programs and any data files and
documentation.
5. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. You can give `configure'
initial values for variables by setting them in the environment. Using
a Bourne-compatible shell, you can do that on the command line like
this:
CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
Or on systems that have the `env' program, you can do it like this:
env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you must use a version of `make' that
supports the `VPATH' variable, such as GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'.
If you have to use a `make' that does not supports the `VPATH'
variable, you have to compile the package for one architecture at a time
in the source code directory. After you have installed the package for
one architecture, use `make distclean' before reconfiguring for another
architecture.
Installation Names
==================
By default, `make install' will install the package's files in
`/usr/local/bin', `/usr/local/man', etc. You can specify an
installation prefix other than `/usr/local' by giving `configure' the
option `--prefix=PATH'.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
give `configure' the option `--exec-prefix=PATH', the package will use
PATH as the prefix for installing programs and libraries.
Documentation and other data files will still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=PATH' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them.
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Optional Features
=================
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Specifying the System Type
==========================
There may be some features `configure' can not figure out
automatically, but needs to determine by the type of host the package
will run on. Usually `configure' can figure that out, but if it prints
a message saying it can not guess the host type, give it the
`--host=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name with three fields:
CPU-COMPANY-SYSTEM
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the host type.
If you are building compiler tools for cross-compiling, you can also
use the `--target=TYPE' option to select the type of system they will
produce code for and the `--build=TYPE' option to select the type of
system on which you are compiling the package.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Operation Controls
==================
`configure' recognizes the following options to control how it
operates.
`--cache-file=FILE'
Use and save the results of the tests in FILE instead of
`./config.cache'. Set FILE to `/dev/null' to disable caching, for
debugging `configure'.
`--help'
Print a summary of the options to `configure', and exit.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--version'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`configure' also accepts some other, not widely useful, options.

View File

@ -1,15 +1,141 @@
.SILENT:
export CC=gcc
export CXX=g++
#
# NuFX archive manipulation library
# Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
# This is free software; you can redistribute it and/or modify it under the
# terms of the BSD License, see the file COPYING-LIB.
#
# Makefile for nufxlib (should work with non-GNU "make").
#
# You can use:
# make (builds library and sample applications)
# make shared (builds shared library if you're using GNU ld or similar)
#
# The shared library support currently leaves much to be desired.
#
# If you build with -DDEBUG_MSGS, nulib2 will be able to use the hidden
# 'g' command, which generates a verbose archive dump for debugging.
#
.PHONY: clean
# NufxLib install location.
prefix = /usr/local
exec_prefix = ${prefix}
includedir = ${prefix}/include
libdir = ${exec_prefix}/lib
srcdir = .
all: nufx
SHELL = /bin/sh
INSTALL = /usr/bin/install -c
INSTALL_PROGRAM = ${INSTALL}
INSTALL_DATA = ${INSTALL} -m 644
CC = gcc
AR = ar rcv
#OPT = -g -O2 -DNDEBUG
OPT = -g -O2
#OPT = -g -O2 -DDEBUG_MSGS
#OPT = -g -O2 -DDEBUG_VERBOSE
GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow
CFLAGS = $(OPT) $(GCC_FLAGS) -I. -DHAVE_CONFIG_H -DOPTFLAGSTR="\"$(OPT)\""
nufx:
-mkdir -p ./build
cd ./build && cmake ..
cd ./build && $(MAKE)
SRCS = Archive.c ArchiveIO.c Bzip2.c Charset.c Compress.c Crc16.c \
Debug.c Deferred.c Deflate.c Entry.c Expand.c FileIO.c Funnel.c \
Lzc.c Lzw.c MiscStuff.c MiscUtils.c Record.c SourceSink.c \
Squeeze.c Thread.c Value.c Version.c
OBJS = Archive.o ArchiveIO.o Bzip2.o Charset.o Compress.o Crc16.o \
Debug.o Deferred.o Deflate.o Entry.o Expand.o FileIO.o Funnel.o \
Lzc.o Lzw.o MiscStuff.o MiscUtils.o Record.o SourceSink.o \
Squeeze.o Thread.o Value.o Version.o
STATIC_PRODUCT = libnufx.a
SHARED_PRODUCT = libnufx.so
PRODUCT = $(STATIC_PRODUCT)
#
# Build stuff
#
all: $(PRODUCT) samples
@true
install: $(STATIC_PRODUCT)
$(srcdir)/mkinstalldirs $(libdir)
$(INSTALL_DATA) $(STATIC_PRODUCT) $(libdir)
$(srcdir)/mkinstalldirs $(includedir)
$(INSTALL_DATA) NufxLib.h $(includedir)
install-shared: $(SHARED_PRODUCT)
$(srcdir)/mkinstalldirs $(libdir)
$(INSTALL_DATA) $(SHARED_PRODUCT) $(libdir)
$(srcdir)/mkinstalldirs $(includedir)
$(INSTALL_DATA) NufxLib.h $(includedir)
samples::
@echo "Building samples..."
@(cd samples; set +e; unset CFLAGS OBJS; set -e; \
LIB_PRODUCT="../$(PRODUCT)" $(MAKE))
shared::
PRODUCT="$(SHARED_PRODUCT)" $(MAKE) -e
$(STATIC_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
$(AR) $@ $(OBJS)
ranlib $@
# BUG: we need -fPIC, maybe -D_REENTRANT when compiling for this.
# BUG: for Linux we may want -Wl,-soname,libnufx.so.1 on the link line.
$(SHARED_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
$(CC) -shared -o $@ $(OBJS) -lz
clean:
-rm -rf ./build
(cd samples; make clean)
-rm -f *.o core
-rm -f $(SHARED_PRODUCT) $(STATIC_PRODUCT)
# build tags; assumes fancy GNU tag generation
tags::
@ctags -R --totals *
@#ctags *.[ch]
distclean: clean
(cd samples; make distclean)
-rm -f Makefile Makefile.bak
-rm -f config.log config.cache config.status config.h
-rm -f tags
# Make a tarfile with a backup of the essential files. We include "Makefile"
# so that we can do a "make distclean" during packaging.
baktar:
@tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \
Makefile.msc Makefile.dll install-sh config.guess config.sub \
mkinstalldirs *.[ch] samples/*.txt samples/Makefile* samples/*.[ch]
@gzip -9 nufxlib.tar
@mv -i nufxlib.tar.gz /home/fadden/BAK/
# dependency info
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
Archive.o: Archive.c $(COMMON_HDRS)
ArchiveIO.o: ArchiveIO.c $(COMMON_HDRS)
Bzip2.o: Bzip2.c $(COMMON_HDRS)
Charset.o: Charset.c $(COMMON_HDRS)
Compress.o: Compress.c $(COMMON_HDRS)
Crc16.o: Crc16.c $(COMMON_HDRS)
Debug.o: Debug.c $(COMMON_HDRS)
Deferred.o: Deferred.c $(COMMON_HDRS)
Deflate.o: Deflate.c $(COMMON_HDRS)
Entry.o: Entry.c $(COMMON_HDRS)
Expand.o: Expand.c $(COMMON_HDRS)
FileIO.o: FileIO.c $(COMMON_HDRS)
Funnel.o: Funnel.c $(COMMON_HDRS)
Lzc.o: Lzc.c $(COMMON_HDRS)
Lzw.o: Lzw.c $(COMMON_HDRS)
MiscStuff.o: MiscStuff.c $(COMMON_HDRS)
MiscUtils.o: MiscUtils.c $(COMMON_HDRS)
Record.o: Record.c $(COMMON_HDRS)
SourceSink.o: SourceSink.c $(COMMON_HDRS)
Squeeze.o: Squeeze.c $(COMMON_HDRS)
Thread.o: Thread.c $(COMMON_HDRS)
Value.o: Value.c $(COMMON_HDRS)
Version.o: Version.c $(COMMON_HDRS) Makefile

View File

@ -0,0 +1,141 @@
#
# NuFX archive manipulation library
# Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
# This is free software; you can redistribute it and/or modify it under the
# terms of the BSD License, see the file COPYING-LIB.
#
# Makefile for nufxlib (should work with non-GNU "make").
#
# You can use:
# make (builds library and sample applications)
# make shared (builds shared library if you're using GNU ld or similar)
#
# The shared library support currently leaves much to be desired.
#
# If you build with -DDEBUG_MSGS, nulib2 will be able to use the hidden
# 'g' command, which generates a verbose archive dump for debugging.
#
# NufxLib install location.
prefix = @prefix@
exec_prefix = @exec_prefix@
includedir = @includedir@
libdir = @libdir@
srcdir = @srcdir@
SHELL = @SHELL@
INSTALL = @INSTALL@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_DATA = @INSTALL_DATA@
CC = @CC@
AR = ar rcv
#OPT = @CFLAGS@ -DNDEBUG
OPT = @CFLAGS@
#OPT = @CFLAGS@ -DDEBUG_MSGS
#OPT = @CFLAGS@ -DDEBUG_VERBOSE
GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow
CFLAGS = @BUILD_FLAGS@ -I. @DEFS@ -DOPTFLAGSTR="\"$(OPT)\""
SRCS = Archive.c ArchiveIO.c Bzip2.c Charset.c Compress.c Crc16.c \
Debug.c Deferred.c Deflate.c Entry.c Expand.c FileIO.c Funnel.c \
Lzc.c Lzw.c MiscStuff.c MiscUtils.c Record.c SourceSink.c \
Squeeze.c Thread.c Value.c Version.c
OBJS = Archive.o ArchiveIO.o Bzip2.o Charset.o Compress.o Crc16.o \
Debug.o Deferred.o Deflate.o Entry.o Expand.o FileIO.o Funnel.o \
Lzc.o Lzw.o MiscStuff.o MiscUtils.o Record.o SourceSink.o \
Squeeze.o Thread.o Value.o Version.o
STATIC_PRODUCT = libnufx.a
SHARED_PRODUCT = libnufx.so
PRODUCT = $(STATIC_PRODUCT)
#
# Build stuff
#
all: $(PRODUCT) samples
@true
install: $(STATIC_PRODUCT)
$(srcdir)/mkinstalldirs $(libdir)
$(INSTALL_DATA) $(STATIC_PRODUCT) $(libdir)
$(srcdir)/mkinstalldirs $(includedir)
$(INSTALL_DATA) NufxLib.h $(includedir)
install-shared: $(SHARED_PRODUCT)
$(srcdir)/mkinstalldirs $(libdir)
$(INSTALL_DATA) $(SHARED_PRODUCT) $(libdir)
$(srcdir)/mkinstalldirs $(includedir)
$(INSTALL_DATA) NufxLib.h $(includedir)
samples::
@echo "Building samples..."
@(cd samples; set +e; unset CFLAGS OBJS; set -e; \
@SET_MAKE@ LIB_PRODUCT="../$(PRODUCT)" $(MAKE))
shared::
PRODUCT="$(SHARED_PRODUCT)" $(MAKE) -e
$(STATIC_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
$(AR) $@ $(OBJS)
@RANLIB@ $@
# BUG: we need -fPIC, maybe -D_REENTRANT when compiling for this.
# BUG: for Linux we may want -Wl,-soname,libnufx.so.1 on the link line.
$(SHARED_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
$(CC) @SHARE_FLAGS@ -o $@ $(OBJS) @LIBS@
clean:
(cd samples; make clean)
-rm -f *.o core
-rm -f $(SHARED_PRODUCT) $(STATIC_PRODUCT)
# build tags; assumes fancy GNU tag generation
tags::
@ctags -R --totals *
@#ctags *.[ch]
distclean: clean
(cd samples; make distclean)
-rm -f Makefile Makefile.bak
-rm -f config.log config.cache config.status config.h
-rm -f tags
# Make a tarfile with a backup of the essential files. We include "Makefile"
# so that we can do a "make distclean" during packaging.
baktar:
@tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \
Makefile.msc Makefile.dll install-sh config.guess config.sub \
mkinstalldirs *.[ch] samples/*.txt samples/Makefile* samples/*.[ch]
@gzip -9 nufxlib.tar
@mv -i nufxlib.tar.gz /home/fadden/BAK/
# dependency info
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
Archive.o: Archive.c $(COMMON_HDRS)
ArchiveIO.o: ArchiveIO.c $(COMMON_HDRS)
Bzip2.o: Bzip2.c $(COMMON_HDRS)
Charset.o: Charset.c $(COMMON_HDRS)
Compress.o: Compress.c $(COMMON_HDRS)
Crc16.o: Crc16.c $(COMMON_HDRS)
Debug.o: Debug.c $(COMMON_HDRS)
Deferred.o: Deferred.c $(COMMON_HDRS)
Deflate.o: Deflate.c $(COMMON_HDRS)
Entry.o: Entry.c $(COMMON_HDRS)
Expand.o: Expand.c $(COMMON_HDRS)
FileIO.o: FileIO.c $(COMMON_HDRS)
Funnel.o: Funnel.c $(COMMON_HDRS)
Lzc.o: Lzc.c $(COMMON_HDRS)
Lzw.o: Lzw.c $(COMMON_HDRS)
MiscStuff.o: MiscStuff.c $(COMMON_HDRS)
MiscUtils.o: MiscUtils.c $(COMMON_HDRS)
Record.o: Record.c $(COMMON_HDRS)
SourceSink.o: SourceSink.c $(COMMON_HDRS)
Squeeze.o: Squeeze.c $(COMMON_HDRS)
Thread.o: Thread.c $(COMMON_HDRS)
Value.o: Value.c $(COMMON_HDRS)
Version.o: Version.c $(COMMON_HDRS) Makefile

View File

@ -0,0 +1,160 @@
# Makefile for NufxLib using Microsoft Visual C++. This builds the library
# as a static lib and as a DLL, and builds all samples. The test-basic
# sample is built twice, once with the static lib, and once with the DLL.
#
# Tested with VS 2013 Pro. From the "VS2013 x86 Native Tools Command
# Prompt", run "nmake -f makefile.msc".
#
# If you're including zlib support, place copies of zlib.h, zconf.h,
# and the zlib library in this directory.
#
# Adapted from zlib's Makefile.msc.
#
TOP = .
STATICLIB = nufxlib2.lib
SHAREDLIB = nufxlib2.dll
IMPLIB = nufxdll.lib
CC = cl
LD = link
AR = lib
# C compiler flags
# -Fd: rename PDB file from "VCx0.pdb" (where 'x' is the version number);
# allows DLL debug info to be separate from app debug info
# -Ox: full optimization
# -Oy-: disable frame pointer omission (for easier debugging)
# -MD: create a multithreaded DLL using MSVCRT.lib; alternatively,
# use -MDd to create a debug executable with MSVCRTD.lib
# -nologo: suppress display of copyright banner
# -W3: set warning level to 3 (all production-level warnings)
# -Zi: generate a PDB file with full debugging info
#
# The OPTFLAGSTR define is used by Version.c to show how the library was
# built. Defining NUFXLIB_EXPORTS enables the __declspec(dllexport)
# macros that are required for creating the DLL.
OPTFLAGS = -Ox -Oy-
CFLAGS = -nologo -MD -W3 $(OPTFLAGS) -Zi -Fd"nufxlib"
LIB_CFLAGS = -DOPTFLAGSTR="\"$(OPTFLAGS)\"" #-DNUFXLIB_EXPORTS
# Warning suppression flags
WFLAGS = -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE
# Linker flags
# -debug: creates debugging info for EXE or DLL in PDB file
# -incremental:no: disable incremental linking, making the resulting library
# a tad smaller
# -nologo: suppress display of copyright banner
# -opt:ref: eliminates unreferenced functions and data (default for non-debug
# builds, but we've enabled debug info)
LDFLAGS = -nologo -debug -incremental:no -opt:ref
# Library creator flags
ARFLAGS = -nologo
ZLIB=1
!ifdef ZLIB
# enable deflate support; requires zlib
CFLAGS = $(CFLAGS) -DENABLE_DEFLATE
LDFLAGS = $(LDFLAGS) zlib.lib
!endif
# object files
OBJS = Archive.obj ArchiveIO.obj Bzip2.obj Charset.obj Compress.obj \
Crc16.obj Debug.obj Deferred.obj Deflate.obj Entry.obj Expand.obj \
FileIO.obj Funnel.obj Lzc.obj Lzw.obj MiscStuff.obj MiscUtils.obj \
Record.obj SourceSink.obj Squeeze.obj Thread.obj Value.obj Version.obj
# build targets -- static library, dynamic library, and test programs
all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) \
exerciser.exe imgconv.exe launder.exe test-basic.exe test-basic-d.exe \
test-extract.exe test-names.exe test-simple.exe test-twirl.exe
clean:
-del *.obj *.pdb *.exp
-del $(STATICLIB) $(SHAREDLIB) $(IMPLIB)
$(STATICLIB): $(OBJS)
$(AR) $(ARFLAGS) -out:$@ $(OBJS)
$(IMPLIB): $(SHAREDLIB)
$(SHAREDLIB): $(OBJS)
$(LD) $(LDFLAGS) -dll -def:nufxlib.def -implib:$(IMPLIB) -out:$@ \
$(OBJS)
exerciser.exe: Exerciser.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ Exerciser.obj $(STATICLIB)
imgconv.exe: ImgConv.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ ImgConv.obj $(STATICLIB)
launder.exe: Launder.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ Launder.obj $(STATICLIB)
test-basic.exe: TestBasic.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ TestBasic.obj $(STATICLIB)
test-basic-d.exe: TestBasic.obj $(IMPLIB)
$(LD) $(LDFLAGS) -out:$@ TestBasic.obj $(IMPLIB)
test-extract.exe: TestExtract.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ TestExtract.obj $(STATICLIB)
test-names.exe: TestNames.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ TestNames.obj $(STATICLIB)
test-simple.exe: TestSimple.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ TestSimple.obj $(STATICLIB)
test-twirl.exe: TestTwirl.obj $(STATICLIB)
$(LD) $(LDFLAGS) -out:$@ TestTwirl.obj $(STATICLIB)
# generic rules
{$(TOP)}.c.obj:
$(CC) -c $(WFLAGS) $(CFLAGS) $(LIB_CFLAGS) $<
{$(TOP)/samples}.c.obj:
$(CC) -c -I$(TOP) $(WFLAGS) $(CFLAGS) $<
# dependency info
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
Archive.obj: Archive.c $(COMMON_HDRS)
ArchiveIO.obj: ArchiveIO.c $(COMMON_HDRS)
Bzip2.obj: Bzip2.c $(COMMON_HDRS)
Charset.obj: Charset.c $(COMMON_HDRS)
Compress.obj: Compress.c $(COMMON_HDRS)
Crc16.obj: Crc16.c $(COMMON_HDRS)
Debug.obj: Debug.c $(COMMON_HDRS)
Deferred.obj: Deferred.c $(COMMON_HDRS)
Deflate.obj: Deflate.c $(COMMON_HDRS)
Entry.obj: Entry.c $(COMMON_HDRS)
Expand.obj: Expand.c $(COMMON_HDRS)
FileIO.obj: FileIO.c $(COMMON_HDRS)
Funnel.obj: Funnel.c $(COMMON_HDRS)
Lzc.obj: Lzc.c $(COMMON_HDRS)
Lzw.obj: Lzw.c $(COMMON_HDRS)
MiscStuff.obj: MiscStuff.c $(COMMON_HDRS)
MiscUtils.obj: MiscUtils.c $(COMMON_HDRS)
Record.obj: Record.c $(COMMON_HDRS)
SourceSink.obj: SourceSink.c $(COMMON_HDRS)
Squeeze.obj: Squeeze.c $(COMMON_HDRS)
Thread.obj: Thread.c $(COMMON_HDRS)
Value.obj: Value.c $(COMMON_HDRS)
Version.obj: Version.c $(COMMON_HDRS)
Exerciser.obj: samples/Exerciser.c $(COMMON_HDRS)
ImgConv.obj: samples/ImgConv.c $(COMMON_HDRS)
Launder.obj: samples/Launder.c $(COMMON_HDRS)
TestBasic.obj: samples/TestBasic.c $(COMMON_HDRS)
TestExtract.obj: samples/TestExtract.c $(COMMON_HDRS)
TestNames.obj: samples/TestNames.c $(COMMON_HDRS)
TestSimple.obj: samples/TestSimple.c $(COMMON_HDRS)
TestTwirl.obj: samples/TestTwirl.c $(COMMON_HDRS)

339
ciderpress/nufxlib/NOTES.md Normal file
View File

@ -0,0 +1,339 @@
NufxLib NOTES
=============
Last revised: 2015/01/04
The interface is documented in "nufxlibapi.html", available from the
http://www.nulib.com/ web site. This discusses some of the internal
design that may be of interest.
Some familiarity with the NuFX file format is assumed.
- - -
### Read-Write Data Structures ###
For both read-only and read-write files (but not streaming read-only files),
the archive is represented internally as a linked list of Records, each
of which has an array of Threads attached. No attempt is made to
optimize searches by filename, so use of the "replace existing entry when
filenames match" option should be restricted to situations where it is
necessary. Otherwise, O(N^2) behavior can result.
Modifications, such as deletions, changes to filename threads, and
additions of new records, are queued up in a separate list until a NuFlush
call is issued. The list works much the same way as the temporary file:
when the operation completes, the "new" list becomes the "original" list.
If the operation is aborted, the "new" list is scrubbed, and the "original"
list remains unmodified.
Just as it is inefficient to write data to the temp file when it's not
necessary to do so, it is inefficient to allocate a complete copy of the
records from the original list if none are changed. As a result, there are
actually two "new" lists, one with a copy of the original record list, and
one with new additions. The "copy" list starts out uninitialized, and
remains that way until one of the entries from the original list is
modified. When that happens, the entire original list is duplicated, and
the changes are made directly to members of the "copy" list. (This is
important for really large archives, like a by-file archive with the
entire contents of a hard drive, where the record index could be several
megabytes in size.)
It would be more *memory* efficient to simply maintain a list of what
has changed. However, we can't disturb the "original" list in any way or
we lose the ability to roll back quickly if the operation is aborted.
Consequently, we need to create a new list of records that reflects
the state of the new archive, so that when we rename the temp file over
the original, we can simply "rename" the new record list over the original.
Since we're going to need the new list eventually, we might as well create
it as soon as it is needed, and deal with memory allocation failures up
front rather than during the update process. (Some items, such as the
record's file offset in the archive, have to be updated even for records
that aren't themselves changing... which means we potentially need to
modify all existing record structures, so we need a complete copy of the
record list regardless of how little or how much has changed.)
This also ties into the "modify original archive file directly if possible"
option, which avoids the need for creating and renaming a temp file. If
the only changes are updates to pre-sized records (e.g. renaming a file
inside the archive, or updating a comment), or adding new records onto the
end, there is little risk and possibly a huge efficiency gain in just
modifying the archive in place. If none of the operations caused the
"copy" list to be initialized, then clearly there's no need to write to a
temp file. (It's not actually this simple, because updates to pre-sized
threads are annotated in the "copy" list.)
One of the goals was to be able to execute a sequence of operations like:
open original archive
read original archive
modify archive
flush (success)
modify archive
flush (failure, rollback)
modify archive
flush (success)
close archive
The archive is opened at the start and held open across many operations.
There is never a need to re-read the entire archive. We could avoid the
need to allocate two complete Record lists by requiring that the archive be
re-scanned after changes are aborted; if we did that, we could just modify
the original record list in place, and let the changes become "permanent"
after a successful write. In many ways, though, its cleaner to have two
lists.
Archives with several thousand entries should be sufficiently rare, and
virtual memory should be sufficiently plentiful, that this won't be a
problem for anyone. Scanning repeatedly through a 15MB archive stored on a
CD-ROM is likely to be very annoying though, so the design makes every
attempt to avoid repeated scans of the archive. And in any event, this
only applies to archive updates. The memory requirements for simple file
extraction are minimal.
In summary:
- "orig" list has original set of records, and is not disturbed until
the changes are committed.
- "copy" list is created on first add/update/delete operation, and
initially contains a complete copy of "orig".
- "new" list contains all new additions to the archive, including
new additions that replace existing entries (the existing entry
is deleted from "copy" and then added to "new").
Each Record in the list has a "thread modification" list attached to it.
Any changes to the record header or additions to the thread mod list are
made in the "copy" set; the "original" set remains untouched. The thread
mod list can have the following items in it:
- delete thread (NuThreadIdx)
- add thread (type, otherSize, format, +contents)
- update pre-sized thread (NuThreadIdx, +contents)
Contents are specified with a NuDataSource, which allows the application
to indicate that the data is already compressed. This is useful for
copying parts of records between archives without having to expand and
recompress the data.
Some interactions and concepts that are important to understand:
When a file is added, the file type information will be placed in the
"new" Record immediately (subject to some restrictions: adding a data
fork always causes the type info to be updated, adding a rsrc fork only
updates the type info if a data fork is not already present).
Deleting a record results in the Record being removed from the "copy"
list immediately. Future modify operations on that NuRecordIdx will
fail. Future read operations will work just fine until the next
NuFlush is issued, because read operations use the "original" list.
Deleting all threads from a record results in the record being
deleted, but not until the NuFlush call is issued. It is possible to
delete all the existing threads and then add new ones.
It is *not* allowed to delete a modified thread, modify a deleted thread,
or delete a record that has been modified. This limitation was added to
keep the system simple. Note this does not mean you can't delete a data
fork and add a data fork; doing so results in operations on two threads
with different NuThreadIdx values. What you can't do is update the
filename thread and then delete it, or vice-versa. (If anyone can think
of a reason why you'd want to rename a file and then delete it with the
same NuFlush call, I'll figure out a way to support it.)
Updating a filename thread is intercepted, and causes the Record's
filename cache to be updated as well. Adding a filename thread for
records where the filename is stored in the record itself cause the
"in-record" filename to be zeroed. Adding a filename thread to a
record that already has one isn't allowed; nufxlib restricts you to
a single filename thread per record.
Some actions on an archive are allowed but strongly discouraged. For
example, deleting a filename thread but leaving the data threads behind
is a valid thing to do, but leaves most archivers in a state of mild
confusion. Deleting the data threads but leaving the filename thread is
similarly perplexing.
You can't call "update thread" on a thread that doesn't yet exist,
even if an "add thread" call has been made. You can, however, call
"add thread" on a newly created Record.
When a new record is created because of a "create record" call, a filename
thread is created automatically. It is not necessary to explicitly add the
filename.
Failures encountered while committing changes to a record cause all
operations on that record to be rolled back. If, during a NuFlush, a
file add fails, the user is given the option of aborting the entire
operation or skipping the file in question (and perhaps retrying or other
options as well). Aborting the flush causes a complete rollback. If only
the thread mod operation is canceled, then all thread mods for that record
are ignored. The temp file (or archive file) will have its file pointer
reset to the original start of the record, and if the record already
existed in the original archive, the full original record will be copied
over. This may seem drastic, but it helps ensure that you don't end up
with a record in a partially created state.
If a failure occurs during an "update in place", it isn't possible to
roll back all changes. If the failure was due to a bug in NufxLib, it
is possible that the archive could be unrecoverably damaged. NufxLib
tries to identify such situations, and will leave the archive open in
read-only mode after rolling back any new file additions.
- - -
### Updating Filenames ###
Updating filenames is a small nightmare, because the filename can be
either in the record header or in a filename thread. It's possible,
but illogical, to have a single record with a filename in the record
header and two or more filenames in threads.
NufxLib will not automatically "fix" broken records, but it will prevent
applications from creating situations that should not exist.
- When reading an archive, NufxLib will use the filename from the
first filename thread found. If no filename threads are found, the
filename from the record header will be used.
- If you add a filename thread to a record that has a filename in the
record header, the header name will be removed.
- If you update a filename thread in a record that has a filename in
the record header, the header name will be left untouched.
- Adding a filename thread is only allowed if no filename thread exists,
or all existing filename threads have been deleted.
- - -
### Unicode Filenames ###
Modern operating systems support filenames with a broader range of
characters than the Apple II did. This presents problems and opportunities.
#### Background ####
The Apple IIgs and old Macintoshes use the Mac OS Roman ("MOR") character
set. This defines a set of characters outside the ASCII range, i.e.
byte values with the high bit set. In addition to the usual collection
of vowels with accents and umlauts, MOR has some less-common characters,
including the Apple logo.
On Windows, the high-ASCII values are generally interpreted according
to Windows Code Page 1252 ("CP-1252"), which defines a similar set
of vowels with accents and miscellaneous symbols. MOR and CP-1252
have some overlap, but you can't really translate one into the other.
The standards-approved equivalent of CP-1252 is ISO-8859-1, though
according to [wikipedia](http://en.wikipedia.org/wiki/Windows-1252)
there was some confusion between the two.
Modern operating systems support the Unicode Universal Character Set.
This system allows for a very large number of characters (over a million),
and includes definitions for all of the symbols in MOR and CP-1252.
Each character is assigned a "code point", which is a numeric value between
zero and 0x10FFFF. Most of the characters used in modern languages can
be found in the Basic Multilingual Plane (BMP), which uses code points
between zero and 0xFFFF (requiring only 16 bits).
There are different ways of encoding code points. Consider, for example,
Unicode LATIN SMALL LETTER A WITH ACUTE:
MOR: 0x87
CP-1252: 0xE1
Unicode: U+00E1
UTF-16: 0x00E1
UTF-8: 0xC3 0xA1
Or the humble TRADE MARK SIGN:
MOR: 0xAA
CP-1252: 0x99
Unicode: U+2122
UTF-16: 0x2122
UTF-8: 0xE2 0x84 0xA2
Modern Linux and Mac OS X use UTF-8 encoding in filenames. Because it's a
byte-oriented encoding, and 7-bit ASCII values are trivially represented
as 7-bit ASCII values, all of the existing system and library calls work
as they did before (i.e. if they took a `char*`, they still do).
Windows uses UTF-16, which requires at least 16 bits per code point.
Filenames are now "wide" strings, based on `wchar_t*`. Windows includes
an elaborate system of defines based around the `TCHAR` type, which can
be either `char` or `wchar_t` depending on whether a program is compiled
with `_MBCS` (Multi-Byte Character System) or `_UNICODE`. A set of
preprocessor definitions is provided that will map I/O function names,
so you can call `_tfopen(TCHAR* ...)`, and the compiler will turn it into
either `fopen(char* ...)` or `_wfopen(wchar_t* ...)`. MBCS is deprecated
in favor of Unicode, so any new code should be strictly UTF-16 based.
This means that, for code to work on both Linux and Windows, it has to
work with incompatible filename string types and different I/O functions.
#### Opening Archive Files ####
On Linux and Mac OS X, NuLib2 can open any file named on the command line.
On Windows, it's a bit trickier.
The problem is that NuLib2 provides a `main()` function that is passed a
vector of "narrow" strings. The filenames provided on the command line
will be converted from wide to narrow, so unless the filename is entirely
composed of ASCII or CP-1252 characters, some information will be lost
and it will be impossible to open the file.
NuLib2 must instead provide a `wmain()` function that takes wide strings.
The strings must be stored and passed around as wide throughout the
program, and passed into NufxLib this way (because NufxLib issues the
actual _wopen call). This means that NufxLib API must take narrow strings
when built for Linux, and wide strings when built for Windows.
#### Adding/Extracting Mac OS Roman Files ####
GS/ShrinkIt was designed to handle GS/OS files from HFS volumes, so NuFX
archive filenames use the MOR character set. To preserve the encoding
we could simply extract the values as-is and let them appear as whatever
values happen to line up in CP-1252, which is what pre-3.0 NuLib2 did.
It's much nicer to translate from MOR to Unicode when extracting, and
convert back from Unicode to MOR when adding files to an archive.
The key consideration is that the character set associated with a
filename must be tracked. The code can't simply extract a filename from
the archive and pass it to a 'creat()` call. Character set conversions
must take place at appropriate times.
With Windows it's a bit harder to confuse MOR and Unicode names, because
one uses 8-bit characters and the other uses UTF-16, but the compiler
doesn't catch everything.
#### Current State ####
NufxLib defines the UNICHAR type, which has a role very like TCHAR:
it can be `char*` or `wchar_t*`, and can be accompanied by a set of
preprocessor mappings that switch between I/O functions. The UNICHAR
type will be determined based on a define provided from the compiler
command line (perhaps `-DUSE_UTF16_FILENAMES`).
The current version of NufxLib (v3.0.0) takes the first step, defining
all filename strings as either UNICHAR or MOR, and converting between them
as necessary. This, plus a few minor tweaks to NuLib2, was enough to
get Unicode filename support working on Linux and Mac OS X.
None of the work needed to make Windows work properly has been done.
The string conversion functions are no-ops for Win32. As a result,
NuLib2 for Windows treats filenames the same way in 3.x as it did in 2.x.
There are some situations where things can go awry even with UNICHAR,
most notably printf-style arguments. These are checked by gcc, but
not by Visual Studio unless you run the static analyzer. A simple
`printf("filename=%s\n", filename)` would be correct for narrow strings
but wrong for wide strings. It will likely be necessary to define a
filename format string (similar to `PRI64d` for 64-bit values) and switch
between "%s" and "%ls".
This is a fair bit of work and requires some amount of uglification to
NuLib2 and NufxLib. Since Windows users can use CiderPress, and the
vast majority of NuFX archives use ASCII-only ProDOS file names, it's
not clear that the effort would be worthwhile.

View File

@ -0,0 +1,119 @@
NufxLib README, updated 2014/12/23
http://www.nulib.com/
See "COPYING-LIB" for distribution restrictions.
UNIX
====
Run the "configure" script. Read through "INSTALL" if you haven't used
one of these before, especially if you want to use a specific compiler
or a particular set of compiler flags.
You can disable specific compression methods with "--disable-METHOD"
(run "sh ./configure --help" to see the possible options). By default,
all methods are enabled except bzip2.
Run "make depend" if you have makedepend, and then type "make". This will
build the library and all of the programs in the "samples" directory.
There are some useful programs in "samples", described in a README.txt
file there. In particular, you should run samples/test-basic to verify
that things are more or less working.
If you want to install the library and header file into standard system
locations (usually /usr/local), run "make install". To learn how to
specify different locations, read the INSTALL document.
There are some flags in "OPT" you may want to use. The "autoconf" default
for @CFLAGS@ is "-g -O2".
-DNDEBUG
Disable assert() calls and extra tests. This will speed things up,
but errors won't get caught until later on, making the root cause
harder to locate.
-DDEBUG_MSGS
Enable debug messages. This increases the size of the executable,
but shouldn't affect performance. When errors occur, more output is
produced. The "debug dump" feature is enabled by this flag.
-DDEBUG_VERBOSE
(Implicitly sets DEBUG_MSGS.) Spray lots of debugging output.
If you want to do benchmarks, use "-O2 -DNDEBUG". The recommended
configuration is "-g -O2 -DDEBUG_MSGS", so that verbose debug output is
available when errors occur.
BeOS
====
This works just like the UNIX version, but certain defaults have been
changed. Running configure without arguments under BeOS is equivalent to:
./configure --prefix=/boot --includedir='${prefix}/develop/headers'
--libdir='${exec_prefix}/home/config/lib' --mandir='/tmp'
--bindir='${exec_prefix}/home/config/bin'
If you're using BeOS/PPC, it will also do:
CC=cc CFLAGS='-proc 603 -opt full'
Mac OS X
========
This works just like the UNIX version, but includes support for resource
forks and Finder file/aux types.
Tested with Xcode v5.1.1 and Mac OS 10.8.5.
Win32
=====
If you're using an environment that supports "configure" scripts, such as
DJGPP, follow the UNIX instructions.
NufxLib has been tested with Microsoft Visual C++ 12 (Visual Studio 2013).
To build NufxLib, run the "Visual Studio 2013 x86 Native Tools Command
Prompt" shortcut to get a shell. Change to the nufxlib directory, then:
nmake -f makefile.msc
When the build finishes, run "test-basic.exe" to confirm things are working.
If you want to have zlib support enabled, you will need to have zlib.h,
zconf.h, and zlib.lib copied into the directory. See "makefile.msc" for
more details.
The makefile builds NufxLib as a static library and as a DLL.
Other Notes
===========
If you want to use the library in a multithreaded application, you should
define "USE_REENTRANT_CALLS" to tell it to use reentrant versions of
certain library calls. This defines _REENTRANT, which causes Solaris to
add the appropriate goodies. (Seems to me you'd always want this on, but
for some reason Solaris makes you take an extra step, so I'm not going to
define it by default.)
Originally, NufxLib / NuLib2 were intended to be usable natively on the
Apple IIgs, so some of the design decisions were influenced by the need
to minimize memory usage (e.g. being able to get a directory listing
without holding the entire directory in memory) and interact with GS/OS
(forked files have a single filename, files have type/auxtype). The IIgs
port was never started.
Legalese
========
NufxLib, a NuFX archive manipulation library.
Copyright (C) 2000-2014 by Andy McFadden, All Rights Reserved.
See COPYING for license.

View File

@ -72,40 +72,6 @@
# define vsnprintf _vsnprintf
# endif
#else
# ifndef HAVE_CONFIG_H
# define HAVE_FCNTL_H
# define HAVE_MALLOC_H
# define HAVE_STDLIB_H
# define HAVE_SYS_STAT_H
# undef HAVE_SYS_TIME_H
# define HAVE_SYS_TYPES_H
# define HAVE_UNISTD_H
# define HAVE_UTIME_H
# undef HAVE_SYS_UTIME_H
# define HAVE_FDOPEN
# undef HAVE_FTRUNCATE
# define HAVE_MEMMOVE
# undef HAVE_MKSTEMP
# define HAVE_MKTIME
# define HAVE_SNPRINTF
# undef HAVE_STRCASECMP
# undef HAVE_STRNCASECMP
# define HAVE_STRERROR
# define HAVE_STRTOUL
# define HAVE_VSNPRINTF
# define SNPRINTF_DECLARED
# define VSNPRINTF_DECLARED
# define SPRINTF_RETURNS_INT
# define inline /*Visual C++6.0 can't inline ".c" files*/
# define ENABLE_SQ
# define ENABLE_LZW
# define ENABLE_LZC
/*# define ENABLE_DEFLATE*/
/*# define ENABLE_BZIP2*/
# endif
#endif
#ifdef HAVE_MALLOC_H

1466
ciderpress/nufxlib/config.guess vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,134 @@
/*
* NuFX archive manipulation library
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
* This is free software; you can redistribute it and/or modify it under the
* terms of the BSD License, see the file COPYING-LIB.
*/
/* config.h.in. */
/* Define to empty if the keyword does not work. */
#undef const
/* Define to empty if the keyword does not work. */
#undef inline
/* Define to `int' if <sys/types.h> doesn't define. */
#undef mode_t
/* Define to `long' if <sys/types.h> doesn't define. */
#undef off_t
/* Define to `unsigned' if <sys/types.h> doesn't define. */
#undef size_t
/* Define if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Define if your <sys/time.h> declares struct tm. */
#undef TM_IN_SYS_TIME
/* Define to `int' if <sys/types.h> doesn't define. */
#undef mode_t
/* Define to `long' if <sys/types.h> doesn't define. */
#undef off_t
/* Define to `unsigned' if <sys/types.h> doesn't define. */
#undef size_t
/* Define if you have the fdopen function. */
#undef HAVE_FDOPEN
/* Define if you have the ftruncate function. */
#undef HAVE_FTRUNCATE
/* Define if you have the localtime_r function. */
#undef HAVE_LOCALTIME_R
/* Define if you have the memmove function. */
#undef HAVE_MEMMOVE
/* Define if you have the mkdir function. */
#undef HAVE_MKDIR
/* Define if you have the mkstemp function. */
#undef HAVE_MKSTEMP
/* Define if you have the mktime function. */
#undef HAVE_MKTIME
/* Define if you have the snprintf function. */
#undef HAVE_SNPRINTF
/* Define if you have the strcasecmp function. */
#undef HAVE_STRCASECMP
/* Define if you have the strncasecmp function. */
#undef HAVE_STRNCASECMP
/* Define if you have the strerror function. */
#undef HAVE_STRERROR
/* Define if you have the strtoul function. */
#undef HAVE_STRTOUL
/* Define if you have the timelocal function. */
#undef HAVE_TIMELOCAL
/* Define if you have the vsnprintf function. */
#undef HAVE_VSNPRINTF
/* Define if you have the <fcntl.h> header file. */
#undef HAVE_FCNTL_H
/* Define if you have the <malloc.h> header file. */
#undef HAVE_MALLOC_H
/* Define if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define if you have the <sys/time.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define if you have the <sys/time.h> header file. */
#undef HAVE_SYS_TIME_H
/* Define if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define if you have the <sys/utime.h> header file. */
#undef HAVE_SYS_UTIME_H
/* Define if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define if you have the <utime.h> header file. */
#undef HAVE_UTIME_H
/* Define if sprintf returns an int. */
#undef SPRINTF_RETURNS_INT
/* Define if SNPRINTF is declared in stdio.h. */
#undef SNPRINTF_DECLARED
/* Define if VSNPRINTF is declared in stdio.h. */
#undef VSNPRINTF_DECLARED
/* Define to include SQ (Huffman+RLE) compression. */
#undef ENABLE_SQ
/* Define to include LZW (ShrinkIt LZW/1 and LZW/2) compression. */
#undef ENABLE_LZW
/* Define to include LZC (12-bit and 16-bit UNIX "compress") compression. */
#undef ENABLE_LZC
/* Define to include deflate (zlib) compression (also need -l in Makefile). */
#undef ENABLE_DEFLATE
/* Define to include bzip2 (libbz2) compression (also need -l in Makefile). */
#undef ENABLE_BZIP2
/* Define if we want to use the dmalloc library (also need -l in Makefile). */
#undef USE_DMALLOC

1579
ciderpress/nufxlib/config.sub vendored Normal file

File diff suppressed because it is too large Load Diff

5503
ciderpress/nufxlib/configure vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,218 @@
dnl NuFX archive manipulation library
dnl Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
dnl This is free software; you can redistribute it and/or modify it under the
dnl terms of the BSD License, see the file COPYING-LIB.
dnl
dnl Process this file with autoconf to produce a configure script.
AC_INIT(NufxLibPriv.h)
AC_CONFIG_HEADER(config.h)
dnl Checks for programs.
AC_CANONICAL_HOST
dnl AC_PROG_AWK
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_MAKE_SET
AC_PROG_RANLIB
dnl Checks for header files.
AC_CHECK_HEADERS(fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \
sys/utime.h unistd.h utime.h)
LIBS=""
dnl Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_C_INLINE
AC_TYPE_MODE_T
AC_TYPE_OFF_T
AC_TYPE_SIZE_T
AC_STRUCT_TM
dnl Checks for library functions.
AC_CHECK_FUNCS(fdopen ftruncate memmove mkdir mkstemp mktime timelocal \
localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf)
dnl Kent says: snprintf doesn't always have a declaration
AC_MSG_CHECKING(if snprintf is declared)
AC_CACHE_VAL(nufxlib_cv_snprintf_in_header, [
AC_EGREP_HEADER(snprintf, stdio.h,
[nufxlib_cv_snprintf_in_header=yes],
[nufxlib_cv_snprintf_in_header=no])
])
if test $nufxlib_cv_snprintf_in_header = "yes"; then
AC_DEFINE(SNPRINTF_DECLARED)
fi
AC_MSG_RESULT($nufxlib_cv_snprintf_in_header)
dnl Kent says: vsnprintf doesn't always have a declaration
AC_MSG_CHECKING(if vsnprintf is declared)
AC_CACHE_VAL(nufxlib_cv_vsnprintf_in_header, [
AC_EGREP_HEADER(vsnprintf, stdio.h,
[nufxlib_cv_vsnprintf_in_header=yes],
[nufxlib_cv_vsnprintf_in_header=no])
])
if test $nufxlib_cv_vsnprintf_in_header = "yes"; then
AC_DEFINE(VSNPRINTF_DECLARED)
fi
AC_MSG_RESULT($nufxlib_cv_vsnprintf_in_header)
dnl if we're using gcc, include gcc-specific warning flags
if test -z "$GCC"; then
BUILD_FLAGS='$(OPT)'
else
BUILD_FLAGS='$(OPT) $(GCC_FLAGS)'
fi
dnl ---------------------------------------------------------------------------
dnl Some host-specific stuff. Variables you can test (set by the
dnl AC_CANONICAL_HOST call earlier) look like this:
dnl
dnl $host = i686-pc-linux-gnu
dnl $host_cpu = i686
dnl $host_vendor = pc
dnl $host_os = linux-gnu
dnl Figure out what the build and link flags should be; different for BeOS.
dnl (Should really use the auto-shared-lib stuff, but that adds a whole
dnl bunch of stuff.)
SHARE_FLAGS='-shared'
if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then
dnl BeOS/PPC, with Metrowerks compiler
CC=cc
GCC=
CFLAGS='-proc 603 -opt full'
SHARE_FLAGS='-shared -nostdlib'
echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\""
elif test "$host_os" = "beos"; then
dnl BeOS/x86
SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"'
fi
AC_SUBST(BUILD_FLAGS)
AC_SUBST(SHARE_FLAGS)
dnl BeOS doesn't like /usr/local/include, and gets feisty about it. If libdir
dnl and includedir are set to defaults, replace them with BeOS values. This
dnl might be going a little too far...
if test "$host_os" = "beos"; then
if test "$prefix" = "NONE" -a \
"$includedir" = '${prefix}/include' -a \
"$libdir" = '${exec_prefix}/lib' -a \
"$bindir" = '${exec_prefix}/bin' -a \
"$mandir" = '${prefix}/man'
then
echo replacing install locations with BeOS values
prefix=/boot
includedir='${prefix}/develop/headers'
libdir='${exec_prefix}/home/config/lib'
bindir='${exec_prefix}/home/config/bin'
mandir='/tmp'
AC_SUBST(prefix)
AC_SUBST(includedir)
AC_SUBST(libdir)
AC_SUBST(bindir)
AC_SUBST(mandir)
fi
fi
dnl Test to see if sprintf does something reasonable. I'm going to assume
dnl that vsprintf and (if available) vsnprintf return the same thing.
AC_MSG_CHECKING(if sprintf returns int)
AC_CACHE_VAL(nufxlib_cv_sprintf_returns_int, [
AC_TRY_RUN([
#include <stdio.h>
int main(void)
{
int count;
char buf[8];
count = sprintf(buf, "123"); /* should return three */
exit(count != 3);
}
],
[nufxlib_cv_sprintf_returns_int=yes], [nufxlib_cv_sprintf_returns_int=no],
[nufxlib_cv_sprintf_returns_int=no])
])
if test $nufxlib_cv_sprintf_returns_int = "yes"; then
AC_DEFINE(SPRINTF_RETURNS_INT)
fi
AC_MSG_RESULT($nufxlib_cv_sprintf_returns_int)
dnl
dnl Allow selective disabling of compression algorithms. By default,
dnl all are enabled. We do a little extra work for libz and libbz2
dnl because they're not built in.
dnl
dnl If we're creating a shared library, we need to explicitly link
dnl against libz and/or libbz2 when those features are enabled.
dnl
AC_ARG_ENABLE(sq,
[ --disable-sq disable SQ compression],
[ ], [ enable_sq=yes ])
if test $enable_sq = "yes"; then
AC_DEFINE(ENABLE_SQ)
fi
AC_ARG_ENABLE(lzw,
[ --disable-lzw disable LZW/1 and LZW/2 compression],
[ ], [ enable_lzw=yes ])
if test $enable_lzw = "yes"; then
AC_DEFINE(ENABLE_LZW)
fi
AC_ARG_ENABLE(lzc,
[ --disable-lzc disable 12- and 16-bit LZC compression],
[ ], [ enable_lzc=yes ])
if test $enable_lzc = "yes"; then
AC_DEFINE(ENABLE_LZC)
fi
AC_ARG_ENABLE(deflate,
[ --disable-deflate disable zlib deflate compression],
[ ], [ enable_deflate=yes ])
if test $enable_deflate = "yes"; then
dnl Check for zlib. Make sure it comes with zlib.h.
got_zlibh=false
AC_CHECK_LIB(z, deflate, got_libz=true, got_libz=false)
if $got_libz; then
AC_CHECK_HEADER(zlib.h, got_zlibh=true LIBS="$LIBS -lz")
fi
if $got_zlibh; then
echo " (found libz and zlib.h, enabling deflate)"
AC_DEFINE(ENABLE_DEFLATE)
else
echo " (couldn't find libz and zlib.h, not enabling deflate)"
fi
fi
AC_ARG_ENABLE(bzip2,
[ --enable-bzip2 enable libbz2 bzip2 compression],
[ ], [ enable_bzip2=no ])
if test $enable_bzip2 = "yes"; then
dnl Check for libbz2. Make sure it comes with bzlib.h.
dnl AC_CHECK_LIB(bz2, BZ2_bzCompress,
dnl AC_CHECK_HEADER(bzlib.h, AC_DEFINE(ENABLE_BZIP2) LIBS="$LIBS -lbz2"))
got_bzlibh=false
AC_CHECK_LIB(bz2, BZ2_bzCompress, got_libbz=true, got_libbz=false)
if $got_libbz; then
AC_CHECK_HEADER(bzlib.h, got_bzlibh=true LIBS="$LIBS -lbz2")
fi
if $got_bzlibh; then
echo " (found libbz2 and bzlib.h, enabling bzip2)"
AC_DEFINE(ENABLE_BZIP2)
else
echo " (couldn't find libbz2 and bzlib.h, not enabling bzip2)"
fi
fi
AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc do dmalloc stuff],
[ echo "--- enabling dmalloc";
LIBS="$LIBS -L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ])
AC_OUTPUT(Makefile samples/Makefile)

View File

@ -0,0 +1,69 @@
; NufxLib library exported symbols
;
; This is redundant with the __declspec(dllexport) declarations enabled
; with the NUFXLIB_EXPORTS symbol. If we're just building a DLL, there's
; no need for this file. However, the objects we're building are used for
; both the static library and the dynamic library, and we don't want to
; have exports in the static library. If we do, the linker will create
; .lib and .exp files for every executable we link against it. This is
; mostly harmless, but a tad messy. I don't expect the interface to change,
; so there's not much of a maintenance burden here.
;
EXPORTS
NuAbort
NuAddFile
NuAddRecord
NuAddThread
NuClose
NuContents
NuConvertMORToUNI
NuConvertUNIToMOR
NuCreateDataSinkForBuffer
NuCreateDataSinkForFP
NuCreateDataSinkForFile
NuCreateDataSourceForBuffer
NuCreateDataSourceForFP
NuCreateDataSourceForFile
NuDataSinkGetOutCount
NuDataSourceSetRawCrc
NuDebugDumpArchive
NuDelete
NuDeleteRecord
NuDeleteThread
NuExtract
NuExtractRecord
NuExtractThread
NuFlush
NuFreeDataSink
NuFreeDataSource
NuGetAttr
NuGetExtraData
NuGetMasterHeader
NuGetRecord
NuGetRecordIdxByName
NuGetRecordIdxByPosition
NuGetValue
NuGetVersion
NuIsPresizedThreadID
NuOpenRO
NuOpenRW
NuRecordCopyAttr
NuRecordCopyThreads
NuRecordGetNumThreads
NuRename
NuSetErrorHandler
NuSetErrorMessageHandler
NuSetExtraData
NuSetGlobalErrorMessageHandler
NuSetOutputPathnameFilter
NuSetProgressUpdater
NuSetRecordAttr
NuSetSelectionFilter
NuSetValue
NuStrError
NuStreamOpenRO
NuTest
NuTestFeature
NuTestRecord
NuThreadGetByIdx
NuUpdatePresizedThread

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C48AE53B-3DCB-43B1-9207-B7C5B6BB78AF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>nufxlib</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="MiscStuff.h" />
<ClInclude Include="NufxLib.h" />
<ClInclude Include="NufxLibPriv.h" />
<ClInclude Include="SysDefs.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Archive.c" />
<ClCompile Include="ArchiveIO.c" />
<ClCompile Include="Bzip2.c" />
<ClCompile Include="Charset.c" />
<ClCompile Include="Compress.c" />
<ClCompile Include="Crc16.c" />
<ClCompile Include="Debug.c" />
<ClCompile Include="Deferred.c" />
<ClCompile Include="Deflate.c" />
<ClCompile Include="Entry.c" />
<ClCompile Include="Expand.c" />
<ClCompile Include="FileIO.c" />
<ClCompile Include="Funnel.c" />
<ClCompile Include="Lzc.c" />
<ClCompile Include="Lzw.c" />
<ClCompile Include="MiscStuff.c" />
<ClCompile Include="MiscUtils.c" />
<ClCompile Include="Record.c" />
<ClCompile Include="SourceSink.c" />
<ClCompile Include="Squeeze.c" />
<ClCompile Include="Thread.c" />
<ClCompile Include="Value.c" />
<ClCompile Include="Version.c" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\zlib\zlib.vcxproj">
<Project>{b66109f4-217b-43c0-86aa-eb55657e5ac0}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="MiscStuff.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NufxLib.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NufxLibPriv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SysDefs.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Archive.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ArchiveIO.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Bzip2.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Compress.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Crc16.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Debug.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Deferred.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Deflate.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Entry.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Expand.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileIO.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Funnel.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Lzc.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Lzw.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MiscStuff.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MiscUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Record.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SourceSink.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Squeeze.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Thread.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Value.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Version.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Charset.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

615
diskimg/ASPI.cpp Normal file
View File

@ -0,0 +1,615 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* ASPI I/O functions.
*
* Some notes on ASPI stuff:
* - The Nero ASPI provides an interface for IDE hard drives. It also
* throws in a couple of mystery devices on a host adapter at the end.
* It has "unknown" device type and doesn't respond to SCSI device
* inquiries, so it's easy to ignore.
* - The Win98 generic ASPI only finds CD-ROM drives on the IDE bus.
*/
#include "StdAfx.h"
#if defined(_WIN32) && defined (WANT_ASPI)
#include "DiskImgPriv.h"
#include "SCSIDefs.h"
#include "CP_wnaspi32.h"
#include "ASPI.h"
/*
* Initialize ASPI.
*/
DIError ASPI::Init(void)
{
DWORD aspiStatus;
static const char* kASPIDllName = "wnaspi32.dll";
/*
* Try to load the DLL.
*/
fhASPI = ::LoadLibrary(kASPIDllName);
if (fhASPI == NULL) {
DWORD lastErr = ::GetLastError();
if (lastErr == ERROR_MOD_NOT_FOUND) {
LOGI("ASPI DLL '%s' not found", kASPIDllName);
} else {
LOGI("ASPI LoadLibrary(%s) failed (err=%ld)",
kASPIDllName, GetLastError());
}
return kDIErrGeneric;
}
GetASPI32SupportInfo = (DWORD(*)(void))::GetProcAddress(fhASPI, "GetASPI32SupportInfo");
SendASPI32Command = (DWORD(*)(LPSRB))::GetProcAddress(fhASPI, "SendASPI32Command");
GetASPI32DLLVersion = (DWORD(*)(void))::GetProcAddress(fhASPI, "GetASPI32DLLVersion");
if (GetASPI32SupportInfo == NULL || SendASPI32Command == NULL) {
LOGI("ASPI functions not found in dll");
::FreeLibrary(fhASPI);
fhASPI = NULL;
return kDIErrGeneric;
}
if (GetASPI32DLLVersion != NULL) {
fASPIVersion = GetASPI32DLLVersion();
LOGI(" ASPI version is %d.%d.%d.%d",
fASPIVersion & 0x0ff,
(fASPIVersion >> 8) & 0xff,
(fASPIVersion >> 16) & 0xff,
(fASPIVersion >> 24) & 0xff);
} else {
LOGI("ASPI WARNING: couldn't find GetASPI32DLLVersion interface");
}
/*
* Successfully loaded the library. Start it up and see if it works.
*/
aspiStatus = GetASPI32SupportInfo();
if (HIBYTE(LOWORD(aspiStatus)) != SS_COMP) {
LOGI("ASPI loaded but not working (status=%d)",
HIBYTE(LOWORD(aspiStatus)));
::FreeLibrary(fhASPI);
fhASPI = NULL;
return kDIErrASPIFailure;
}
fHostAdapterCount = LOBYTE(LOWORD(aspiStatus));
LOGI("ASPI loaded successfully, hostAdapterCount=%d",
fHostAdapterCount);
return kDIErrNone;
}
/*
* Destructor. Unload the ASPI DLL.
*/
ASPI::~ASPI(void)
{
if (fhASPI != NULL) {
LOGI("Unloading ASPI DLL");
::FreeLibrary(fhASPI);
fhASPI = NULL;
}
}
/*
* Issue an ASPI host adapter inquiry request for the specified adapter.
*
* Pass in a pointer to a struct that receives the result.
*/
DIError ASPI::HostAdapterInquiry(unsigned char adapter, AdapterInfo* pAdapterInfo)
{
SRB_HAInquiry req;
DWORD result;
assert(adapter >= 0 && adapter < kMaxAdapters);
memset(&req, 0, sizeof(req));
req.SRB_Cmd = SC_HA_INQUIRY;
req.SRB_HaId = adapter;
result = SendASPI32Command(&req);
if (result != SS_COMP) {
LOGI("ASPI(SC_HA_INQUIRY on %d) failed with result=0x%lx",
adapter, result);
return kDIErrASPIFailure;
}
pAdapterInfo->adapterScsiID = req.HA_SCSI_ID;
memcpy(pAdapterInfo->managerID, req.HA_ManagerId,
sizeof(pAdapterInfo->managerID)-1);
pAdapterInfo->managerID[sizeof(pAdapterInfo->managerID)-1] = '\0';
memcpy(pAdapterInfo->identifier, req.HA_Identifier,
sizeof(pAdapterInfo->identifier)-1);
pAdapterInfo->identifier[sizeof(pAdapterInfo->identifier)-1] = '\0';
pAdapterInfo->maxTargets = req.HA_Unique[3];
pAdapterInfo->bufferAlignment =
(unsigned short) req.HA_Unique[1] << 8 | req.HA_Unique[0];
return kDIErrNone;
}
/*
* Issue an ASPI query on device type.
*/
DIError ASPI::GetDeviceType(unsigned char adapter, unsigned char target,
unsigned char lun, unsigned char* pType)
{
SRB_GDEVBlock req;
DWORD result;
assert(adapter >= 0 && adapter < kMaxAdapters);
assert(target >= 0 && target < kMaxTargets);
assert(lun >= 0 && lun < kMaxLuns);
assert(pType != NULL);
memset(&req, 0, sizeof(req));
req.SRB_Cmd = SC_GET_DEV_TYPE;
req.SRB_HaId = adapter;
req.SRB_Target = target;
req.SRB_Lun = lun;
result = SendASPI32Command(&req);
if (result != SS_COMP)
return kDIErrASPIFailure;
*pType = req.SRB_DeviceType;
return kDIErrNone;
}
/*
* Return a printable string for the given device type.
*/
const char* ASPI::DeviceTypeToString(unsigned char deviceType)
{
switch (deviceType) {
case kScsiDevTypeDASD: return "Disk device";
case kScsiDevTypeSEQD: return "Tape device";
case kScsiDevTypePRNT: return "Printer";
case kScsiDevTypePROC: return "Processor";
case kScsiDevTypeWORM: return "Write-once read-multiple";
case kScsiDevTypeCDROM: return "CD-ROM device";
case kScsiDevTypeSCAN: return "Scanner device";
case kScsiDevTypeOPTI: return "Optical memory device";
case kScsiDevTypeJUKE: return "Medium changer device";
case kScsiDevTypeCOMM: return "Communications device";
case kScsiDevTypeUNKNOWN: return "Unknown or no device type";
default: return "Invalid type";
}
}
/*
* Issue a SCSI device inquiry and return the interesting parts.
*/
DIError ASPI::DeviceInquiry(unsigned char adapter, unsigned char target,
unsigned char lun, Inquiry* pInquiry)
{
DIError dierr;
SRB_ExecSCSICmd srb;
CDB6Inquiry* pCDB;
unsigned char buf[96]; // enough to hold everything of interest, and more
CDB_InquiryData* pInqData = (CDB_InquiryData*) buf;
assert(sizeof(CDB6Inquiry) == 6);
memset(&srb, 0, sizeof(srb));
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
srb.SRB_HaId = adapter;
srb.SRB_Target = target;
srb.SRB_Lun = lun;
srb.SRB_Flags = SRB_DIR_IN;
srb.SRB_BufLen = sizeof(buf);
srb.SRB_BufPointer = buf;
srb.SRB_SenseLen = SENSE_LEN;
srb.SRB_CDBLen = sizeof(*pCDB);
pCDB = (CDB6Inquiry*) srb.CDBByte;
pCDB->operationCode = kScsiOpInquiry;
pCDB->allocationLength = sizeof(buf);
// Don't set pCDB->logicalUnitNumber. It's only there for SCSI-1
// devices. SCSI-2 uses an IDENTIFY command; I gather ASPI is doing
// this for us.
dierr = ExecSCSICommand(&srb);
if (dierr != kDIErrNone)
return dierr;
memcpy(pInquiry->vendorID, pInqData->vendorId,
sizeof(pInquiry->vendorID)-1);
pInquiry->vendorID[sizeof(pInquiry->vendorID)-1] = '\0';
memcpy(pInquiry->productID, pInqData->productId,
sizeof(pInquiry->productID)-1);
pInquiry->productID[sizeof(pInquiry->productID)-1] = '\0';
pInquiry->productRevision[0] = pInqData->productRevisionLevel[0];
pInquiry->productRevision[1] = pInqData->productRevisionLevel[1];
pInquiry->productRevision[2] = pInqData->productRevisionLevel[2];
pInquiry->productRevision[3] = pInqData->productRevisionLevel[3];
return kDIErrNone;
}
/*
* Get the capacity of a SCSI block device.
*/
DIError ASPI::GetDeviceCapacity(unsigned char adapter, unsigned char target,
unsigned char lun, unsigned long* pLastBlock, unsigned long* pBlockSize)
{
DIError dierr;
SRB_ExecSCSICmd srb;
CDB10* pCDB;
CDB_ReadCapacityData dataBuf;
assert(sizeof(dataBuf) == 8); // READ CAPACITY returns two longs
assert(sizeof(CDB10) == 10);
memset(&srb, 0, sizeof(srb));
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
srb.SRB_HaId = adapter;
srb.SRB_Target = target;
srb.SRB_Lun = lun;
srb.SRB_Flags = SRB_DIR_IN;
srb.SRB_BufLen = sizeof(dataBuf);
srb.SRB_BufPointer = (unsigned char*)&dataBuf;
srb.SRB_SenseLen = SENSE_LEN;
srb.SRB_CDBLen = sizeof(*pCDB);
pCDB = (CDB10*) srb.CDBByte;
pCDB->operationCode = kScsiOpReadCapacity;
// rest of CDB is zero
dierr = ExecSCSICommand(&srb);
if (dierr != kDIErrNone)
return dierr;
*pLastBlock =
(unsigned long) dataBuf.logicalBlockAddr0 << 24 |
(unsigned long) dataBuf.logicalBlockAddr1 << 16 |
(unsigned long) dataBuf.logicalBlockAddr2 << 8 |
(unsigned long) dataBuf.logicalBlockAddr3;
*pBlockSize =
(unsigned long) dataBuf.bytesPerBlock0 << 24 |
(unsigned long) dataBuf.bytesPerBlock1 << 16 |
(unsigned long) dataBuf.bytesPerBlock2 << 8 |
(unsigned long) dataBuf.bytesPerBlock3;
return kDIErrNone;
}
/*
* Test to see if a device is ready.
*
* Returns "true" if the device is ready, "false" if not.
*/
DIError ASPI::TestUnitReady(unsigned char adapter, unsigned char target,
unsigned char lun, bool* pReady)
{
DIError dierr;
SRB_ExecSCSICmd srb;
CDB6* pCDB;
assert(sizeof(CDB6) == 6);
memset(&srb, 0, sizeof(srb));
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
srb.SRB_HaId = adapter;
srb.SRB_Target = target;
srb.SRB_Lun = lun;
srb.SRB_Flags = 0; //SRB_DIR_IN;
srb.SRB_BufLen = 0;
srb.SRB_BufPointer = NULL;
srb.SRB_SenseLen = SENSE_LEN;
srb.SRB_CDBLen = sizeof(*pCDB);
pCDB = (CDB6*) srb.CDBByte;
pCDB->operationCode = kScsiOpTestUnitReady;
// rest of CDB is zero
dierr = ExecSCSICommand(&srb);
if (dierr != kDIErrNone) {
const CDB_SenseData* pSense = (const CDB_SenseData*) srb.SenseArea;
if (srb.SRB_TargStat == kScsiStatCheckCondition &&
pSense->senseKey == kScsiSenseNotReady)
{
// expect pSense->additionalSenseCode to be
// kScsiAdSenseNoMediaInDevice; no need to check it really.
LOGI(" ASPI TestUnitReady: drive %d:%d:%d is NOT ready",
adapter, target, lun);
} else {
LOGI(" ASPI TestUnitReady failed, status=0x%02x sense=0x%02x ASC=0x%02x",
srb.SRB_TargStat, pSense->senseKey,
pSense->additionalSenseCode);
}
*pReady = false;
} else {
const CDB_SenseData* pSense = (const CDB_SenseData*) srb.SenseArea;
LOGI(" ASPI TestUnitReady: drive %d:%d:%d is ready",
adapter, target, lun);
//LOGI(" status=0x%02x sense=0x%02x ASC=0x%02x",
// srb.SRB_TargStat, pSense->senseKey, pSense->additionalSenseCode);
*pReady = true;
}
return kDIErrNone;
}
/*
* Read one or more blocks from the device.
*
* The block size is going to be whatever the device's native size is
* (possibly modified by extents, but we'll ignore that). For a CD-ROM
* this means 2048-byte blocks.
*/
DIError ASPI::ReadBlocks(unsigned char adapter, unsigned char target,
unsigned char lun, long startBlock, short numBlocks, long blockSize,
void* buf)
{
SRB_ExecSCSICmd srb;
CDB10* pCDB;
//LOGI(" ASPI ReadBlocks start=%ld num=%d (size=%d)",
// startBlock, numBlocks, blockSize);
assert(sizeof(CDB10) == 10);
assert(startBlock >= 0);
assert(numBlocks > 0);
assert(buf != NULL);
memset(&srb, 0, sizeof(srb));
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
srb.SRB_HaId = adapter;
srb.SRB_Target = target;
srb.SRB_Lun = lun;
srb.SRB_Flags = SRB_DIR_IN;
srb.SRB_BufLen = numBlocks * blockSize;
srb.SRB_BufPointer = (unsigned char*)buf;
srb.SRB_SenseLen = SENSE_LEN;
srb.SRB_CDBLen = sizeof(*pCDB);
pCDB = (CDB10*) srb.CDBByte;
pCDB->operationCode = kScsiOpRead;
pCDB->logicalBlockAddr0 = (unsigned char) (startBlock >> 24); // MSB
pCDB->logicalBlockAddr1 = (unsigned char) (startBlock >> 16);
pCDB->logicalBlockAddr2 = (unsigned char) (startBlock >> 8);
pCDB->logicalBlockAddr3 = (unsigned char) startBlock; // LSB
pCDB->transferLength0 = (unsigned char) (numBlocks >> 8); // MSB
pCDB->transferLength1 = (unsigned char) numBlocks; // LSB
return ExecSCSICommand(&srb);
}
/*
* Write one or more blocks to the device.
*/
DIError ASPI::WriteBlocks(unsigned char adapter, unsigned char target,
unsigned char lun, long startBlock, short numBlocks, long blockSize,
const void* buf)
{
SRB_ExecSCSICmd srb;
CDB10* pCDB;
LOGI(" ASPI WriteBlocks start=%ld num=%d (size=%d)",
startBlock, numBlocks, blockSize);
assert(sizeof(CDB10) == 10);
assert(startBlock >= 0);
assert(numBlocks > 0);
assert(buf != NULL);
memset(&srb, 0, sizeof(srb));
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
srb.SRB_HaId = adapter;
srb.SRB_Target = target;
srb.SRB_Lun = lun;
srb.SRB_Flags = SRB_DIR_IN;
srb.SRB_BufLen = numBlocks * blockSize;
srb.SRB_BufPointer = (unsigned char*)buf;
srb.SRB_SenseLen = SENSE_LEN;
srb.SRB_CDBLen = sizeof(*pCDB);
pCDB = (CDB10*) srb.CDBByte;
pCDB->operationCode = kScsiOpWrite;
pCDB->logicalBlockAddr0 = (unsigned char) (startBlock >> 24); // MSB
pCDB->logicalBlockAddr1 = (unsigned char) (startBlock >> 16);
pCDB->logicalBlockAddr2 = (unsigned char) (startBlock >> 8);
pCDB->logicalBlockAddr3 = (unsigned char) startBlock; // LSB
pCDB->transferLength0 = (unsigned char) (numBlocks >> 8); // MSB
pCDB->transferLength1 = (unsigned char) numBlocks; // LSB
return ExecSCSICommand(&srb);
}
/*
* Execute a SCSI command.
*
* Returns an error if ASPI reports an error or the SCSI status isn't
* kScsiStatGood.
*
* The Nero ASPI layer typically returns immediately, and hands back an
* SS_ERR when something fails. Win98 ASPI does the SS_PENDING thang.
*/
DIError ASPI::ExecSCSICommand(SRB_ExecSCSICmd* pSRB)
{
HANDLE completionEvent = NULL;
DWORD eventStatus;
DWORD aspiStatus;
assert(pSRB->SRB_Cmd == SC_EXEC_SCSI_CMD);
assert(pSRB->SRB_Flags == SRB_DIR_IN ||
pSRB->SRB_Flags == SRB_DIR_OUT ||
pSRB->SRB_Flags == 0);
/*
* Set up event-waiting stuff, as described in the Adaptec ASPI docs.
*/
pSRB->SRB_Flags |= SRB_EVENT_NOTIFY;
completionEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if (completionEvent == NULL) {
LOGI("Failed creating a completion event?");
return kDIErrGeneric;
}
pSRB->SRB_PostProc = completionEvent;
/*
* Send the request.
*/
(void)SendASPI32Command((LPSRB) pSRB);
aspiStatus = pSRB->SRB_Status;
if (aspiStatus == SS_PENDING) {
//LOGI(" (waiting for completion)");
eventStatus = ::WaitForSingleObject(completionEvent, kTimeout * 1000);
::CloseHandle(completionEvent);
if (eventStatus == WAIT_TIMEOUT) {
LOGI(" ASPI exec timed out!");
return kDIErrSCSIFailure;
} else if (eventStatus != WAIT_OBJECT_0) {
LOGI(" ASPI exec returned weird wait state %ld", eventStatus);
return kDIErrGeneric;
}
}
/*
* Check the final status.
*/
aspiStatus = pSRB->SRB_Status;
if (aspiStatus == SS_COMP) {
/* success! */
} else if (aspiStatus == SS_ERR) {
const CDB_SenseData* pSense = (const CDB_SenseData*) pSRB->SenseArea;
LOGI(" ASPI SCSI command 0x%02x failed: scsiStatus=0x%02x"
" senseKey=0x%02x ASC=0x%02x\n",
pSRB->CDBByte[0], pSRB->SRB_TargStat,
pSense->senseKey, pSense->additionalSenseCode);
return kDIErrSCSIFailure;
} else {
// SS_ABORTED, SS_ABORT_FAIL, SS_NO_DEVICE, ...
LOGI(" ASPI failed on command 0x%02x: aspiStatus=%d scsiStatus=%d",
pSRB->CDBByte[0], aspiStatus, pSRB->SRB_TargStat);
return kDIErrASPIFailure;
}
return kDIErrNone;
}
/*
* Return an array of accessible devices we found.
*
* Only return the devices matching device types in "deviceMask".
*/
DIError ASPI::GetAccessibleDevices(int deviceMask, ASPIDevice** ppDeviceArray,
int* pNumDevices)
{
DIError dierr;
ASPIDevice* deviceArray = NULL;
int idx = 0;
assert(deviceMask != 0);
assert((deviceMask & ~(kDevMaskCDROM | kDevMaskHardDrive)) == 0);
assert(ppDeviceArray != NULL);
assert(pNumDevices != NULL);
deviceArray = new ASPIDevice[kMaxAccessibleDrives];
if (deviceArray == NULL)
return kDIErrMalloc;
LOGI("ASPI scanning %d host adapters", fHostAdapterCount);
for (int ha = 0; ha < fHostAdapterCount; ha++) {
AdapterInfo adi;
dierr = HostAdapterInquiry(ha, &adi);
if (dierr != kDIErrNone) {
LOGI(" ASPI inquiry on %d failed", ha);
continue;
}
LOGI(" ASPI host adapter %d (SCSI ID=%d)", ha, adi.adapterScsiID);
LOGI(" identifier='%s' managerID='%s'",
adi.identifier, adi.managerID);
LOGI(" maxTargets=%d bufferAlignment=%d",
adi.maxTargets, adi.bufferAlignment);
int maxTargets = adi.maxTargets;
if (!maxTargets) {
/* Win98 ASPI reports zero here for ATAPI */
maxTargets = 8;
}
if (maxTargets > kMaxTargets)
maxTargets = kMaxTargets;
for (int targ = 0; targ < maxTargets; targ++) {
for (int lun = 0; lun < kMaxLuns; lun++) {
Inquiry inq;
unsigned char deviceType;
char addrString[48];
bool deviceReady;
dierr = GetDeviceType(ha, targ, lun, &deviceType);
if (dierr != kDIErrNone)
continue;
sprintf(addrString, "%d:%d:%d", ha, targ, lun);
dierr = DeviceInquiry(ha, targ, lun, &inq);
if (dierr != kDIErrNone) {
LOGI(" ASPI DeviceInquiry for '%s' (type=%d) failed",
addrString, deviceType);
continue;
}
LOGI(" Device %s is %s '%s' '%s'",
addrString, DeviceTypeToString(deviceType),
inq.vendorID, inq.productID);
if ((deviceMask & kDevMaskCDROM) != 0 &&
deviceType == kScsiDevTypeCDROM)
{
/* found CD-ROM */
} else if ((deviceMask & kDevMaskHardDrive) != 0 &&
deviceType == kScsiDevTypeDASD)
{
/* found hard drive */
} else
continue;
if (idx >= kMaxAccessibleDrives) {
LOGI("GLITCH: ran out of places to stuff CD-ROM drives");
assert(false);
goto done;
}
dierr = TestUnitReady(ha, targ, lun, &deviceReady);
if (dierr != kDIErrNone) {
LOGI(" ASPI TestUnitReady for '%s' failed", addrString);
continue;
}
deviceArray[idx].Init(ha, targ, lun, inq.vendorID,
inq.productID, deviceType, deviceReady);
idx++;
}
}
}
done:
*ppDeviceArray = deviceArray;
*pNumDevices = idx;
return kDIErrNone;
}
#endif /*_WIN32*/

202
diskimg/ASPI.h Normal file
View File

@ -0,0 +1,202 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* ASPI (Advanced SCSI Programming Interface) definitions.
*
* This may be included directly by an application. It must not be necessary
* to include the lower-level headers, e.g. wnaspi32.h.
*
* TODO: this was only necessary for older versions of Windows, e.g. Win98,
* as a way to access SCSI drives. It's no longer needed.
*/
#ifndef __ASPI__
#define __ASPI__
#if !defined(_WIN32) || !defined(WANT_ASPI)
/*
* Placeholder definition to keep Linux build happy.
*/
namespace DiskImgLib {
class DISKIMG_API ASPI {
public:
ASPI(void) {}
virtual ~ASPI(void) {}
};
};
#else
#ifndef __WNASPI32_H__
struct SRB_ExecSCSICmd; // fwd
#endif
namespace DiskImgLib {
/*
* Descriptor for one SCSI device.
*/
class DISKIMG_API ASPIDevice {
public:
ASPIDevice(void) : fVendorID(NULL), fProductID(NULL),
fAdapter(0xff), fTarget(0xff), fLun(0xff), fDeviceReady(false)
{}
virtual ~ASPIDevice(void) {
delete[] fVendorID;
delete[] fProductID;
}
void Init(unsigned char adapter, unsigned char target, unsigned char lun,
const unsigned char* vendor, unsigned const char* product,
int deviceType, bool ready)
{
fAdapter = adapter;
fTarget = target;
fLun = lun;
assert(fVendorID == NULL);
fVendorID = new char[strlen((const char*)vendor)+1];
strcpy(fVendorID, (const char*)vendor);
assert(fProductID == NULL);
fProductID = new char[strlen((const char*)product)+1];
strcpy(fProductID, (const char*)product);
fDeviceReady = ready;
fDeviceType = deviceType;
}
enum {
kTypeDASD = 0, // kScsiDevTypeDASD
kTypeCDROM = 5, // kScsiDevTypeCDROM
};
unsigned char GetAdapter(void) const { return fAdapter; }
unsigned char GetTarget(void) const { return fTarget; }
unsigned char GetLun(void) const { return fLun; }
const char* GetVendorID(void) const { return fVendorID; }
const char* GetProductID(void) const { return fProductID; }
bool GetDeviceReady(void) const { return fDeviceReady; }
int GetDeviceType(void) const { return fDeviceType; }
private:
// strings from SCSI inquiry, padded with spaces at end
char* fVendorID;
char* fProductID;
unsigned char fAdapter; // physical or logical host adapter (0-15)
unsigned char fTarget; // SCSI ID on adapter (0-15)
unsigned char fLun; // logical unit (0-7)
int fDeviceType; // e.g. kScsiDevTypeCDROM
bool fDeviceReady;
};
/*
* There should be only one instance of this in the library (part of the
* DiskImgLib Globals). It wouldn't actually break anything to have more than
* one, but there's no need for it.
*/
class DISKIMG_API ASPI {
public:
ASPI(void) :
fhASPI(NULL),
GetASPI32SupportInfo(NULL),
SendASPI32Command(NULL),
GetASPI32DLLVersion(NULL),
fASPIVersion(0),
fHostAdapterCount(-1)
{}
virtual ~ASPI(void);
// load ASPI DLL if it exists
DIError Init(void);
// return the version returned by the loaded DLL
DWORD GetVersion(void) const {
assert(fhASPI != NULL);
return fASPIVersion;
}
// Return an *array* of ASPIDevice structures for drives in system;
// the caller is expected to delete[] it when done.
enum { kDevMaskCDROM = 0x01, kDevMaskHardDrive = 0x02 };
DIError GetAccessibleDevices(int deviceMask, ASPIDevice** ppDeviceArray,
int* pNumDevices);
// Get the type of the device (DTYPE_*, 0x00-0x1f) using ASPI query
DIError GetDeviceType(unsigned char adapter, unsigned char target,
unsigned char lun, unsigned char* pType);
// convert DTYPE_* to a string
const char* DeviceTypeToString(unsigned char deviceType);
// Get the capacity, expressed as the highest-available LBA and the device
// block size.
DIError GetDeviceCapacity(unsigned char adapter, unsigned char target,
unsigned char lun, unsigned long* pLastBlock, unsigned long* pBlockSize);
// Read blocks from the device.
DIError ReadBlocks(unsigned char adapter, unsigned char target,
unsigned char lun, long startBlock, short numBlocks, long blockSize,
void* buf);
// Write blocks to the device.
DIError WriteBlocks(unsigned char adapter, unsigned char target,
unsigned char lun, long startBlock, short numBlocks, long blockSize,
const void* buf);
private:
/*
* The interesting bits that come out of an SC_HA_INQUIRY request.
*/
typedef struct AdapterInfo {
unsigned char adapterScsiID; // SCSI ID of the adapter itself
unsigned char managerID[16+1]; // string describing manager
unsigned char identifier[16+1]; // string describing host adapter
unsigned char maxTargets; // max #of targets on this adapter
unsigned short bufferAlignment; // buffer alignment requirement
} AdapterInfo;
/*
* The interesting bits from a SCSI device INQUIRY command.
*/
typedef struct Inquiry {
unsigned char vendorID[8+1]; // vendor ID string
unsigned char productID[16+1]; // product ID string
unsigned char productRevision[4]; // product revision bytes
} Inquiry;
// Issue an ASPI adapter inquiry request.
DIError HostAdapterInquiry(unsigned char adapter,
AdapterInfo* pAdapterInfo);
// Issue a SCSI device inquiry request.
DIError DeviceInquiry(unsigned char adapter, unsigned char target,
unsigned char lun, Inquiry* pInquiry);
// Issue a SCSI test unit ready request.
DIError TestUnitReady(unsigned char adapter, unsigned char target,
unsigned char lun, bool* pReady);
// execute a SCSI command
DIError ExecSCSICommand(SRB_ExecSCSICmd* pSRB);
enum {
kMaxAdapters = 16,
kMaxTargets = 16,
kMaxLuns = 8,
kTimeout = 30, // timeout, in seconds
kMaxAccessibleDrives = 16,
};
HMODULE fhASPI;
DWORD (*GetASPI32SupportInfo)(void);
DWORD (*SendASPI32Command)(void* lpsrb);
DWORD (*GetASPI32DLLVersion)(void);
DWORD fASPIVersion;
int fHostAdapterCount;
};
}; // namespace DiskImgLib
#endif /*__ASPI__*/
#endif /*_WIN32*/

591
diskimg/CFFA.cpp Normal file
View File

@ -0,0 +1,591 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* The "CFFA" DiskFS is a container class for multiple ProDOS and HFS volumes.
*
* The CFFA card doesn't have any RAM, so the author used a fixed partitioning
* scheme. You get 4 or 8 volumes -- depending on which firmware you jumper
* in -- at 32MB each. CF cards usually hold less than you would expect, so
* a 64MB card would have one 32MB volume and one less-than-32MB volume.
*
* With Dave's GS/OS driver, you get an extra drive or two at the end, at up
* to 1GB each. The driver only works in 4-volume mode.
*
* There is no magic CFFA block at the front, so it looks like a plain
* ProDOS or HFS volume. If the size is less than 32MB -- meaning there's
* only one volume -- we don't need to take an interest in the file,
* because the regular filesystem goodies will handle it just fine. If it's
* more than 32MB, we need to create a structure in which multiple volumes
* reside.
*
* The trick is finding all the volumes. The first four are easy. The
* fifth one is either another 32MB volume (if you're in 8-volume mode)
* or a volume whose size is somewhere between the amount of space left
* and 1GB. Not an issue until we get to CF cards > 128MB. We have to
* rely on the CFFA card making volumes as large as it can.
*
* I think it's reasonable to require that the first volume be either ProDOS
* or HFS. That way we don't go digging through large non-CFFA files when
* auto-probing.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* Figure out if this is a CFFA volume, and if so, whether it was formatted
* in 4-partition or 8-partition mode.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*
* "*pFormatFound" should be either a CFFA format or "unknown" on entry.
* If it's not "unknown", we will look for the specified format first.
* Otherwise, we look for 4-partition then 8-partition. The first one
* we find successfully is returned.
*
* Ideally we'd have some way to express ambiguity here, so that we could
* force the "disk format verification" dialog to come up. No such
* mechanism exists, and for now it doesn't seem worthwhile to add one.
*/
/*static*/ DIError DiskFSCFFA::TestImage(DiskImg* pImg,
DiskImg::SectorOrder imageOrder, DiskImg::FSFormat* pFormatFound)
{
DIError dierr;
long totalBlocks = pImg->GetNumBlocks();
long startBlock, maxBlocks, totalBlocksLeft;
long fsNumBlocks;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
//bool fiveIs32MB;
assert(totalBlocks > kEarlyVolExpectedSize);
// could be "generic" from an earlier override
if (*pFormatFound != DiskImg::kFormatCFFA4 &&
*pFormatFound != DiskImg::kFormatCFFA8)
{
*pFormatFound = DiskImg::kFormatUnknown;
}
LOGI("----- BEGIN CFFA SCAN (fmt=%d) -----", *pFormatFound);
startBlock = 0;
totalBlocksLeft = totalBlocks;
/*
* Look for a 32MB ProDOS or HFS volume in the first slot. If we
* don't find this, it's probably not CFFA. It's possible they just
* didn't format the first one, but that seems unlikely, and it's not
* unreasonable to insist that they format the first partition.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
LOGI(" CFFA failed opening sub-volume #1");
goto bail;
}
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
if (fsNumBlocks != kEarlyVolExpectedSize &&
fsNumBlocks != kEarlyVolExpectedSize-1)
{
LOGI(" CFFA found fsNumBlocks=%ld in slot #1", fsNumBlocks);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
LOGI(" CFFA found good volume in slot #1");
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
assert(totalBlocksLeft > 0);
/*
* Look for a ProDOS or HFS volume <= 32MB in the second slot. If
* we don't find something here, and this is a 64MB card, then there's
* no advantage to using CFFA (in fact, the single-volume handling may
* be more convenient). If there's at least another 32MB, we continue
* looking.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
LOGI(" CFFA failed opening sub-volume #2");
if (maxBlocks < kEarlyVolExpectedSize)
goto bail;
// otherwise, assume they just didn't format #2, and keep going
} else {
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
#if 0
if (fsNumBlocks != kEarlyVolExpectedSize &&
fsNumBlocks != kEarlyVolExpectedSize-1)
{
LOGI(" CFFA found fsNumBlocks=%ld in slot #2", fsNumBlocks);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
#endif
LOGI(" CFFA found good volume in slot #2");
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (totalBlocksLeft == 0) {
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* Skip #3 and #4.
*/
LOGI(" CFFA skipping over slot #3");
maxBlocks = kEarlyVolExpectedSize*2;
if (maxBlocks > totalBlocksLeft)
maxBlocks = totalBlocksLeft;
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (totalBlocksLeft == 0) {
// no more partitions to find; we're done
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
LOGI(" CFFA skipping over slot #4");
/*
* Partition #5. We know where it starts, but not how large it is.
* Could be 32MB, could be 1GB, could be anything between.
*
* CF cards come in power-of-two sizes -- 128MB, 256MB, etc. -- but
* we don't want to make assumptions here. It's possible we're
* looking at an odd-sized image file that some clever person is
* expecting to access with CiderPress.
*/
maxBlocks = totalBlocksLeft;
if (maxBlocks > kOneGB)
maxBlocks = kOneGB;
if (maxBlocks <= kEarlyVolExpectedSize) {
/*
* Only enough room for one <= 32MB volume. Not expected for a
* real CFFA card, unless they come in 160MB sizes.
*
* Treat it like 4-partition; it'll look like somebody slapped a
* 32MB volume into the first 1GB area.
*/
LOGI(" CFFA assuming odd-sized slot #5");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* We could be looking at a 32MB ProDOS partition, 32MB HFS partition,
* or an up to 1GB HFS partition. We have to specify the size in
* the OpenSubVolume request, which means trying it both ways and
* finding a match from GetFSNumBlocks(). Complicating matters is
* truncation (ProDOS max 65535, not 65536) and round-off (not sure
* how HFS deals with left-over blocks).
*
* Start with <= 1GB.
*/
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA failed opening large sub-volume #5");
// if we can't get #5, don't bother looking for #6
// (we could try anyway, but it's easier to just skip it)
dierr = kDIErrNone;
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
fsNumBlocks = pNewFS->GetFSNumBlocks();
delete pNewFS;
delete pNewImg;
if (fsNumBlocks < 2 || fsNumBlocks > maxBlocks) {
LOGI(" CFFA WARNING: FSNumBlocks #5 reported blocks=%ld",
fsNumBlocks);
}
if (fsNumBlocks == kEarlyVolExpectedSize-1 ||
fsNumBlocks == kEarlyVolExpectedSize)
{
LOGI(" CFFA #5 is a 32MB volume");
// tells us nothing -- could still be 4 or 8, so we keep going
maxBlocks = kEarlyVolExpectedSize;
} else if (fsNumBlocks > kEarlyVolExpectedSize) {
// must be a GS/OS 1GB area
LOGI(" CFFA #5 is larger than 32MB");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
} else {
LOGI(" CFFA #5 was unexpectedly small (%ld blocks)", fsNumBlocks);
// just stop now
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (!totalBlocksLeft) {
LOGI(" CFFA got 5 volumes");
*pFormatFound = DiskImg::kFormatCFFA4;
goto bail;
}
/*
* Various possibilities for slots 5 and up:
* A. Card in 4-partition mode. 5th partition isn't formatted. Don't
* bother looking for 6th. [already handled]
* B. Card in 4-partition mode. 5th partition is >32MB HFS. 6th
* partition will be at +1GB. [already handled]
* C. Card in 4-partition mode. 5th partition is 32MB ProDOS. 6th
* partition will be at +1GB.
* D. Card in 8-partition mode. 5th partition is 32MB HFS. 6th
* partition will be at +32MB.
* E. Card in 8-partition mode. 5th partition is 32MB ProDOS. 6th
* partition will be at +32MB.
*
* I'm ignoring D on the off chance somebody could create a 32MB HFS
* partition in the 1GB space. D and E are handled alike.
*
* The difference between C and D/E can *usually* be determined by
* opening up a 6th partition in the two expected locations.
*/
LOGI(" CFFA probing 6th slot for 4-vs-8");
/*
* Look in two different places. If we find something at the
* +32MB mark, assume it's in "8 mode". If we find something at
* the +1GB mark, assume it's in "4 + GS/OS mode". If both exist
* we have a problem.
*/
bool foundSmall, foundGig;
foundSmall = false;
maxBlocks = totalBlocksLeft;
if (maxBlocks > kEarlyVolExpectedSize)
maxBlocks = kEarlyVolExpectedSize;
dierr = OpenSubVolume(pImg, startBlock + kEarlyVolExpectedSize,
maxBlocks, true, &pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA no vol #6 found at +32MB");
} else {
foundSmall = true;
delete pNewFS;
delete pNewImg;
}
foundGig = false;
// no need to look if we don't have at least 1GB left!
if (totalBlocksLeft >= kOneGB) {
maxBlocks = totalBlocksLeft;
if (maxBlocks > kOneGB)
maxBlocks = kOneGB;
dierr = OpenSubVolume(pImg, startBlock + kOneGB,
maxBlocks, true, &pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA no vol #6 found at +1GB");
} else {
foundGig = true;
delete pNewFS;
delete pNewImg;
}
}
dierr = kDIErrNone;
if (!foundSmall && !foundGig) {
LOGI(" CFFA no valid filesystem found in 6th position");
*pFormatFound = DiskImg::kFormatCFFA4;
// don't bother looking for 7 and 8
} else if (foundSmall && foundGig) {
LOGI(" CFFA WARNING: found valid volumes at +32MB *and* +1GB");
// default to 4-partition mode
if (*pFormatFound == DiskImg::kFormatUnknown)
*pFormatFound = DiskImg::kFormatCFFA4;
} else if (foundGig) {
LOGI(" CFFA found 6th volume at +1GB, assuming 4-mode w/GSOS");
if (fsNumBlocks < 2 || fsNumBlocks > kOneGB) {
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
fsNumBlocks);
}
*pFormatFound = DiskImg::kFormatCFFA4;
} else if (foundSmall) {
LOGI(" CFFA found 6th volume at +32MB, assuming 8-mode");
if (fsNumBlocks < 2 || fsNumBlocks > kEarlyVolExpectedSize) {
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
fsNumBlocks);
}
*pFormatFound = DiskImg::kFormatCFFA8;
} else {
assert(false); // how'd we get here??
}
// done!
bail:
LOGI("----- END CFFA SCAN (err=%d format=%d) -----",
dierr, *pFormatFound);
if (dierr == kDIErrNone) {
assert(*pFormatFound != DiskImg::kFormatUnknown);
} else {
*pFormatFound = DiskImg::kFormatUnknown;
}
return dierr;
}
/*
* Open up a sub-volume.
*
* If "scanOnly" is set, the full DiskFS initialization isn't performed.
* We just do enough to get the volume size info.
*/
/*static*/ DIError DiskFSCFFA::OpenSubVolume(DiskImg* pImg, long startBlock,
long numBlocks, bool scanOnly, DiskImg** ppNewImg, DiskFS** ppNewFS)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(pImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" CFFASub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" CFFASub: analysis failed (err=%d)", dierr);
goto bail;
}
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" CFFASub: unable to identify filesystem at %ld",
startBlock);
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* open a DiskFS for the sub-image */
LOGI(" CFFASub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS();
if (pNewFS == NULL) {
LOGI(" CFFASub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* we encapsulate arbitrary stuff, so encourage child to scan */
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
/*
* Load the files from the sub-image. When doing our initial tests,
* or when loading data for the volume copier, we don't want to dig
* into our sub-volumes, just figure out what they are and where.
*/
InitMode initMode;
if (scanOnly)
initMode = kInitHeaderOnly;
else
initMode = kInitFull;
dierr = pNewFS->Initialize(pNewImg, initMode);
if (dierr != kDIErrNone) {
LOGI(" CFFASub: error %d reading list of files from disk", dierr);
goto bail;
}
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
} else {
*ppNewImg = pNewImg;
*ppNewFS = pNewFS;
}
return dierr;
}
/*
* Check to see if this is a CFFA volume.
*/
/*static*/ DIError DiskFSCFFA::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for CFFA inside CFFA!
return kDIErrFilesystemNotFound;
/* assume ProDOS -- shouldn't matter, since it's embedded */
if (TestImage(pImg, DiskImg::kSectorOrderProDOS, pFormat) == kDIErrNone) {
assert(*pFormat == DiskImg::kFormatCFFA4 ||
*pFormat == DiskImg::kFormatCFFA8);
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
// make sure we didn't tamper with it
assert(*pFormat == DiskImg::kFormatUnknown);
LOGI(" FS didn't find valid CFFA");
return kDIErrFilesystemNotFound;
}
/*
* Prep the CFFA "container" for use.
*/
DIError DiskFSCFFA::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("CFFA initializing (scanForSub=%d)", fScanForSubVolumes);
/* seems pointless *not* to, but we just do what we're told */
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = FindSubVolumes();
if (dierr != kDIErrNone)
return dierr;
}
/* blank out the volume usage map */
SetVolumeUsageMap();
return dierr;
}
/*
* Find the various sub-volumes and open them.
*
* We don't handle the volume specially unless it's at least 32MB, which
* means there are at least 2 partitions.
*/
DIError DiskFSCFFA::FindSubVolumes(void)
{
DIError dierr;
long startBlock, blocksLeft;
startBlock = 0;
blocksLeft = fpImg->GetNumBlocks();
if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA4) {
LOGI(" CFFA opening 4+2 volumes");
dierr = AddVolumeSeries(0, 4, kEarlyVolExpectedSize, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
LOGI(" CFFA after first 4, startBlock=%ld blocksLeft=%ld",
startBlock, blocksLeft);
if (blocksLeft > 0) {
dierr = AddVolumeSeries(4, 2, kOneGB, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
}
} else if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA8) {
LOGI(" CFFA opening 8 volumes");
dierr = AddVolumeSeries(0, 8, kEarlyVolExpectedSize, /*ref*/startBlock,
/*ref*/blocksLeft);
if (dierr != kDIErrNone)
goto bail;
} else {
assert(false);
return kDIErrInternal;
}
if (blocksLeft != 0) {
LOGI(" CFFA ignoring leftover %ld blocks", blocksLeft);
}
bail:
return dierr;
}
/*
* Add a series of equal-sized volumes.
*
* Updates "startBlock" and "totalBlocksLeft".
*/
DIError DiskFSCFFA::AddVolumeSeries(int start, int count, long blocksPerVolume,
long& startBlock, long& totalBlocksLeft)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
long maxBlocks, fsNumBlocks;
bool scanOnly = false;
/* used by volume copier, to avoid deep scan */
if (GetScanForSubVolumes() == kScanSubContainerOnly)
scanOnly = true;
for (int i = start; i < start+count; i++) {
maxBlocks = blocksPerVolume;
if (maxBlocks > totalBlocksLeft)
maxBlocks = totalBlocksLeft;
dierr = OpenSubVolume(fpImg, startBlock, maxBlocks, scanOnly,
&pNewImg, &pNewFS);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
LOGI(" CFFA failed opening sub-volume %d (not formatted?)", i);
/* create a fake one to represent the partition */
dierr = CreatePlaceholder(startBlock, maxBlocks, NULL, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGI(" CFFA unable to create placeholder (%ld, %ld) (err=%d)",
startBlock, maxBlocks, dierr);
goto bail;
}
} else {
fsNumBlocks = pNewFS->GetFSNumBlocks();
if (fsNumBlocks < 2 || fsNumBlocks > blocksPerVolume) {
LOGI(" CFFA WARNING: FSNumBlocks #%d reported as %ld",
i, fsNumBlocks);
}
AddSubVolumeToList(pNewImg, pNewFS);
}
startBlock += maxBlocks;
totalBlocksLeft -= maxBlocks;
if (!totalBlocksLeft)
break; // all done
}
bail:
return dierr;
}

75
diskimg/CMakeLists.txt Normal file
View File

@ -0,0 +1,75 @@
cmake_minimum_required(VERSION 3.0)
set(CMAKE_BUILD_TYPE DEBUG)
set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(PROJECT_NAME diskimg)
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
project(${PROJECT_NAME})
set(ALL_DEFINES " " )
set(DEBUG_OPT "-D_DEBUG -DDEBUG -O0 -g3 " )
set(RELEASE_OPT "-O3 " )
set(CMAKE_C_FLAGS "-Wall ${ALL_DEFINES} ")
set(CMAKE_CXX_FLAGS "-Wall ${ALL_DEFINES} ")
set(CMAKE_CXX_FLAGS_DEBUG "${DEBUG_OPT} ")
set(CMAKE_CXX_FLAGS_RELEASE "${RELEASE_OPT}")
set(CMAKE_C_FLAGS_DEBUG "${DEBUG_OPT} ")
set(CMAKE_C_FLAGS_RELEASE "${RELEASE_OPT}")
set(FIND_LIBRARY_USE_LIB64_PATHS TRUE)
#add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/libhfs)
set (SOURCE
ASPI.cpp
DiskFS.cpp
FAT.cpp
Gutenberg.cpp
Nibble35.cpp
ProDOS.cpp
UNIDOS.cpp
CFFA.cpp
DiskImg.cpp
FDI.cpp
HFS.cpp
Nibble.cpp
RDOS.cpp
VolumeUsage.cpp
Container.cpp
DIUtil.cpp
FocusDrive.cpp
ImageWrapper.cpp
OuterWrapper.cpp
SPTI.cpp
Win32BlockIO.cpp
CPM.cpp
DOS33.cpp
GenericFD.cpp
MacPart.cpp
OzDOS.cpp
StdAfx.cpp
DDD.cpp
DOSImage.cpp
Global.cpp
MicroDrive.cpp
Pascal.cpp
TwoImg.cpp
)
include_directories(BEFORE
${PROJECT_ROOT}
)
add_library( ${PROJECT_NAME} SHARED ${SOURCE})
add_library( ${PROJECT_NAME}_static STATIC ${SOURCE})
target_link_libraries (
${PROJECT_NAME}
)

763
diskimg/CPM.cpp Normal file
View File

@ -0,0 +1,763 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Apple II CP/M disk format.
*
* Limitations:
* - Read-only.
* - Does not do much with user numbers.
* - Rumor has it that "sparse" files are possible. Not handled.
* - I'm currently treating the directory as fixed-length. This may
* not be correct.
* - Not handling special entries (volume label, date stamps,
* password control).
*
* As I have no practical experience with CP/M, this is the weakest of the
* filesystem implementations.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSCPM
* ===========================================================================
*/
const int kBlkSize = 512; // really ought to be 1024
const int kVolDirBlock = 24; // track 3 sector 0
const int kVolDirCount = 4; // 4 prodos blocks
const int kNoDataByte = 0xe5;
const int kMaxUserNumber = 31; // 0-15 on some systems, 0-31 on others
const int kMaxSpecialUserNumber = 0x21; // 0x20 and 0x21 have special meanings
const int kMaxExtent = 31; // extent counter, 0-31
/*
* See if this looks like a CP/M volume.
*
* We test a few fields in the volume directory for validity.
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t dirBuf[kBlkSize * kVolDirCount];
uint8_t* dptr;
int i;
assert(sizeof(dirBuf) == DiskFSCPM::kFullDirSize);
for (i = 0; i < kVolDirCount; i++) {
dierr = pImg->ReadBlockSwapped(kVolDirBlock + i, dirBuf + kBlkSize*i,
imageOrder, DiskImg::kSectorOrderCPM);
if (dierr != kDIErrNone)
goto bail;
}
dptr = dirBuf;
for (i = 0; i < DiskFSCPM::kFullDirSize/DiskFSCPM::kDirectoryEntryLen; i++)
{
if (*dptr != kNoDataByte) {
/*
* Usually userNumber is 0, but sometimes not. It's expected to
* be < 0x20 for a normal file, may be 0x21 or 0x22 for special
* entries (volume label, date stamps).
*/
if (*dptr > kMaxSpecialUserNumber) {
dierr = kDIErrFilesystemNotFound;
break;
}
/* extent counter, 0-31 */
if (dptr[12] > kMaxExtent) {
dierr = kDIErrFilesystemNotFound;
break;
}
/* check for a valid filename here; high bit may be set on some bytes */
uint8_t firstLet = *(dptr+1) & 0x7f;
if (firstLet < 0x20) {
dierr = kDIErrFilesystemNotFound;
break;
}
}
dptr += DiskFSCPM::kDirectoryEntryLen;
}
if (dierr == kDIErrNone) {
LOGI(" CPM found clean directory, imageOrder=%d", imageOrder);
}
bail:
return dierr;
}
/*
* Test to see if the image is a CP/M disk.
*
* On the Apple II, these were always on 5.25" disks. However, it's possible
* to create hard drive volumes up to 8MB.
*/
/*static*/ DIError DiskFSCPM::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* CP/M disks use 1K blocks, so ignore anything with odd count */
if (pImg->GetNumBlocks() == 0 ||
(pImg->GetNumBlocks() & 0x01) != 0)
{
LOGI(" CPM rejecting image with numBlocks=%ld",
pImg->GetNumBlocks());
return kDIErrFilesystemNotFound;
}
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
*pOrder = ordering[i];
*pFormat = DiskImg::kFormatCPM;
return kDIErrNone;
}
}
LOGI(" CPM didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Get things rolling.
*
* Since we're assured that this is a valid disk, errors encountered from here
* on out must be handled somehow, possibly by claiming that the disk is
* completely full and has no files on it.
*/
DIError DiskFSCPM::Initialize(void)
{
DIError dierr = kDIErrNone;
dierr = ReadCatalog();
if (dierr != kDIErrNone)
goto bail;
fVolumeUsage.Create(fpImg->GetNumBlocks());
dierr = ScanFileUsage();
if (dierr != kDIErrNone) {
/* this might not be fatal; just means that *some* files are bad */
dierr = kDIErrNone;
goto bail;
}
fDiskIsGood = CheckDiskIsGood();
fVolumeUsage.Dump();
//A2File* pFile;
//pFile = GetNextFile(NULL);
//while (pFile != NULL) {
// pFile->Dump();
// pFile = GetNextFile(pFile);
//}
bail:
return dierr;
}
/*
* Read the entire CP/M catalog (all 2K of it) into memory, and parse
* out the individual files.
*
* A single file can have more than one directory entry. We only want
* to create an A2File object for the first one.
*/
DIError DiskFSCPM::ReadCatalog(void)
{
DIError dierr = kDIErrNone;
uint8_t dirBuf[kFullDirSize];
uint8_t* dptr;
int i;
for (i = 0; i < kVolDirCount; i++) {
dierr = fpImg->ReadBlock(kVolDirBlock + i, dirBuf + kBlkSize*i);
if (dierr != kDIErrNone)
goto bail;
}
dptr = dirBuf;
for (i = 0; i < kNumDirEntries; i++) {
fDirEntry[i].userNumber = dptr[0x00];
/* copy the filename, stripping the high bits off */
for (int j = 0; j < kDirFileNameLen; j++)
fDirEntry[i].fileName[j] = dptr[0x01 + j] & 0x7f;
fDirEntry[i].fileName[kDirFileNameLen] = '\0';
fDirEntry[i].extent = dptr[0x0c] + dptr[0x0e] * kExtentsInLowByte;
fDirEntry[i].S1 = dptr[0x0d];
fDirEntry[i].records = dptr[0x0f];
memcpy(fDirEntry[i].blocks, &dptr[0x10], kDirEntryBlockCount);
fDirEntry[i].readOnly = (dptr[0x09] & 0x80) != 0;
fDirEntry[i].system = (dptr[0x0a] & 0x80) != 0;
fDirEntry[i].badBlockList = false; // set if block list is bad
dptr += kDirectoryEntryLen;
}
/* create an entry for the first extent of each file */
for (i = 0; i < kNumDirEntries; i++) {
A2FileCPM* pFile;
if (fDirEntry[i].userNumber == kNoDataByte || fDirEntry[i].extent != 0)
continue;
if (fDirEntry[i].userNumber > kMaxUserNumber) {
/* skip over volume label, date stamps, etc */
LOGI("Skipping entry with userNumber=0x%02x",
fDirEntry[i].userNumber);
}
pFile = new A2FileCPM(this, fDirEntry);
FormatName(pFile->fFileName, (char*)fDirEntry[i].fileName);
pFile->fReadOnly = fDirEntry[i].readOnly;
pFile->fDirIdx = i;
pFile->fLength = 0;
dierr = ComputeLength(pFile);
if (dierr != kDIErrNone) {
pFile->SetQuality(A2File::kQualityDamaged);
dierr = kDIErrNone;
}
AddFileToList(pFile);
}
/*
* Validate the list of blocks.
*/
int maxCpmBlock;
maxCpmBlock = (fpImg->GetNumBlocks() - kVolDirBlock) / 2;
for (i = 0; i < kNumDirEntries; i++) {
if (fDirEntry[i].userNumber == kNoDataByte)
continue;
for (int j = 0; j < kDirEntryBlockCount; j++) {
if (fDirEntry[i].blocks[j] >= maxCpmBlock) {
LOGI(" CPM invalid block %d in file '%s'",
fDirEntry[i].blocks[j], fDirEntry[i].fileName);
//pFile->SetQuality(A2File::kQualityDamaged);
fDirEntry[i].badBlockList = true;
break;
}
}
}
bail:
return dierr;
}
/*
* Reformat from 11 chars with spaces into clean xxxxx.yyy format.
*/
void DiskFSCPM::FormatName(char* dstBuf, const char* srcBuf)
{
char workBuf[kDirFileNameLen+1];
char* cp;
assert(strlen(srcBuf) < sizeof(workBuf));
strcpy(workBuf, srcBuf);
cp = workBuf;
while (*cp != '\0') {
//*cp &= 0x7f; // [no longer necessary]
if (*cp == ' ')
*cp = '\0';
if (*cp == ':') // don't think this is allowed, but check
*cp = 'X'; // for it anyway
cp++;
}
strcpy(dstBuf, workBuf);
dstBuf[8] = '\0'; // in case filename part is full 8 chars
strcat(dstBuf, ".");
strcat(dstBuf, workBuf+8);
assert(strlen(dstBuf) <= A2FileCPM::kMaxFileName);
}
/*
* Compute the length of a file. Sets "pFile->fLength".
*
* This requires walking through the list of extents and looking for the
* last one. We use the "records" field of the last extent to determine
* the file length.
*
* (Should probably just get the block list and then walk that, rather than
* having directory parse code in two places.)
*/
DIError DiskFSCPM::ComputeLength(A2FileCPM* pFile)
{
int i;
int best, maxExtent;
best = maxExtent = -1;
for (i = 0; i < DiskFSCPM::kNumDirEntries; i++) {
if (fDirEntry[i].userNumber == kNoDataByte)
continue;
if (strcmp((const char*)fDirEntry[i].fileName,
(const char*)fDirEntry[pFile->fDirIdx].fileName) == 0 &&
fDirEntry[i].userNumber == fDirEntry[pFile->fDirIdx].userNumber)
{
/* this entry is part of the file */
if (fDirEntry[i].extent > maxExtent) {
best = i;
maxExtent = fDirEntry[i].extent;
}
}
}
if (maxExtent < 0 || best < 0) {
LOGI(" CPM couldn't find existing file '%s'!", pFile->fFileName);
assert(false);
return kDIErrInternal;
}
pFile->fLength = kDirEntryBlockCount * 1024 * maxExtent +
fDirEntry[best].records * 128;
return kDIErrNone;
}
/*
* Scan file usage into the volume usage map.
*
* Tracks 0, 1, and 2 are always used by the boot loader. The volume directory
* is on the first half of track 3 (blocks 0 and 1).
*/
DIError DiskFSCPM::ScanFileUsage(void)
{
int cpmBlock;
int i, j;
for (i = 0; i < kVolDirBlock; i++)
SetBlockUsage(i, VolumeUsage::kChunkPurposeSystem);
for (i = kVolDirBlock; i < kVolDirBlock + kVolDirCount; i++)
SetBlockUsage(i, VolumeUsage::kChunkPurposeVolumeDir);
for (i = 0; i < kNumDirEntries; i++) {
if (fDirEntry[i].userNumber == kNoDataByte)
continue;
if (fDirEntry[i].badBlockList)
continue;
for (j = 0; j < kDirEntryBlockCount; j++) {
cpmBlock = fDirEntry[i].blocks[j];
if (cpmBlock == 0)
break;
SetBlockUsage(CPMToProDOSBlock(cpmBlock),
VolumeUsage::kChunkPurposeUserData);
SetBlockUsage(CPMToProDOSBlock(cpmBlock)+1,
VolumeUsage::kChunkPurposeUserData);
}
}
return kDIErrNone;
}
/*
* Update an entry in the usage map.
*
* "block" is a 512-byte block, so you will have to call here twice for every
* 1K CP/M block.
*/
void DiskFSCPM::SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose)
{
VolumeUsage::ChunkState cstate;
if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) {
LOGI(" CPM ERROR: unable to set state on block %ld", block);
return;
}
if (cstate.isUsed) {
cstate.purpose = VolumeUsage::kChunkPurposeConflict;
LOGI(" CPM conflicting uses for block=%ld", block);
} else {
cstate.isUsed = true;
cstate.isMarkedUsed = true; // no volume bitmap
cstate.purpose = purpose;
}
fVolumeUsage.SetChunkState(block, &cstate);
}
/*
* Scan for damaged files and conflicting file allocation entries.
*
* Appends some entries to the DiskImg notes, so this should only be run
* once per DiskFS.
*
* Returns "true" if disk appears to be perfect, "false" otherwise.
*/
bool DiskFSCPM::CheckDiskIsGood(void)
{
//DIError dierr;
bool result = true;
//if (fEarlyDamage)
// result = false;
/*
* TO DO: look for multiple files occupying the same blocks.
*/
/*
* Scan for "damaged" or "suspicious" files diagnosed earlier.
*/
bool damaged, suspicious;
ScanForDamagedFiles(&damaged, &suspicious);
if (damaged) {
fpImg->AddNote(DiskImg::kNoteWarning,
"One or more files are damaged.");
result = false;
} else if (suspicious) {
fpImg->AddNote(DiskImg::kNoteWarning,
"One or more files look suspicious.");
result = false;
}
return result;
}
/*
* ===========================================================================
* A2FileCPM
* ===========================================================================
*/
/*
* Not a whole lot to do, since there's no fancy index blocks.
*
* Calling GetBlockList twice is probably not the best way to go through life.
* This needs an overhaul.
*/
DIError A2FileCPM::Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork /*=false*/)
{
DIError dierr;
A2FDCPM* pOpenFile = NULL;
if (fpOpenFile != NULL)
return kDIErrAlreadyOpen;
if (rsrcFork)
return kDIErrForkNotFound;
assert(readOnly);
pOpenFile = new A2FDCPM(this);
dierr = GetBlockList(&pOpenFile->fBlockCount, NULL);
if (dierr != kDIErrNone)
goto bail;
pOpenFile->fBlockList = new uint8_t[pOpenFile->fBlockCount+1];
pOpenFile->fBlockList[pOpenFile->fBlockCount] = 0xff;
dierr = GetBlockList(&pOpenFile->fBlockCount, pOpenFile->fBlockList);
if (dierr != kDIErrNone)
goto bail;
assert(pOpenFile->fBlockList[pOpenFile->fBlockCount] == 0xff);
pOpenFile->fOffset = 0;
//fOpen = true;
fpOpenFile = pOpenFile;
*ppOpenFile = pOpenFile;
pOpenFile = NULL;
bail:
delete pOpenFile;
return dierr;
}
/*
* Get the complete block list for a file. This will involve reading
* one or more directory entries.
*
* Call this once with "blockBuf" equal to "NULL" to get the block count,
* then call a second time after allocating blockBuf.
*/
DIError A2FileCPM::GetBlockList(long* pBlockCount, uint8_t* blockBuf) const
{
di_off_t length = fLength;
int blockCount = 0;
int i, j;
/*
* Run through the entries, pulling blocks out until we account for the
* entire length of the file.
*
* [Should probably pay more attention to extent numbers, making sure
* that they make sense. Not vital until we allow writes.]
*/
for (i = 0; i < DiskFSCPM::kNumDirEntries; i++) {
if (length <= 0)
break;
if (fpDirEntry[i].userNumber == kNoDataByte)
continue;
if (strcmp((const char*)fpDirEntry[i].fileName,
(const char*)fpDirEntry[fDirIdx].fileName) == 0 &&
fpDirEntry[i].userNumber == fpDirEntry[fDirIdx].userNumber)
{
/* this entry is part of the file */
for (j = 0; j < DiskFSCPM::kDirEntryBlockCount; j++) {
if (fpDirEntry[i].blocks[j] == 0) {
LOGI(" CPM found sparse block %d/%d", i, j);
}
blockCount++;
if (blockBuf != NULL) {
long listOffset = j +
fpDirEntry[i].extent * DiskFSCPM::kDirEntryBlockCount;
blockBuf[listOffset] = fpDirEntry[i].blocks[j];
}
length -= 1024;
if (length <= 0)
break;
}
}
}
if (length > 0) {
LOGI(" CPM WARNING: can't account for %ld bytes!", (long) length);
//assert(false);
}
//LOGI(" Returning blockCount=%d for '%s'", blockCount,
// fpDirEntry[fDirIdx].fileName);
if (pBlockCount != NULL) {
assert(blockBuf == NULL || *pBlockCount == blockCount);
*pBlockCount = blockCount;
}
return kDIErrNone;
}
/*
* Dump the contents of the A2File structure.
*/
void A2FileCPM::Dump(void) const
{
LOGI("A2FileCPM '%s' length=%ld", fFileName, (long) fLength);
}
/*
* ===========================================================================
* A2FDCPM
* ===========================================================================
*/
/*
* Read a chunk of data from the current offset.
*/
DIError A2FDCPM::Read(void* buf, size_t len, size_t* pActual)
{
LOGI(" CP/M reading %lu bytes from '%s' (offset=%ld)",
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
A2FileCPM* pFile = (A2FileCPM*) fpFile;
/* don't allow them to read past the end of the file */
if (fOffset + (long)len > pFile->fLength) {
if (pActual == NULL)
return kDIErrDataUnderrun;
len = (size_t) (pFile->fLength - fOffset);
}
if (pActual != NULL)
*pActual = len;
long incrLen = len;
DIError dierr = kDIErrNone;
const int kCPMBlockSize = kBlkSize*2;
assert(kCPMBlockSize == 1024);
uint8_t blkBuf[kCPMBlockSize];
int blkIndex = (int) (fOffset / kCPMBlockSize);
int bufOffset = (int) (fOffset % kCPMBlockSize); // (& 0x3ff)
size_t thisCount;
long prodosBlock;
if (len == 0)
return kDIErrNone;
assert(pFile->fLength != 0);
while (len) {
if (blkIndex >= fBlockCount) {
/* ran out of data */
return kDIErrDataUnderrun;
}
if (fBlockList[blkIndex] == 0) {
/*
* Sparse block.
*/
memset(blkBuf, kNoDataByte, sizeof(blkBuf));
} else {
/*
* Read one CP/M block (two ProDOS blocks) and pull out the
* set of data that the user wants.
*
* On some Microsoft Softcard disks, the first three tracks hold
* file data rather than the system image.
*/
prodosBlock = DiskFSCPM::CPMToProDOSBlock(fBlockList[blkIndex]);
if (prodosBlock >= 280)
prodosBlock -= 280;
dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock(prodosBlock,
blkBuf);
if (dierr != kDIErrNone) {
LOGI(" CP/M error1 reading file '%s'", pFile->fFileName);
return dierr;
}
dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock(prodosBlock+1,
blkBuf + kBlkSize);
if (dierr != kDIErrNone) {
LOGI(" CP/M error2 reading file '%s'", pFile->fFileName);
return dierr;
}
}
thisCount = kCPMBlockSize - bufOffset;
if (thisCount > len)
thisCount = len;
memcpy(buf, blkBuf + bufOffset, thisCount);
len -= thisCount;
buf = (char*)buf + thisCount;
bufOffset = 0;
blkIndex++;
}
fOffset += incrLen;
return dierr;
}
/*
* Write data at the current offset.
*/
DIError A2FDCPM::Write(const void* buf, size_t len, size_t* pActual)
{
return kDIErrNotSupported;
}
/*
* Seek to a new offset.
*/
DIError A2FDCPM::Seek(di_off_t offset, DIWhence whence)
{
di_off_t fileLength = ((A2FileCPM*) fpFile)->fLength;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset > fileLength)
return kDIErrInvalidArg;
fOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fileLength)
return kDIErrInvalidArg;
fOffset = fileLength + offset;
break;
case kSeekCur:
if (offset < -fOffset ||
offset >= (fileLength - fOffset))
{
return kDIErrInvalidArg;
}
fOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fOffset >= 0 && fOffset <= fileLength);
return kDIErrNone;
}
/*
* Return current offset.
*/
di_off_t A2FDCPM::Tell(void)
{
return fOffset;
}
/*
* Release file state, such as it is.
*/
DIError A2FDCPM::Close(void)
{
fpFile->CloseDescr(this);
return kDIErrNone;
}
/*
* Return the #of sectors/blocks in the file.
*/
long A2FDCPM::GetSectorCount(void) const
{
return fBlockCount * 4;
}
long A2FDCPM::GetBlockCount(void) const
{
return fBlockCount * 2;
}
/*
* Return the Nth track/sector in this file.
*/
DIError A2FDCPM::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
{
long cpmIdx = sectorIdx / 4; // 4 256-byte sectors per 1K CP/M block
if (cpmIdx >= fBlockCount)
return kDIErrInvalidIndex; // CP/M files can have *no* storage
long cpmBlock = fBlockList[cpmIdx];
long prodosBlock = DiskFSCPM::CPMToProDOSBlock(cpmBlock);
if (sectorIdx & 0x02)
prodosBlock++;
BlockToTrackSector(prodosBlock, (sectorIdx & 0x01) != 0, pTrack, pSector);
return kDIErrNone;
}
/*
* Return the Nth 512-byte block in this file. Since things aren't stored
* in 512-byte blocks, we grab the appropriate 1K block and pick half.
*/
DIError A2FDCPM::GetStorage(long blockIdx, long* pBlock) const
{
long cpmIdx = blockIdx / 2; // 4 256-byte sectors per 1K CP/M block
if (cpmIdx >= fBlockCount)
return kDIErrInvalidIndex;
long cpmBlock = fBlockList[cpmIdx];
long prodosBlock = DiskFSCPM::CPMToProDOSBlock(cpmBlock);
if (blockIdx & 0x01)
prodosBlock++;
*pBlock = prodosBlock;
assert(*pBlock < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
return kDIErrNone;
}

325
diskimg/CP_WNASPI32.H Normal file
View File

@ -0,0 +1,325 @@
/******************************************************************************
**
** Module Name: wnaspi32.h
**
** Description: Header file for ASPI for Win32. This header includes
** macro and type declarations, and can be included without
** modification when using Borland C++ or Microsoft Visual
** C++ with 32-bit compilation. If you are using a different
** compiler then you MUST ensure that structures are packed
** onto byte alignments, and that C++ name mangling is turned
** off.
**
** Notes: This file created using 4 spaces per tab.
**
******************************************************************************/
#ifndef DISKIMG_WNASPI32_H
#define DISKIMG_WNASPI32_H
/*
** Make sure structures are packed and undecorated.
*/
#ifdef __BORLANDC__
#pragma option -a1
#endif //__BORLANDC__
#ifdef _MSC_VER
#pragma pack(1)
#endif //__MSC_VER
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
//*****************************************************************************
// %%% SCSI MISCELLANEOUS EQUATES %%%
//*****************************************************************************
#define SENSE_LEN 18 // Default sense buffer length (ATM: was 14)
#define SRB_DIR_SCSI 0x00 // Direction determined by SCSI
#define SRB_POSTING 0x01 // Enable ASPI posting
#define SRB_ENABLE_RESIDUAL_COUNT 0x04 // Enable residual byte count reporting
#define SRB_DIR_IN 0x08 // Transfer from SCSI target to host
#define SRB_DIR_OUT 0x10 // Transfer from host to SCSI target
#define SRB_EVENT_NOTIFY 0x40 // Enable ASPI event notification
#define RESIDUAL_COUNT_SUPPORTED 0x02 // Extended buffer flag
#define MAX_SRB_TIMEOUT 108000lu // 30 hour maximum timeout in s
#define DEFAULT_SRB_TIMEOUT 108000lu // Max timeout by default
//*****************************************************************************
// %%% ASPI Command Definitions %%%
//*****************************************************************************
#define SC_HA_INQUIRY 0x00 // Host adapter inquiry
#define SC_GET_DEV_TYPE 0x01 // Get device type
#define SC_EXEC_SCSI_CMD 0x02 // Execute SCSI command
#define SC_ABORT_SRB 0x03 // Abort an SRB
#define SC_RESET_DEV 0x04 // SCSI bus device reset
#define SC_SET_HA_PARMS 0x05 // Set HA parameters
#define SC_GET_DISK_INFO 0x06 // Get Disk information
#define SC_RESCAN_SCSI_BUS 0x07 // ReBuild SCSI device map
#define SC_GETSET_TIMEOUTS 0x08 // Get/Set target timeouts
//*****************************************************************************
// %%% SRB Status %%%
//*****************************************************************************
#define SS_PENDING 0x00 // SRB being processed
#define SS_COMP 0x01 // SRB completed without error
#define SS_ABORTED 0x02 // SRB aborted
#define SS_ABORT_FAIL 0x03 // Unable to abort SRB
#define SS_ERR 0x04 // SRB completed with error
#define SS_INVALID_CMD 0x80 // Invalid ASPI command
#define SS_INVALID_HA 0x81 // Invalid host adapter number
#define SS_NO_DEVICE 0x82 // SCSI device not installed
#define SS_INVALID_SRB 0xE0 // Invalid parameter set in SRB
#define SS_OLD_MANAGER 0xE1 // ASPI manager doesn't support Windows
#define SS_BUFFER_ALIGN 0xE1 // Buffer not aligned (replaces OLD_MANAGER in Win32)
#define SS_ILLEGAL_MODE 0xE2 // Unsupported Windows mode
#define SS_NO_ASPI 0xE3 // No ASPI managers resident
#define SS_FAILED_INIT 0xE4 // ASPI for windows failed init
#define SS_ASPI_IS_BUSY 0xE5 // No resources available to execute cmd
#define SS_BUFFER_TO_BIG 0xE6 // Buffer size to big to handle!
#define SS_MISMATCHED_COMPONENTS 0xE7 // The DLLs/EXEs of ASPI don't version check
#define SS_NO_ADAPTERS 0xE8 // No host adapters to manage
#define SS_INSUFFICIENT_RESOURCES 0xE9 // Couldn't allocate resources needed to init
#define SS_ASPI_IS_SHUTDOWN 0xEA // Call came to ASPI after PROCESS_DETACH
#define SS_BAD_INSTALL 0xEB // The DLL or other components are installed wrong
//*****************************************************************************
// %%% Host Adapter Status %%%
//*****************************************************************************
#define HASTAT_OK 0x00 // Host adapter did not detect an error
#define HASTAT_SEL_TO 0x11 // Selection Timeout
#define HASTAT_DO_DU 0x12 // Data overrun data underrun
#define HASTAT_BUS_FREE 0x13 // Unexpected bus free
#define HASTAT_PHASE_ERR 0x14 // Target bus phase sequence failure
#define HASTAT_TIMEOUT 0x09 // Timed out while SRB was waiting to be processed.
#define HASTAT_COMMAND_TIMEOUT 0x0B // Adapter timed out processing SRB.
#define HASTAT_MESSAGE_REJECT 0x0D // While processing SRB, the adapter received a MESSAGE
#define HASTAT_BUS_RESET 0x0E // A bus reset was detected.
#define HASTAT_PARITY_ERROR 0x0F // A parity error was detected.
#define HASTAT_REQUEST_SENSE_FAILED 0x10 // The adapter failed in issuing
//*****************************************************************************
// %%% SRB - HOST ADAPTER INQUIRY - SC_HA_INQUIRY (0) %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_HA_INQUIRY
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 ASPI request flags
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
BYTE HA_Count; // 08/008 Number of host adapters present
BYTE HA_SCSI_ID; // 09/009 SCSI ID of host adapter
BYTE HA_ManagerId[16]; // 0A/010 String describing the manager
BYTE HA_Identifier[16]; // 1A/026 String describing the host adapter
BYTE HA_Unique[16]; // 2A/042 Host Adapter Unique parameters
WORD HA_Rsvd1; // 3A/058 Reserved, MUST = 0
}
SRB_HAInquiry, *PSRB_HAInquiry, FAR *LPSRB_HAInquiry;
//*****************************************************************************
// %%% SRB - GET DEVICE TYPE - SC_GET_DEV_TYPE (1) %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GET_DEV_TYPE
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
BYTE SRB_Target; // 08/008 Target's SCSI ID
BYTE SRB_Lun; // 09/009 Target's LUN number
BYTE SRB_DeviceType; // 0A/010 Target's peripheral device type
BYTE SRB_Rsvd1; // 0B/011 Reserved, MUST = 0
}
SRB_GDEVBlock, *PSRB_GDEVBlock, FAR *LPSRB_GDEVBlock;
//*****************************************************************************
// %%% SRB - EXECUTE SCSI COMMAND - SC_EXEC_SCSI_CMD (2) %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_EXEC_SCSI_CMD
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 ASPI request flags
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
BYTE SRB_Target; // 08/008 Target's SCSI ID
BYTE SRB_Lun; // 09/009 Target's LUN number
WORD SRB_Rsvd1; // 0A/010 Reserved for Alignment
DWORD SRB_BufLen; // 0C/012 Data Allocation Length
BYTE FAR *SRB_BufPointer; // 10/016 Data Buffer Pointer
BYTE SRB_SenseLen; // 14/020 Sense Allocation Length
BYTE SRB_CDBLen; // 15/021 CDB Length
BYTE SRB_HaStat; // 16/022 Host Adapter Status
BYTE SRB_TargStat; // 17/023 Target Status
VOID FAR *SRB_PostProc; // 18/024 Post routine
BYTE SRB_Rsvd2[20]; // 1C/028 Reserved, MUST = 0
BYTE CDBByte[16]; // 30/048 SCSI CDB
BYTE SenseArea[SENSE_LEN+2]; // 50/064 Request Sense buffer
}
SRB_ExecSCSICmd, *PSRB_ExecSCSICmd, FAR *LPSRB_ExecSCSICmd;
//*****************************************************************************
// %%% SRB - ABORT AN SRB - SC_ABORT_SRB (3) %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_ABORT_SRB
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 Reserved
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
VOID FAR *SRB_ToAbort; // 08/008 Pointer to SRB to abort
}
SRB_Abort, *PSRB_Abort, FAR *LPSRB_Abort;
//*****************************************************************************
// %%% SRB - BUS DEVICE RESET - SC_RESET_DEV (4) %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_RESET_DEV
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 ASPI request flags
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
BYTE SRB_Target; // 08/008 Target's SCSI ID
BYTE SRB_Lun; // 09/009 Target's LUN number
BYTE SRB_Rsvd1[12]; // 0A/010 Reserved for Alignment
BYTE SRB_HaStat; // 16/022 Host Adapter Status
BYTE SRB_TargStat; // 17/023 Target Status
VOID FAR *SRB_PostProc; // 18/024 Post routine
BYTE SRB_Rsvd2[36]; // 1C/028 Reserved, MUST = 0
}
SRB_BusDeviceReset, *PSRB_BusDeviceReset, FAR *LPSRB_BusDeviceReset;
//*****************************************************************************
// %%% SRB - GET DISK INFORMATION - SC_GET_DISK_INFO %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GET_DISK_INFO
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
BYTE SRB_Target; // 08/008 Target's SCSI ID
BYTE SRB_Lun; // 09/009 Target's LUN number
BYTE SRB_DriveFlags; // 0A/010 Driver flags
BYTE SRB_Int13HDriveInfo; // 0B/011 Host Adapter Status
BYTE SRB_Heads; // 0C/012 Preferred number of heads translation
BYTE SRB_Sectors; // 0D/013 Preferred number of sectors translation
BYTE SRB_Rsvd1[10]; // 0E/014 Reserved, MUST = 0
}
SRB_GetDiskInfo, *PSRB_GetDiskInfo, FAR *LPSRB_GetDiskInfo;
//*****************************************************************************
// %%% SRB - RESCAN SCSI BUS(ES) ON SCSIPORT %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_RESCAN_SCSI_BUS
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
}
SRB_RescanPort, *PSRB_RescanPort, FAR *LPSRB_RescanPort;
//*****************************************************************************
// %%% SRB - GET/SET TARGET TIMEOUTS %%%
//*****************************************************************************
typedef struct // Offset
{ // HX/DEC
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GETSET_TIMEOUTS
BYTE SRB_Status; // 01/001 ASPI command status byte
BYTE SRB_HaId; // 02/002 ASPI host adapter number
BYTE SRB_Flags; // 03/003 ASPI request flags
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
BYTE SRB_Target; // 08/008 Target's SCSI ID
BYTE SRB_Lun; // 09/009 Target's LUN number
DWORD SRB_Timeout; // 0A/010 Timeout in half seconds
}
SRB_GetSetTimeouts, *PSRB_GetSetTimeouts, FAR *LPSRB_GetSetTimeouts;
//*****************************************************************************
// %%% ASPIBUFF - Structure For Controllng I/O Buffers %%%
//*****************************************************************************
typedef struct tag_ASPI32BUFF // Offset
{ // HX/DEC
PBYTE AB_BufPointer; // 00/000 Pointer to the ASPI allocated buffer
DWORD AB_BufLen; // 04/004 Length in bytes of the buffer
DWORD AB_ZeroFill; // 08/008 Flag set to 1 if buffer should be zeroed
DWORD AB_Reserved; // 0C/012 Reserved
}
ASPI32BUFF, *PASPI32BUFF, FAR *LPASPI32BUFF;
//*****************************************************************************
// %%% PROTOTYPES - User Callable ASPI for Win32 Functions %%%
//*****************************************************************************
typedef void *LPSRB;
#if defined(__BORLANDC__)
DWORD _import GetASPI32SupportInfo( void );
DWORD _import SendASPI32Command( LPSRB );
BOOL _import GetASPI32Buffer( PASPI32BUFF );
BOOL _import FreeASPI32Buffer( PASPI32BUFF );
BOOL _import TranslateASPI32Address( PDWORD, PDWORD );
#elif defined(_MSC_VER)
__declspec(dllimport) DWORD GetASPI32SupportInfo( void );
__declspec(dllimport) DWORD SendASPI32Command( LPSRB );
__declspec(dllimport) BOOL GetASPI32Buffer( PASPI32BUFF );
__declspec(dllimport) BOOL FreeASPI32Buffer( PASPI32BUFF );
__declspec(dllimport) BOOL TranslateASPI32Address( PDWORD, PDWORD );
#else
extern DWORD GetASPI32SupportInfo( void );
extern DWORD SendASPI32Command( LPSRB );
extern BOOL GetASPI32Buffer( PASPI32BUFF );
extern BOOL FreeASPI32Buffer( PASPI32BUFF );
extern BOOL TranslateASPI32Address( PDWORD, PDWORD );
#endif
/*
** Restore compiler default packing and close off the C declarations.
*/
#ifdef __BORLANDC__
#pragma option -a.
#endif //__BORLANDC__
#ifdef _MSC_VER
#pragma pack()
#endif //_MSC_VER
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //DISKIMG_WNASPI32_H

184
diskimg/CP_ntddscsi.h Normal file
View File

@ -0,0 +1,184 @@
/*
* ntddscsi.h
*
* SCSI port IOCTL interface.
*
* This file is part of the w32api package.
*
* Contributors:
* Created by Casper S. Hornstrup <chorns@users.sourceforge.net>
*
* THIS SOFTWARE IS NOT COPYRIGHTED
*
* This source code is offered for use in the public domain. You may
* use, modify or distribute it freely.
*
* This code is distributed in the hope that it will be useful but
* WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
* DISCLAIMED. This includes but is not limited to warranties of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#ifndef DISKIMG_NTDDSCSI_H
#define DISKIMG_NTDDSCSI_H
#if __GNUC__ >=3
#pragma GCC system_header
#endif
#ifdef __cplusplus
extern "C" {
#endif
#pragma pack(push,4)
//#include "ntddk.h"
#ifndef ULONG_PTR
# define ULONG_PTR DWORD
#endif
#define DD_SCSI_DEVICE_NAME "\\Device\\ScsiPort"
#define DD_SCSI_DEVICE_NAME_U L"\\Device\\ScsiPort"
#define IOCTL_SCSI_BASE FILE_DEVICE_CONTROLLER
#define IOCTL_SCSI_GET_INQUIRY_DATA \
CTL_CODE(IOCTL_SCSI_BASE, 0x0403, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SCSI_GET_CAPABILITIES \
CTL_CODE(IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SCSI_GET_ADDRESS \
CTL_CODE(IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SCSI_MINIPORT \
CTL_CODE(IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_SCSI_PASS_THROUGH \
CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_SCSI_PASS_THROUGH_DIRECT \
CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_SCSI_RESCAN_BUS \
CTL_CODE(IOCTL_SCSI_BASE, 0x0407, METHOD_BUFFERED, FILE_ANY_ACCESS)
//DEFINE_GUID(ScsiRawInterfaceGuid, \
// 0x53f56309L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
//DEFINE_GUID(WmiScsiAddressGuid, \
// 0x53f5630fL, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
typedef struct _SCSI_PASS_THROUGH {
USHORT Length;
UCHAR ScsiStatus;
UCHAR PathId;
UCHAR TargetId;
UCHAR Lun;
UCHAR CdbLength;
UCHAR SenseInfoLength;
UCHAR DataIn;
ULONG DataTransferLength;
ULONG TimeOutValue;
ULONG_PTR DataBufferOffset;
ULONG SenseInfoOffset;
UCHAR Cdb[16];
} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;
typedef struct _SCSI_PASS_THROUGH_DIRECT {
USHORT Length;
UCHAR ScsiStatus;
UCHAR PathId;
UCHAR TargetId;
UCHAR Lun;
UCHAR CdbLength;
UCHAR SenseInfoLength;
UCHAR DataIn;
ULONG DataTransferLength;
ULONG TimeOutValue;
PVOID DataBuffer;
ULONG SenseInfoOffset;
UCHAR Cdb[16];
} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
typedef struct _SRB_IO_CONTROL {
ULONG HeaderLength;
UCHAR Signature[8];
ULONG Timeout;
ULONG ControlCode;
ULONG ReturnCode;
ULONG Length;
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
typedef struct _SCSI_ADDRESS {
ULONG Length;
UCHAR PortNumber;
UCHAR PathId;
UCHAR TargetId;
UCHAR Lun;
} SCSI_ADDRESS, *PSCSI_ADDRESS;
typedef struct _SCSI_BUS_DATA {
UCHAR NumberOfLogicalUnits;
UCHAR InitiatorBusId;
ULONG InquiryDataOffset;
}SCSI_BUS_DATA, *PSCSI_BUS_DATA;
typedef struct _SCSI_ADAPTER_BUS_INFO {
UCHAR NumberOfBuses;
SCSI_BUS_DATA BusData[1];
} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO;
typedef struct _IO_SCSI_CAPABILITIES {
ULONG Length;
ULONG MaximumTransferLength;
ULONG MaximumPhysicalPages;
ULONG SupportedAsynchronousEvents;
ULONG AlignmentMask;
BOOLEAN TaggedQueuing;
BOOLEAN AdapterScansDown;
BOOLEAN AdapterUsesPio;
} IO_SCSI_CAPABILITIES, *PIO_SCSI_CAPABILITIES;
typedef struct _SCSI_INQUIRY_DATA {
UCHAR PathId;
UCHAR TargetId;
UCHAR Lun;
BOOLEAN DeviceClaimed;
ULONG InquiryDataLength;
ULONG NextInquiryDataOffset;
UCHAR InquiryData[1];
} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA;
#define SCSI_IOCTL_DATA_OUT 0
#define SCSI_IOCTL_DATA_IN 1
#define SCSI_IOCTL_DATA_UNSPECIFIED 2
struct ADAPTER_OBJECT;
typedef struct ADAPTER_OBJECT* PADAPTER_OBJECT;
typedef struct _DUMP_POINTERS {
PADAPTER_OBJECT AdapterObject;
PVOID MappedRegisterBase;
PVOID DumpData;
PVOID CommonBufferVa;
LARGE_INTEGER CommonBufferPa;
ULONG CommonBufferSize;
BOOLEAN AllocateCommonBuffers;
BOOLEAN UseDiskDump;
UCHAR Spare1[2];
PVOID DeviceObject;
} DUMP_POINTERS, *PDUMP_POINTERS;
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif /*DISKIMG_NTDDSCSI_H*/

120
diskimg/Container.cpp Normal file
View File

@ -0,0 +1,120 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Base "container FS" support.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* Blank out the volume usage map, setting all entries to "embedded".
*/
void DiskFSContainer::SetVolumeUsageMap(void)
{
VolumeUsage::ChunkState cstate;
long block;
fVolumeUsage.Create(fpImg->GetNumBlocks());
cstate.isUsed = true;
cstate.isMarkedUsed = true;
cstate.purpose = VolumeUsage::kChunkPurposeEmbedded;
for (block = fpImg->GetNumBlocks()-1; block >= 0; block--)
fVolumeUsage.SetChunkState(block, &cstate);
}
/*
* Create a "placeholder" sub-volume. Useful for some of the tools when
* dealing with unformatted (or unknown-formatted) partitions.
*/
DIError DiskFSContainer::CreatePlaceholder(long startBlock, long numBlocks,
const char* partName, const char* partType,
DiskImg** ppNewImg, DiskFS** ppNewFS)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
LOGI(" %s/CrPl creating placeholder for %ld +%ld", GetDebugName(),
startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
LOGI(" %s/CrPl start block out of range (%ld vs %ld)",
GetDebugName(), startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
if (partName != NULL) {
if (partType != NULL)
pNewImg->AddNote(DiskImg::kNoteInfo,
"Partition name='%s' type='%s'.", partName, partType);
else
pNewImg->AddNote(DiskImg::kNoteInfo,
"Partition name='%s'.", partName);
}
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" %s/CrPl: OpenImage(%ld,%ld) failed (err=%d)",
GetDebugName(), startBlock, numBlocks, dierr);
goto bail;
}
/*
* If this slot isn't formatted at all, the call will return with
* "unknown FS". If it's formatted enough to pass the initial test,
* but fails during "Initialize", we'll have a non-unknown value
* for the FS format. We need to stomp that.
*/
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" %s/CrPl: analysis failed (err=%d)", GetDebugName(), dierr);
goto bail;
}
if (pNewImg->GetFSFormat() != DiskImg::kFormatUnknown) {
dierr = pNewImg->OverrideFormat(pNewImg->GetPhysicalFormat(),
DiskImg::kFormatUnknown, pNewImg->GetSectorOrder());
if (dierr != kDIErrNone) {
LOGI(" %s/CrPl: unable to override to unknown",
GetDebugName());
goto bail;
}
}
/* open a DiskFS for the sub-image, allowing "unknown" */
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == NULL) {
LOGI(" %s/CrPl: OpenAppropriateDiskFS failed", GetDebugName());
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* sets the DiskImg ptr (and very little else) */
dierr = pNewFS->Initialize(pNewImg, kInitFull);
if (dierr != kDIErrNone) {
LOGI(" %s/CrPl: init failed (err=%d)", GetDebugName(), dierr);
goto bail;
}
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
} else {
*ppNewImg = pNewImg;
*ppNewFS = pNewFS;
}
return dierr;
}

629
diskimg/DDD.cpp Normal file
View File

@ -0,0 +1,629 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Pack and unpack DDD format.
*/
/*
The trouble with unpacking DOS DDD 2.x files:
[ Most of this is no longer relevant, but the discussion is enlightening. ]
DDD writes its files as DOS 3.3 binary (type 'B') files, which have
starting address and length embedded as the first 4 bytes. Unfortunately, it
cannot write the length, because the largest possible 16-bit length value is
only 64K. Instead, DDD sets it to zero. DDD v2.0 does store a copy of the
length *in sectors* in the filename (e.g. "<397>"), but this doesn't really
help much. When CiderPress goes to extract or view the file, it just sees a
zero-length binary file.
CiderPress could make an exception and assume that any binary file with
zero length and more than one sector allocated has a length equal to the
number of sectors times 256. This could cause problems for other things,
but it's probably pretty safe. However, we still don't have an accurate
idea of where the end of the file is.
Knowing where the file ends is important because there is no identifying
information or checksum in a DDD file. The only way to know that it's a
DDD compressed disk is to try to unpack it and see if you end up at exactly
140K at the same time that you run out of input. Without knowing where the
file really ends this test is much less certain. (DDD 2.5 appears to have
added some sort of checksum, which was appended to the DOS filename, but
without knowing how it was calculated there's no way to verify it.)
The only safe way to make this work would be to skip the automatic format
detection and tell CiderPress that the file is definitely DDD format.
There's currently no easy way to do that without complicating the user
interface. Filename extensions might be useful in making the decision,
but they're rare under DOS 3.3, and I don't know if the "<397>" convention
is common to all versions of DDD.
Complicating the matter is that, if a DOS DDD file (type 'B') is converted
to ProDOS, the first 4 bytes will be stripped off. Without unpacking
the file and knowing to within a byte where it ends, there's no way to
automatically tell whether to start at byte 0 or byte 4. (DDD Pro files
have four bytes of garbage at the very start, probably in an attempt to
retain compatibility with the DOS version. Because it uses REL files the
4 bytes of extra DOS stuff aren't added when the files are copied around,
so this was a reasonably smart thing to do, but it complicates matters
for CiderPress because a file extracted from DOS and a file extracted
from ProDOS will come out differently because only the DOS version has the
leading 4 bytes stripped. This could be avoided if the DOS file uses the
'R' or 'S' file type, but we still lack an accurate file length.)
To unpack a file created by DOS DDD v2.x with CiderPress:
- In an emulator, copy the file to a ProDOS disk, using something that
guesses at the actual length when one isn't provided (Copy ][+ 9.0
may work).
- Reduce the length to within a byte or two of the actual end of file.
This is nearly impossible, because DDD doesn't zero out the remaining
data in the last sector.
- Insert 4 bytes of garbage at the front of the file. My copy of DDD
Pro 1.1 seems to like 03 c9 bf d0.
Probably not worth the effort. Just unpack it with an emulator.
In general DDD is a rather poor choice, because the compression isn't
very good and there's no checksum. ShrinkIt is a much better way to go.
NOTE: DOS DDD v2.0 seems to have a bug where it doesn't always write the last
run correctly. On the DOS system master this caused the last half of the
last sector (FID's T/S list) to have garbage written instead of zero bytes,
which caused CP to label FID as damaged. DDD v2.1 and later doesn't
appear to have this issue. Unfortunate that DDD 2.0 is what shipped on the
SST disk.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
const int kNumSymbols = 256;
const int kNumFavorites = 20;
const int kRLEDelim = 0x97; // value MUST have high bit set
const int kMaxExcessByteCount = WrapperDDD::kMaxDDDZeroCount + 1;
//const int kTrackLen = 4096;
//const int kNumTracks = 35;
/* I suspect this is random garbage, but it's appearing consistently */
const unsigned long kDDDProSignature = 0xd0bfc903;
/*
* ===========================================================================
* BitBuffer
* ===========================================================================
*/
/*
* Class for getting and putting bits to and from a file.
*/
class WrapperDDD::BitBuffer {
public:
BitBuffer(void) : fpGFD(NULL), fBits(0), fBitCount(0), fIOFailure(false) {}
~BitBuffer(void) {}
void SetFile(GenericFD* pGFD) { fpGFD = pGFD; }
void PutBits(uint8_t bits, int numBits);
uint8_t GetBits(int numBits);
bool IOFailure(void) const { return fIOFailure; }
static uint8_t Reverse(uint8_t val);
private:
GenericFD* fpGFD;
uint8_t fBits;
int fBitCount;
bool fIOFailure;
};
/*
* Add bits to the buffer.
*
* We roll the low bits out of "bits" and shift them to the left (in the
* reverse order in which they were passed in). As soon as we get 8 bits
* we flush.
*/
void WrapperDDD::BitBuffer::PutBits(uint8_t bits, int numBits)
{
assert(fBitCount >= 0 && fBitCount < 8);
assert(numBits > 0 && numBits <= 8);
assert(fpGFD != NULL);
DIError dierr;
while (numBits--) {
fBits = (fBits << 1) | (bits & 0x01);
fBitCount++;
if (fBitCount == 8) {
dierr = fpGFD->Write(&fBits, 1);
fIOFailure = (dierr != kDIErrNone);
fBitCount = 0;
}
bits >>= 1;
}
}
/*
* Get bits from the buffer.
*
* These come out in the order in which they appear in the file, which
* means that in some cases they will have to be reversed.
*/
uint8_t WrapperDDD::BitBuffer::GetBits(int numBits)
{
assert(fBitCount >= 0 && fBitCount < 8);
assert(numBits > 0 && numBits <= 8);
assert(fpGFD != NULL);
DIError dierr;
uint8_t retVal;
if (fBitCount == 0) {
/* have no bits */
dierr = fpGFD->Read(&fBits, 1);
fIOFailure = (dierr != kDIErrNone);
fBitCount = 8;
}
if (numBits <= fBitCount) {
/* just serve up what we've already got */
retVal = fBits >> (8 - numBits);
fBits <<= numBits;
fBitCount -= numBits;
} else {
/* some old, some new; load what we have right-aligned */
retVal = fBits >> (8 - fBitCount);
numBits -= fBitCount;
dierr = fpGFD->Read(&fBits, 1);
fIOFailure = (dierr != kDIErrNone);
fBitCount = 8;
/* make room for the rest (also zeroes out the low bits) */
retVal <<= numBits;
/* add the high bits from the new byte */
retVal |= fBits >> (8 - numBits);
fBits <<= numBits;
fBitCount -= numBits;
}
return retVal;
}
/*
* Utility function to reverse the order of bits in a byte.
*/
/*static*/ uint8_t WrapperDDD::BitBuffer::Reverse(uint8_t val)
{
int i;
uint8_t result = 0; // init is to make valgrind happy
for (i = 0; i < 8; i++) {
result = (result << 1) + (val & 0x01);
val >>= 1;
}
return result;
}
/*
* ===========================================================================
* DDD compression functions
* ===========================================================================
*/
/*
* These are all odd, which when they're written in reverse order means
* they all have their hi bits set.
*/
static const uint8_t kFavoriteBitEnc[kNumFavorites] = {
0x03, 0x09, 0x1f, 0x0f, 0x07, 0x1b, 0x0b, 0x0d, 0x15, 0x37,
0x3d, 0x25, 0x05, 0xb1, 0x11, 0x21, 0x01, 0x57, 0x5d, 0x1d
};
static const int kFavoriteBitEncLen[kNumFavorites] = {
4, 4, 5, 5, 5, 5, 5, 5, 5, 6,
6, 6, 6, 6, 6, 6, 6, 7, 7, 7
};
/*
* Pack a disk image with DDD.
*
* Assumes pSrcGFD points to DOS-ordered sectors. (This is enforced when the
* disk image is first being created.)
*/
/*static*/ DIError WrapperDDD::PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD,
short diskVolNum)
{
DIError dierr = kDIErrNone;
BitBuffer bitBuffer;
assert(diskVolNum >= 0 && diskVolNum < 256);
/* write four zeroes to replace the DOS addr/len bytes */
/* (actually, let's write the apparent DDD Pro v1.1 signature instead) */
WriteLongLE(pWrapperGFD, kDDDProSignature);
bitBuffer.SetFile(pWrapperGFD);
bitBuffer.PutBits(0x00, 3);
bitBuffer.PutBits((uint8_t)diskVolNum, 8);
/*
* Process all tracks.
*/
for (int track = 0; track < kNumTracks; track++) {
uint8_t trackBuf[kTrackLen];
dierr = pSrcGFD->Read(trackBuf, kTrackLen);
if (dierr != kDIErrNone) {
LOGI(" DDD error during read (err=%d)", dierr);
goto bail;
}
PackTrack(trackBuf, &bitBuffer);
}
/* write 8 bits of zeroes to flush remaining data out of buffer */
bitBuffer.PutBits(0x00, 8);
/* write another zero byte because that's what DDD Pro v1.1 does */
long zero;
zero = 0;
dierr = pWrapperGFD->Write(&zero, 1);
if (dierr != kDIErrNone)
goto bail;
assert(dierr == kDIErrNone);
bail:
return dierr;
}
/*
* Compress a track full of data.
*/
/*static*/ void WrapperDDD::PackTrack(const uint8_t* trackBuf, BitBuffer* pBitBuf)
{
uint16_t freqCounts[kNumSymbols];
uint8_t favorites[kNumFavorites];
int i, fav;
ComputeFreqCounts(trackBuf, freqCounts);
ComputeFavorites(freqCounts, favorites);
/* write favorites */
for (fav = 0; fav < kNumFavorites; fav++)
pBitBuf->PutBits(favorites[fav], 8);
/*
* Compress track data. Store runs as { 0x97 char count }, where
* a count of zero means 256.
*/
const uint8_t* ucp = trackBuf;
for (i = 0; i < kTrackLen; i++, ucp++) {
if (i < (kTrackLen-3) &&
*ucp == *(ucp+1) &&
*ucp == *(ucp+2) &&
*ucp == *(ucp+3))
{
int runLen = 4;
i += 3;
ucp += 3;
while (i < kTrackLen-1 && *ucp == *(ucp+1)) {
runLen++;
ucp++;
i++;
if (runLen == 256) {
runLen = 0;
break;
}
}
pBitBuf->PutBits(kRLEDelim, 8); // note kRLEDelim has hi bit set
pBitBuf->PutBits(*ucp, 8);
pBitBuf->PutBits(runLen, 8);
} else {
/*
* Not a run, see if it's one of our favorites.
*/
for (fav = 0; fav < kNumFavorites; fav++) {
if (*ucp == favorites[fav])
break;
}
if (fav == kNumFavorites) {
/* just a plain byte */
pBitBuf->PutBits(0x00, 1);
pBitBuf->PutBits(*ucp, 8);
} else {
/* found a favorite; leading hi bit is implied */
pBitBuf->PutBits(kFavoriteBitEnc[fav], kFavoriteBitEncLen[fav]);
}
}
}
}
/*
* Compute the #of times each byte appears in trackBuf. Runs of four
* bytes or longer are completely ignored.
*
* "trackBuf" holds kTrackLen bytes of data, and "freqCounts" holds
* kNumSymbols (256) 16-bit values.
*/
/*static*/ void WrapperDDD::ComputeFreqCounts(const uint8_t* trackBuf,
uint16_t* freqCounts)
{
const uint8_t* ucp;
int i;
memset(freqCounts, 0, 256 * sizeof(uint16_t));
ucp = trackBuf;
for (i = 0; i < kTrackLen; i++, ucp++) {
if (i < (kTrackLen-3) &&
*ucp == *(ucp+1) &&
*ucp == *(ucp+2) &&
*ucp == *(ucp+3))
{
int runLen = 4; // DEBUG only
i += 3;
ucp += 3;
while (i < kTrackLen-1 && *ucp == *(ucp+1)) {
runLen++;
ucp++;
i++;
if (runLen == 256) {
runLen = 0;
break;
}
}
//LOGI("Found run of %d of 0x%02x", runLen, *ucp);
} else {
/* not a run, just update stats */
freqCounts[*ucp]++;
}
}
}
/*
* Find the 20 most frequently occurring symbols, in order.
*
* Modifies "freqCounts".
*/
/*static*/ void WrapperDDD::ComputeFavorites(uint16_t* freqCounts,
uint8_t* favorites)
{
int i, fav;
for (fav = 0; fav < kNumFavorites; fav++) {
uint16_t bestCount = 0;
uint8_t bestSym = 0;
for (i = 0; i < kNumSymbols; i++) {
if (freqCounts[i] >= bestCount) {
bestSym = (uint8_t) i;
bestCount = freqCounts[i];
}
}
favorites[fav] = bestSym;
freqCounts[bestSym] = 0;
}
//LOGI("FAVORITES: ");
//for (fav = 0; fav < kNumFavorites; fav++)
// LOGI("%02x", favorites[fav]);
//LOGI("");
}
/*
* ===========================================================================
* DDD expansion functions
* ===========================================================================
*/
/*
* This is the reverse of the kFavoriteBitEnc table. The bits are
* reversed and lack the high bit.
*/
static const uint8_t kFavoriteBitDec[kNumFavorites] = {
0x04, 0x01, 0x0f, 0x0e, 0x0c, 0x0b, 0x0a, 0x06, 0x05, 0x1b,
0x0f, 0x09, 0x08, 0x03, 0x02, 0x01, 0x00, 0x35, 0x1d, 0x1c
};
/*
* Entry point for unpacking a disk image compressed with DDD.
*
* The result is an unadorned DOS-ordered image.
*/
/*static*/ DIError WrapperDDD::UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD,
short* pDiskVolNum)
{
DIError dierr = kDIErrNone;
BitBuffer bitBuffer;
uint8_t val;
long lbuf;
assert(pGFD != NULL);
assert(pNewGFD != NULL);
/* read four zeroes to skip the DOS addr/len bytes */
assert(sizeof(lbuf) >= 4);
dierr = pGFD->Read(&lbuf, 4);
if (dierr != kDIErrNone)
goto bail;
bitBuffer.SetFile(pGFD);
val = bitBuffer.GetBits(3);
if (val != 0) {
LOGI(" DDD bits not zero, this isn't a DDD II file (0x%02x)", val);
dierr = kDIErrGeneric;
goto bail;
}
val = bitBuffer.GetBits(8);
*pDiskVolNum = bitBuffer.Reverse(val);
LOGI(" DDD found disk volume num = %d", *pDiskVolNum);
int track;
for (track = 0; track < kNumTracks; track++) {
uint8_t trackBuf[kTrackLen];
if (!UnpackTrack(&bitBuffer, trackBuf)) {
LOGI(" DDD failed unpacking track %d", track);
dierr = kDIErrBadCompressedData;
goto bail;
}
if (bitBuffer.IOFailure()) {
LOGI(" DDD failure or EOF on input file");
dierr = kDIErrBadCompressedData;
goto bail;
}
dierr = pNewGFD->Write(trackBuf, kTrackLen);
if (dierr != kDIErrNone)
goto bail;
}
/*
* We should be within a byte or two of the end of the file. Try
* to read more and expect it to fail.
*
* Unfortunately, if this was a DOS DDD file, we could be up to 256
* bytes off (the 1 additional byte it adds plus the remaining 255
* bytes in the sector). We have to choose between a tight auto-detect
* and the ability to process DOS DDD files.
*
* Fortunately the need to hit track boundaries exactly and the quick test
* for long runs of bytes provides some opportunity for correct
* detection.
*/
size_t actual;
char sctBuf[256 + 16];
dierr = pGFD->Read(&sctBuf, sizeof(sctBuf), &actual);
if (dierr == kDIErrNone) {
if (actual > /*kMaxExcessByteCount*/ 256) {
LOGW(" DDD looks like too much data in input file (%lu extra)",
(unsigned long) actual);
dierr = kDIErrBadCompressedData;
goto bail;
} else {
LOGI(" DDD excess bytes (%lu) within normal parameters",
(unsigned long) actual);
}
}
LOGI(" DDD looks like a DDD archive!");
dierr = kDIErrNone;
bail:
return dierr;
}
/*
* Unpack a single track.
*
* Returns "true" if all went well, "false" if something failed.
*/
/*static*/ bool WrapperDDD::UnpackTrack(BitBuffer* pBitBuffer, uint8_t* trackBuf)
{
uint8_t favorites[kNumFavorites];
uint8_t val;
uint8_t* trackPtr;
int fav;
/*
* Start by pulling our favorites out, in reverse order.
*/
for (fav = 0; fav < kNumFavorites; fav++) {
val = pBitBuffer->GetBits(8);
val = pBitBuffer->Reverse(val);
favorites[fav] = val;
}
trackPtr = trackBuf;
/*
* Keep pulling data out until the track is full.
*/
while (trackPtr < trackBuf + kTrackLen) {
val = pBitBuffer->GetBits(1);
if (!val) {
/* simple byte */
val = pBitBuffer->GetBits(8);
val = pBitBuffer->Reverse(val);
*trackPtr++ = val;
} else {
/* try for a prefix match */
int extraBits;
val = pBitBuffer->GetBits(2);
for (extraBits = 0; extraBits < 4; extraBits++) {
val = (val << 1) | pBitBuffer->GetBits(1);
int start, end;
if (extraBits == 0) {
start = 0;
end = 2;
} else if (extraBits == 1) {
start = 2;
end = 9;
} else if (extraBits == 2) {
start = 9;
end = 17;
} else {
start = 17;
end = 20;
}
while (start < end) {
if (val == kFavoriteBitDec[start]) {
/* winner! */
*trackPtr++ = favorites[start];
break;
}
start++;
}
if (start != end)
break; // we got it, break out of for loop
}
if (extraBits == 4) {
/* we didn't get it, this must be RLE */
uint8_t rleChar;
int rleCount;
(void) pBitBuffer->GetBits(1); // get last bit of 0x97
val = pBitBuffer->GetBits(8);
rleChar = pBitBuffer->Reverse(val);
val = pBitBuffer->GetBits(8);
rleCount = pBitBuffer->Reverse(val);
//LOGI(" DDD found run of %d of 0x%02x", rleCount, rleChar);
if (rleCount == 0)
rleCount = 256;
/* make sure we won't overrun */
if (trackPtr + rleCount > trackBuf + kTrackLen) {
LOGI(" DDD overrun in RLE");
return false;
}
while (rleCount--)
*trackPtr++ = rleChar;
}
}
}
return true;
}

350
diskimg/DIUtil.cpp Normal file
View File

@ -0,0 +1,350 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* DiskImgLib global utility functions.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
#define kFilenameExtDelim '.' /* separates extension from filename */
/*
* Get values from a memory buffer.
*/
uint16_t DiskImgLib::GetShortLE(const uint8_t* ptr)
{
return *ptr | (uint16_t) *(ptr+1) << 8;
}
uint32_t DiskImgLib::GetLongLE(const uint8_t* ptr)
{
return *ptr |
(uint32_t) *(ptr+1) << 8 |
(uint32_t) *(ptr+2) << 16 |
(uint32_t) *(ptr+3) << 24;
}
uint16_t DiskImgLib::GetShortBE(const uint8_t* ptr)
{
return *(ptr+1) | (uint16_t) *ptr << 8;
}
uint32_t DiskImgLib::GetLongBE(const uint8_t* ptr)
{
return *(ptr+3) |
(uint32_t) *(ptr+2) << 8 |
(uint32_t) *(ptr+1) << 16 |
(uint32_t) *ptr << 24;
}
uint32_t DiskImgLib::Get24BE(const uint8_t* ptr)
{
return *(ptr+2) |
(uint32_t) *(ptr+1) << 8 |
(uint32_t) *ptr << 16;
}
void DiskImgLib::PutShortLE(uint8_t* ptr, uint16_t val)
{
*ptr++ = (uint8_t) val;
*ptr = val >> 8;
}
void DiskImgLib::PutLongLE(uint8_t* ptr, uint32_t val)
{
*ptr++ = (uint8_t) val;
*ptr++ = (uint8_t) (val >> 8);
*ptr++ = (uint8_t) (val >> 16);
*ptr = (uint8_t) (val >> 24);
}
void DiskImgLib::PutShortBE(uint8_t* ptr, uint16_t val)
{
*ptr++ = val >> 8;
*ptr = (uint8_t) val;
}
void DiskImgLib::PutLongBE(uint8_t* ptr, uint32_t val)
{
*ptr++ = (uint8_t) (val >> 24);
*ptr++ = (uint8_t) (val >> 16);
*ptr++ = (uint8_t) (val >> 8);
*ptr = (uint8_t) val;
}
/*
* Read a two-byte little-endian value.
*/
DIError DiskImgLib::ReadShortLE(GenericFD* pGFD, uint16_t* pBuf)
{
DIError dierr;
uint8_t val[2];
dierr = pGFD->Read(&val[0], 1);
if (dierr == kDIErrNone)
dierr = pGFD->Read(&val[1], 1);
*pBuf = val[0] | (short) val[1] << 8;
return dierr;
}
/*
* Read a four-byte little-endian value.
*/
DIError DiskImgLib::ReadLongLE(GenericFD* pGFD, uint32_t* pBuf)
{
DIError dierr;
uint8_t val[4];
dierr = pGFD->Read(&val[0], 1);
if (dierr == kDIErrNone)
dierr = pGFD->Read(&val[1], 1);
if (dierr == kDIErrNone)
dierr = pGFD->Read(&val[2], 1);
if (dierr == kDIErrNone)
dierr = pGFD->Read(&val[3], 1);
*pBuf = val[0] | (uint32_t)val[1] << 8 |
(uint32_t)val[2] << 16 | (uint32_t)val[3] << 24;
return dierr;
}
/*
* Write a two-byte little-endian value.
*/
DIError DiskImgLib::WriteShortLE(FILE* fp, uint16_t val)
{
putc(val, fp);
putc(val >> 8, fp);
return kDIErrNone;
}
/*
* Write a four-byte little-endian value.
*/
DIError DiskImgLib::WriteLongLE(FILE* fp, uint32_t val)
{
putc(val, fp);
putc(val >> 8, fp);
putc(val >> 16, fp);
putc(val >> 24, fp);
return kDIErrNone;
}
/*
* Write a two-byte little-endian value.
*/
DIError DiskImgLib::WriteShortLE(GenericFD* pGFD, uint16_t val)
{
uint8_t buf;
buf = (uint8_t) val;
pGFD->Write(&buf, 1);
buf = val >> 8;
return pGFD->Write(&buf, 1);
}
/*
* Write a four-byte little-endian value.
*/
DIError DiskImgLib::WriteLongLE(GenericFD* pGFD, uint32_t val)
{
uint8_t buf;
buf = (uint8_t) val;
pGFD->Write(&buf, 1);
buf = (uint8_t) (val >> 8);
pGFD->Write(&buf, 1);
buf = (uint8_t) (val >> 16);
pGFD->Write(&buf, 1);
buf = (uint8_t) (val >> 24);
return pGFD->Write(&buf, 1);
}
/*
* Write a two-byte big-endian value.
*/
DIError DiskImgLib::WriteShortBE(GenericFD* pGFD, uint16_t val)
{
uint8_t buf;
buf = val >> 8;
pGFD->Write(&buf, 1);
buf = (uint8_t) val;
return pGFD->Write(&buf, 1);
}
/*
* Write a four-byte big-endian value.
*/
DIError DiskImgLib::WriteLongBE(GenericFD* pGFD, uint32_t val)
{
uint8_t buf;
buf = (uint8_t) (val >> 24);
pGFD->Write(&buf, 1);
buf = (uint8_t) (val >> 16);
pGFD->Write(&buf, 1);
buf = (uint8_t) (val >> 8);
pGFD->Write(&buf, 1);
buf = (uint8_t) val;
return pGFD->Write(&buf, 1);
}
/*
* Find the filename component of a local pathname. Uses the fssep passed
* in. If the fssep is '\0' (as is the case for DOS 3.3), then the entire
* pathname is returned.
*
* Always returns a pointer to a string; never returns NULL.
*/
const char* DiskImgLib::FilenameOnly(const char* pathname, char fssep)
{
const char* retstr;
const char* pSlash;
char* tmpStr = NULL;
assert(pathname != NULL);
if (fssep == '\0') {
retstr = pathname;
goto bail;
}
pSlash = strrchr(pathname, fssep);
if (pSlash == NULL) {
retstr = pathname; /* whole thing is the filename */
goto bail;
}
pSlash++;
if (*pSlash == '\0') {
if (strlen(pathname) < 2) {
retstr = pathname; /* the pathname is just "/"? Whatever */
goto bail;
}
/* some bonehead put an fssep on the very end; back up before it */
/* (not efficient, but this should be rare, and I'm feeling lazy) */
tmpStr = strdup(pathname);
tmpStr[strlen(pathname)-1] = '\0';
pSlash = strrchr(tmpStr, fssep);
if (pSlash == NULL) {
retstr = pathname; /* just a filename with a '/' after it */
goto bail;
}
pSlash++;
if (*pSlash == '\0') {
retstr = pathname; /* I give up! */
goto bail;
}
retstr = pathname + (pSlash - tmpStr);
} else {
retstr = pSlash;
}
bail:
free(tmpStr);
return retstr;
}
/*
* Return the filename extension found in a full pathname.
*
* An extension is the stuff following the last '.' in the filename. If
* there is nothing following the last '.', then there is no extension.
*
* Returns a pointer to the '.' preceding the extension, or NULL if no
* extension was found.
*
* We guarantee that there is at least one character after the '.'.
*/
const char* DiskImgLib::FindExtension(const char* pathname, char fssep)
{
const char* pFilename;
const char* pExt;
/*
* We have to isolate the filename so that we don't get excited
* about "/foo.bar/file".
*/
pFilename = FilenameOnly(pathname, fssep);
assert(pFilename != NULL);
pExt = strrchr(pFilename, kFilenameExtDelim);
/* also check for "/blah/foo.", which doesn't count */
if (pExt != NULL && *(pExt+1) != '\0')
return pExt;
return NULL;
}
/*
* Like strcpy(), but allocate with new[] instead.
*
* If "str" is NULL, or "new" fails, this returns NULL.
*
* TODO: should be "StrdupNew()"
*/
char* DiskImgLib::StrcpyNew(const char* str)
{
char* newStr;
if (str == NULL)
return NULL;
newStr = new char[strlen(str)+1];
if (newStr != NULL)
strcpy(newStr, str);
return newStr;
}
#ifdef _WIN32
/*
* Convert the value from GetLastError() to its DIError counterpart.
*/
DIError DiskImgLib::LastErrorToDIError(void)
{
DWORD lastErr = ::GetLastError();
switch (lastErr) {
case ERROR_FILE_NOT_FOUND: return kDIErrFileNotFound; // 2
case ERROR_ACCESS_DENIED: return kDIErrAccessDenied; // 5
case ERROR_WRITE_PROTECT: return kDIErrWriteProtected; // 19
case ERROR_SECTOR_NOT_FOUND: return kDIErrGeneric; // 27
case ERROR_SHARING_VIOLATION: return kDIErrSharingViolation; // 32
case ERROR_HANDLE_EOF: return kDIErrEOF; // 38
case ERROR_INVALID_PARAMETER: return kDIErrInvalidArg; // 87
case ERROR_SEM_TIMEOUT: return kDIErrGenericIO; // 121
// ERROR_SEM_TIMEOUT seen read bad blocks from floptical under Win2K
case ERROR_INVALID_HANDLE: // 6
LOGI("HEY: got ERROR_INVALID_HANDLE!");
return kDIErrInternal;
case ERROR_NEGATIVE_SEEK: // 131
LOGI("HEY: got ERROR_NEGATIVE_SEEK!");
return kDIErrInternal;
default:
LOGI("LastErrorToDIError: not converting 0x%08lx (%ld)",
lastErr, lastErr);
return kDIErrGeneric;
}
}
/*
* Returns "true" if we're running on Win9x (Win95, Win98, WinME), "false"
* if not (could be WinNT/2K/XP or even Win31 with Win32s).
*/
bool DiskImgLib::IsWin9x(void)
{
return false;
}
#endif

3400
diskimg/DOS33.cpp Normal file

File diff suppressed because it is too large Load Diff

2904
diskimg/DOSImage.cpp Normal file

File diff suppressed because it is too large Load Diff

537
diskimg/DiskFS.cpp Normal file
View File

@ -0,0 +1,537 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* DiskFS base class.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* A2File
* ===========================================================================
*/
/*
* Set the quality level (a/k/a damage level) of a file.
*
* Refuse to "improve" the quality level of a file.
*/
void A2File::SetQuality(FileQuality quality)
{
if (quality == kQualityGood &&
(fFileQuality == kQualitySuspicious || fFileQuality == kQualityDamaged))
{
assert(false);
return;
}
if (quality == kQualitySuspicious && fFileQuality == kQualityDamaged) {
//assert(false);
return;
}
fFileQuality = quality;
}
/*
* Reset the quality level after making repairs.
*/
void A2File::ResetQuality(void)
{
fFileQuality = kQualityGood;
}
/*
* ===========================================================================
* DiskFS
* ===========================================================================
*/
/*
* Set the DiskImg pointer. We add or subtract from the DiskImg's ref count
* so that it can be sure there are no DiskFS objects left dangling when the
* DiskImg is deleted.
*/
void DiskFS::SetDiskImg(DiskImg* pImg)
{
if (pImg == NULL && fpImg == NULL) {
LOGI("SetDiskImg: no-op (both NULL)");
return;
} else if (fpImg == pImg) {
LOGI("SetDiskImg: no-op (old == new)");
return;
}
if (fpImg != NULL)
fpImg->RemoveDiskFS(this);
if (pImg != NULL)
pImg->AddDiskFS(this);
fpImg = pImg;
}
/*
* Flush changes to disk.
*/
DIError DiskFS::Flush(DiskImg::FlushMode mode)
{
SubVolume* pSubVol = GetNextSubVolume(NULL);
DIError dierr;
while (pSubVol != NULL) {
// quick sanity check
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
dierr = pSubVol->GetDiskFS()->Flush(mode); // recurse
if (dierr != kDIErrNone)
return dierr;
pSubVol = GetNextSubVolume(pSubVol);
}
assert(fpImg != NULL);
return fpImg->FlushImage(mode);
}
/*
* Set the "read only" flag on our DiskImg and those of our sub-volumes.
*/
void DiskFS::SetAllReadOnly(bool val)
{
SubVolume* pSubVol = GetNextSubVolume(NULL);
/* put current volume in read-only mode */
if (fpImg != NULL)
fpImg->SetReadOnly(val);
/* handle our kids */
while (pSubVol != NULL) {
// quick sanity check
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
//pSubVol->GetDiskImg()->SetReadOnly(val);
pSubVol->GetDiskFS()->SetAllReadOnly(val); // recurse
pSubVol = GetNextSubVolume(pSubVol);
}
}
/*
* The file list looks something like this:
*
* volume-dir
* file1
* file2
* subdir1
* subdir1:file1
* subdir1:file2
* subdir1:subsub1
* subdir1:subsub1:file1
* subdir1:subsub2
* subdir1:subsub2:file1
* subdir1:subsub2:file2
* subdir1:file3
* file3
*
* Everything contained within a subdir comes after the subdir entry and
* before any entries from later subdirs at the same level.
*
* It's unclear whether a linear list or a hierarchical tree structure is
* the most appropriate way to hold the data. The tree is easier to update,
* but the linear list corresponds to the primary view in CiderPress, and
* lists are simpler and easier to manage. For now I'm sticking with a list.
*
* The files MUST be in the order in which they came from the disk. This
* doesn't matter most of the time, but for Pascal volumes it's essential
* for ensuring that the Write command doesn't run over the next file.
*/
/*
* Add a file to the end of our list.
*/
void DiskFS::AddFileToList(A2File* pFile)
{
assert(pFile->GetNext() == NULL);
if (fpA2Head == NULL) {
assert(fpA2Tail == NULL);
fpA2Head = fpA2Tail = pFile;
} else {
pFile->SetPrev(fpA2Tail);
fpA2Tail->SetNext(pFile);
fpA2Tail = pFile;
}
}
/*
* Insert a file into its appropriate place in the list, based on a file
* hierarchy.
*
* Pass in the thing to be added ("pFile") and the previous entry ("pPrev").
* An empty hierarchic filesystem will have an entry for the volume dir, so
* we should never have an empty list or a NULL pPrev.
*
* The part where things go pear-shaped happens if "pPrev" is a subdirectory.
* If so, we need to come after all of the subdir's entries, including any
* entries for sub-subdirs. There's no graceful way to go about this in a
* linear list.
*
* (We'd love to be able to find the *next* entry and then back up one,
* but odds are that there isn't a "next" entry if we're busily creating
* files.)
*/
void DiskFS::InsertFileInList(A2File* pFile, A2File* pPrev)
{
assert(pFile->GetNext() == NULL);
if (fpA2Head == NULL) {
assert(pPrev == NULL);
fpA2Head = fpA2Tail = pFile;
return;
} else if (pPrev == NULL) {
// create two entries on DOS disk, delete first, add new file
pFile->SetNext(fpA2Head);
fpA2Head = pFile;
return;
}
/*
* If we're inserting after the parent (i.e. we're the very first thing
* in a subdir) or after a plain file, just drop it in.
*
* If we're inserting after a subdir, go fish.
*/
if (pPrev->IsDirectory() && pFile->GetParent() != pPrev) {
pPrev = SkipSubdir(pPrev);
}
pFile->SetNext(pPrev->GetNext());
pPrev->SetNext(pFile);
}
/*
* Skip over all entries in the subdir we're pointing to.
*
* The return value is the very last entry in the subdir.
*/
A2File* DiskFS::SkipSubdir(A2File* pSubdir)
{
if (pSubdir->GetNext() == NULL)
return pSubdir; // end of list reached -- subdir is empty
A2File* pCur = pSubdir;
A2File* pNext = NULL;
assert(pCur != NULL); // at least one time through the loop
while (pCur != NULL) {
pNext = pCur->GetNext();
if (pNext == NULL) // end of list reached
return pCur;
if (pNext->GetParent() != pSubdir) // end of dir reached
return pCur;
if (pNext->IsDirectory())
pCur = SkipSubdir(pNext); // get last entry in dir
else
pCur = pNext; // advance forward one
}
/* should never get here */
assert(false);
return pNext;
}
/*
* Delete a member from the list.
*
* We're currently singly-linked, making this rather expensive.
*/
void DiskFS::DeleteFileFromList(A2File* pFile)
{
if (fpA2Head == pFile) {
/* delete the head of the list */
fpA2Head = fpA2Head->GetNext();
delete pFile;
} else {
A2File* pCur = fpA2Head;
while (pCur != NULL) {
if (pCur->GetNext() == pFile) {
/* found it */
A2File* pNextNext = pCur->GetNext()->GetNext();
delete pCur->GetNext();
pCur->SetNext(pNextNext);
break;
}
pCur = pCur->GetNext();
}
if (pCur == NULL) {
LOGI("GLITCH: couldn't find element to delete!");
assert(false);
}
}
}
/*
* Access the "next" pointer.
*
* Because we apparently can't declare an anonymous class as a friend
* in MSVC++6.0, this can't be an inline function.
*/
A2File* DiskFS::GetNextFile(A2File* pFile) const
{
if (pFile == NULL)
return fpA2Head;
else
return pFile->GetNext();
}
/*
* Return the #of elements in the linear file list.
*
* Right now the only code that calls this is the disk info panel in
* CiderPress, so we don't need it to be efficient.
*/
long DiskFS::GetFileCount(void) const
{
long count = 0;
A2File* pFile = fpA2Head;
while (pFile != NULL) {
count++;
pFile = pFile->GetNext();
}
return count;
}
/*
* Delete all entries in the list.
*/
void DiskFS::DeleteFileList(void)
{
A2File* pFile;
A2File* pNext;
pFile = fpA2Head;
while (pFile != NULL) {
pNext = pFile->GetNext();
delete pFile;
pFile = pNext;
}
}
/*
* Dump file list.
*/
void DiskFS::DumpFileList(void)
{
A2File* pFile;
LOGI("DiskFS file list contents:");
pFile = GetNextFile(NULL);
while (pFile != NULL) {
LOGI(" %s", pFile->GetPathName());
pFile = GetNextFile(pFile);
}
}
/*
* Run through the list of files and find one that matches (case-insensitive).
*
* This does not attempt to open files in sub-volumes. We could, but it's
* likely that the application has "decorated" the name in some fashion,
* e.g. by prepending the sub-volume's volume name to the filename. May
* be best to let the application dig for the sub-volume.
*/
A2File* DiskFS::GetFileByName(const char* fileName, StringCompareFunc func)
{
A2File* pFile;
if (func == NULL)
func = ::strcasecmp;
pFile = GetNextFile(NULL);
while (pFile != NULL) {
if ((*func)(pFile->GetPathName(), fileName) == 0)
return pFile;
pFile = GetNextFile(pFile);
}
return NULL;
}
/*
* Add a sub-volume to the end of our list.
*
* Copies some parameters from "this" into pDiskFS, such as whether to
* scan for sub-volumes and the various DiskFS parameters.
*
* Note this happens AFTER the disk has been scanned.
*/
void DiskFS::AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS)
{
SubVolume* pSubVol;
/*
* Check the arguments.
*/
if (pDiskImg == NULL || pDiskFS == NULL) {
LOGI(" DiskFS bogus sub volume ptrs %08lx %08lx",
(long) pDiskImg, (long) pDiskFS);
assert(false);
return;
}
if (pDiskImg == fpImg || pDiskFS == this) {
LOGI(" DiskFS attempt to add self to sub-vol list");
assert(false);
return;
}
if (pDiskFS->GetDiskImg() == NULL) {
LOGI(" DiskFS lacks a DiskImg pointer");
assert(false);
return;
}
pSubVol = fpSubVolumeHead;
while (pSubVol != NULL) {
if (pSubVol->GetDiskImg() == pDiskImg ||
pSubVol->GetDiskFS() == pDiskFS)
{
LOGI(" DiskFS multiple adds on diskimg or diskfs");
assert(false);
return;
}
pSubVol = pSubVol->GetNext();
}
assert(pDiskFS->GetDiskImg() == pDiskImg);
/*
* Looks good. Add it.
*/
pSubVol = new SubVolume;
if (pSubVol == NULL)
return;
pSubVol->Create(pDiskImg, pDiskFS);
if (fpSubVolumeHead == NULL) {
assert(fpSubVolumeTail == NULL);
fpSubVolumeHead = fpSubVolumeTail = pSubVol;
} else {
pSubVol->SetPrev(fpSubVolumeTail);
fpSubVolumeTail->SetNext(pSubVol);
fpSubVolumeTail = pSubVol;
}
/* make sure inheritable stuff gets copied */
CopyInheritables(pDiskFS);
}
/*
* Copy parameters to a sub-volume.
*/
void DiskFS::CopyInheritables(DiskFS* pNewFS)
{
for (int i = 0; i < (int) NELEM(fParmTable); i++)
pNewFS->fParmTable[i] = fParmTable[i];
pNewFS->fScanForSubVolumes = fScanForSubVolumes;
#if 0
/* copy scan progress update stuff */
pNewFS->fpScanProgressCallback = fpScanProgressCallback;
pNewFS->fpScanProgressCookie = fpScanProgressCookie;
pNewFS->fpScanCount = -1;
strcpy(pNewFS->fpScanMsg, "HEY");
#endif
}
/*
* Access the "next" pointer.
*
* Because we apparently can't declare an anonymous class as a friend
* in MSVC++6.0, this can't be an inline function.
*/
DiskFS::SubVolume* DiskFS::GetNextSubVolume(const SubVolume* pSubVol) const
{
if (pSubVol == NULL)
return fpSubVolumeHead;
else
return pSubVol->GetNext();
}
/*
* Delete all entries in the list.
*/
void DiskFS::DeleteSubVolumeList(void)
{
SubVolume* pSubVol;
SubVolume* pNext;
pSubVol = fpSubVolumeHead;
while (pSubVol != NULL) {
pNext = pSubVol->GetNext();
delete pSubVol;
pSubVol = pNext;
}
}
/*
* Get a parameter.
*/
long DiskFS::GetParameter(DiskFSParameter parm)
{
assert(parm > kParmUnknown && parm < kParmMax);
return fParmTable[parm];
}
/*
* Set a parameter.
*
* The setting propagates to all sub-volumes.
*/
void DiskFS::SetParameter(DiskFSParameter parm, long val)
{
assert(parm > kParmUnknown && parm < kParmMax);
fParmTable[parm] = val;
SubVolume* pSubVol = GetNextSubVolume(NULL);
while (pSubVol != NULL) {
pSubVol->GetDiskFS()->SetParameter(parm, val);
pSubVol = GetNextSubVolume(pSubVol);
}
}
/*
* Scan for damaged or suspicious files.
*/
void DiskFS::ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious)
{
A2File* pFile;
*pDamaged = *pSuspicious = false;
pFile = GetNextFile(NULL);
while (pFile != NULL) {
if (pFile->GetQuality() == A2File::kQualityDamaged)
*pDamaged = true;
if (pFile->GetQuality() != A2File::kQualityGood)
*pSuspicious = true;
pFile = GetNextFile(pFile);
}
}

3502
diskimg/DiskImg.cpp Normal file

File diff suppressed because it is too large Load Diff

1686
diskimg/DiskImg.h Normal file

File diff suppressed because it is too large Load Diff

3320
diskimg/DiskImgDetail.h Normal file

File diff suppressed because it is too large Load Diff

348
diskimg/DiskImgPriv.h Normal file
View File

@ -0,0 +1,348 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Declarations common within but private to the DiskImg library.
*
* External code should not include this.
*/
#ifndef DISKIMG_DISKIMGPRIV_H
#define DISKIMG_DISKIMGPRIV_H
#include "DiskImgDetail.h"
#include <errno.h>
#include <assert.h>
// "GenericFD.h" included at end
using namespace DiskImgLib; // make life easy for all internal code
namespace DiskImgLib {
/*
* Debug logging macros.
*
* The macro choice implies a severity level, but we don't currently
* support that in the callback interface, so it's not used.
*/
#define DLOG_BASE(file, line, format, ...) \
Global::PrintDebugMsg((file), (line), (format), ##__VA_ARGS__)
//#ifdef SHOW_LOGV
# define LOGV(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
//#else
//# define LOGV(format, ...) ((void) 0)
//#endif
#define LOGD(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
#define LOGI(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
#define LOGW(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
#define LOGE(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
/* put this in to break on interesting events when built debug */
#if defined(_DEBUG)
# define DebugBreak() { assert(false); }
#else
# define DebugBreak() ((void) 0)
#endif
/*
* Standard goodies.
*/
#define NELEM(x) (sizeof(x) / sizeof(x[0]))
#define ErrnoOrGeneric() (errno != 0 ? (DIError) errno : kDIErrGeneric)
/* filename manipulation functions */
const char* FilenameOnly(const char* pathname, char fssep);
const char* FindExtension(const char* pathname, char fssep);
char* StrcpyNew(const char* str);
/* get/set integer values out of a memory buffer */
uint16_t GetShortLE(const uint8_t* buf);
uint32_t GetLongLE(const uint8_t* buf);
uint16_t GetShortBE(const uint8_t* buf);
uint32_t GetLongBE(const uint8_t* buf);
uint32_t Get24BE(const uint8_t* ptr);
void PutShortLE(uint8_t* ptr, uint16_t val);
void PutLongLE(uint8_t* ptr, uint32_t val);
void PutShortBE(uint8_t* ptr, uint16_t val);
void PutLongBE(uint8_t* ptr, uint32_t val);
/* little-endian read/write, for file headers (mainly 2MG and DC42) */
DIError ReadShortLE(GenericFD* pGFD, uint16_t* pBuf);
DIError ReadLongLE(GenericFD* pGFD, uint32_t* pBuf);
DIError WriteShortLE(FILE* fp, uint16_t val);
DIError WriteLongLE(FILE* fp, uint32_t val);
DIError WriteShortLE(GenericFD* pGFD, uint16_t val);
DIError WriteLongLE(GenericFD* pGFD, uint32_t val);
DIError WriteShortBE(GenericFD* pGFD, uint16_t val);
DIError WriteLongBE(GenericFD* pGFD, uint32_t val);
#ifdef _WIN32
/* Windows helpers */
DIError LastErrorToDIError(void);
bool IsWin9x(void);
#endif
/*
* Provide access to a buffer of data as if it were a circular buffer.
* Access is through the C array operator ([]).
*
* This DOES NOT own the array it is handed, and will not try to
* free it.
*/
class CircularBufferAccess {
public:
CircularBufferAccess(uint8_t* buf, long len) :
fBuf(buf), fLen(len)
{ assert(fLen > 0); assert(fBuf != NULL); }
CircularBufferAccess(const uint8_t* buf, long len) :
fBuf(const_cast<uint8_t*>(buf)), fLen(len)
{ assert(fLen > 0); assert(fBuf != NULL); }
~CircularBufferAccess(void) {}
/*
* Be circular. Assume that we won't stray far past the end, so
* it's cheaper to subtract than mod.
*/
uint8_t& operator[](int idx) const {
if (idx < 0) {
assert(false);
}
while (idx >= fLen)
idx -= fLen;
return fBuf[idx];
}
//uint8_t* GetPointer(int idx) const {
// while (idx >= fLen)
// idx -= fLen;
// return &fBuf[idx];
//}
int Normalize(int idx) const {
while (idx >= fLen)
idx -= fLen;
return idx;
}
long GetSize(void) const {
return fLen;
}
private:
uint8_t* fBuf;
long fLen;
};
/*
* Manage an output buffer into which we write one bit at a time.
*
* Bits fill in from the MSB to the LSB. If we write 10 bits, the
* output buffer will look like this:
*
* xxxxxxxx xx000000
*
* Call WriteBit() repeatedly. When done, call Finish() to write any pending
* data and return the number of bits in the buffer.
*/
class BitOutputBuffer {
public:
/* pass in the output buffer and the output buffer's size */
BitOutputBuffer(uint8_t* buf, int size) {
fBufStart = fBuf = buf;
fBufSize = size;
fBitMask = 0x80;
fByte = 0;
fOverflow = false;
}
virtual ~BitOutputBuffer(void) {}
/* write a single bit */
void WriteBit(int val) {
if (fBuf - fBufStart >= fBufSize) {
if (!fOverflow) {
LOGI("Overran bit output buffer");
DebugBreak();
fOverflow = true;
}
return;
}
if (val)
fByte |= fBitMask;
fBitMask >>= 1;
if (fBitMask == 0) {
*fBuf++ = fByte;
fBitMask = 0x80;
fByte = 0;
}
}
/* flush pending bits; returns length in bits (or -1 on overrun) */
int Finish(void) {
int outputBits;
if (fOverflow)
return -1;
outputBits = (fBuf - fBufStart) * 8;
if (fBitMask != 0x80) {
*fBuf++ = fByte;
assert(fBitMask != 0);
while (fBitMask != 0x80) {
outputBits++;
fBitMask <<= 1;
}
}
return outputBits;
}
private:
uint8_t* fBufStart;
uint8_t* fBuf;
int fBufSize;
uint8_t fBitMask;
uint8_t fByte;
bool fOverflow;
};
/*
* Extract data from the buffer one bit or one byte at a time.
*/
class BitInputBuffer {
public:
BitInputBuffer(const uint8_t* buf, int bitCount) {
fBufStart = fBuf = buf;
fBitCount = bitCount;
fCurrentBit = 0;
fBitPosn = 7;
fBitsConsumed = 0;
}
virtual ~BitInputBuffer(void) {}
/*
* Get the next bit. Returns 0 or 1.
*
* If we wrapped around to the start of the buffer, and "pWrap" is
* non-null, set "*pWrap". (This does *not* set it to "false" if we
* don't wrap.)
*/
uint8_t GetBit(bool* pWrap) {
uint8_t val;
//assert(fBitPosn == 7 - (fCurrentBit & 0x07));
if (fCurrentBit == fBitCount) {
/* end reached, wrap to start */
fCurrentBit = 0;
fBitPosn = 7;
fBuf = fBufStart;
//fByte = *fBuf++;
if (pWrap != NULL)
*pWrap = true;
}
val = (*fBuf >> fBitPosn) & 0x01;
fCurrentBit++;
fBitPosn--;
if (fBitPosn < 0) {
fBitPosn = 7;
fBuf++;
}
fBitsConsumed++;
return val;
}
/*
* Get the next 8 bits.
*/
uint8_t GetByte(bool* pWrap) {
uint8_t val;
int i;
if (true || fCurrentBit > fBitCount-8) {
/* near end, use single-bit function iteratively */
val = 0;
for (i = 0; i < 8; i++)
val = (val << 1) | GetBit(pWrap);
} else {
/* room to spare, grab it in one or two chunks */
assert(false);
}
return val;
}
/*
* Set the start position.
*/
void SetStartPosition(int bitOffset) {
assert(bitOffset >= 0 && bitOffset < fBitCount);
fCurrentBit = bitOffset;
fBitPosn = 7 - (bitOffset & 0x07); // mod 8, 0 to MSB
fBuf = fBufStart + (bitOffset >> 3); // div 8
}
/* used to ensure we consume exactly 100% of bits */
void ResetBitsConsumed(void) { fBitsConsumed = 0; }
int GetBitsConsumed(void) const { return fBitsConsumed; }
private:
const uint8_t* fBufStart;
const uint8_t* fBuf;
int fBitCount; // #of bits in buffer
int fCurrentBit; // where we are in buffer
int fBitPosn; // which bit to access within byte
//uint8_t fByte;
int fBitsConsumed; // sanity check - all bits used?
};
/*
* Linear bitmap. Suitable for use as a bad block map.
*/
class LinearBitmap {
public:
LinearBitmap(int numBits) {
assert(numBits > 0);
fBits = new uint8_t[(numBits + 7) / 8];
memset(fBits, 0, (numBits + 7) / 8);
fNumBits = numBits;
}
~LinearBitmap(void) {
delete[] fBits;
}
/*
* Set or get the status of bit N.
*/
bool IsSet(int bit) const {
assert(bit >= 0 && bit < fNumBits);
return ((fBits[bit >> 3] >> (bit & 0x07)) & 0x01) != 0;
}
void Set(int bit) {
assert(bit >= 0 && bit < fNumBits);
fBits[bit >> 3] |= 1 << (bit & 0x07);
}
private:
uint8_t* fBits;
int fNumBits;
};
} // namespace DiskImgLib
/*
* Most of the code needs these.
*/
#include "GenericFD.h"
#endif /*DISKIMG_DISKIMGPRIV_H*/

508
diskimg/FAT.cpp Normal file
View File

@ -0,0 +1,508 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of the Windows FAT filesystem.
*
* Right now we just try to identify that a disk is in a PC format rather
* than Apple II. The trick here is to figure out whether block 0 is a
* Master Boot Record or merely a Boot Sector.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSFAT
* ===========================================================================
*/
const int kBlkSize = 512;
const long kBootBlock = 0;
const uint16_t kSignature = 0xaa55; // MBR or boot sector
const int kSignatureOffset = 0x1fe;
const uint8_t kOpcodeMumble = 0x33; // seen on 2nd drive
const uint8_t kOpcodeBranch = 0xeb;
const uint8_t kOpcodeSetInt = 0xfa;
typedef struct PartitionTableEntry {
uint8_t driveNum; // dl (0x80 or 0x00)
uint8_t startHead; // dh
uint8_t startSector; // cl (&0x3f=sector, +two hi bits cyl)
uint8_t startCylinder; // ch (low 8 bits of 10-bit cylinder)
uint8_t type; // partition type
uint8_t endHead; // dh
uint8_t endSector; // cl
uint8_t endCylinder; // ch
uint32_t startLBA; // in blocks
uint32_t size; // in blocks
} PartitionTableEntry;
/*
* Definition of a Master Boot Record, which is block 0 of a physical volume.
*/
typedef struct DiskFSFAT::MasterBootRecord {
/*
* Begins immediately with code, usually 0xfa (set interrupt flag) or
* 0xeb (relative branch).
*/
uint8_t firstByte;
/*
* Partition table starts at 0x1be. Four entries, each 16 bytes.
*/
PartitionTableEntry parTab[4];
} MasterBootRecord;
/*
* Definition of a boot sector, which is block 0 of a logical volume.
*/
typedef struct DiskFSFAT::BootSector {
/*
* The first few bytes of the boot sector is called the BIOS Parameter
* Block, or BPB.
*/
uint8_t jump[3]; // usually EB XX 90
uint8_t oemName[8]; // e.g. "MSWIN4.1" or "MSDOS5.0"
uint16_t bytesPerSector; // usually (always?) 512
uint8_t sectPerCluster;
uint16_t reservedSectors;
uint8_t numFAT;
uint16_t numRootDirEntries;
uint16_t numSectors; // if set, ignore numSectorsHuge
uint8_t mediaType;
uint16_t numFATSectors;
uint16_t sectorsPerTrack;
uint16_t numHeads;
uint32_t numHiddenSectors;
uint32_t numSectorsHuge; // only if numSectors==0
/*
* This next part can start immediately after the above (at 0x24) for
* FAT12/FAT16, or somewhat later (0x42) for FAT32. It doesn't seem
* to exist for NTFS. Probably safest to assume it doesn't exist.
*
* The only way to be sure of what we're dealing with is to know the
* partition type, but if this is our block 0 then we can't know what
* that is.
*/
uint8_t driveNum;
uint8_t reserved;
uint8_t signature; // 0x29
uint32_t volumeID;
uint8_t volumeLabel[11]; // e.g. "FUBAR "
uint8_t fileSysType[8]; // e.g. "FAT12 "
/*
* Code follows. Signature 0xaa55 in the last two bytes.
*/
} BootSector;
// some values for MediaType
enum MediaType {
kMediaTypeLarge = 0xf0, // 1440KB or 2800KB 3.5" disk
kMediaTypeHardDrive = 0xf8,
kMediaTypeMedium = 0xf9, // 720KB 3.5" disk or 1.2MB 5.25" disk
kMediaTypeSmall = 0xfd, // 360KB 5.25" disk
};
/*
* Unpack the MBR.
*
* Returns "true" if this looks like an MBR, "false" otherwise.
*/
/*static*/ bool DiskFSFAT::UnpackMBR(const uint8_t* buf, MasterBootRecord* pOut)
{
const uint8_t* ptr;
int i;
pOut->firstByte = buf[0x00];
ptr = &buf[0x1be];
for (i = 0; i < 4; i++) {
pOut->parTab[i].driveNum = ptr[0x00];
pOut->parTab[i].startHead = ptr[0x01];
pOut->parTab[i].startSector = ptr[0x02];
pOut->parTab[i].startCylinder = ptr[0x03];
pOut->parTab[i].type = ptr[0x04];
pOut->parTab[i].endHead = ptr[0x05];
pOut->parTab[i].endSector = ptr[0x06];
pOut->parTab[i].endCylinder = ptr[0x07];
pOut->parTab[i].startLBA = GetLongLE(&ptr[0x08]);
pOut->parTab[i].size = GetLongLE(&ptr[0x0c]);
ptr += 16;
}
if (pOut->firstByte != kOpcodeBranch &&
pOut->firstByte != kOpcodeSetInt &&
pOut->firstByte != kOpcodeMumble)
return false;
bool foundActive = false;
for (i = 0; i < 4; i++) {
if (pOut->parTab[i].driveNum == 0x80)
foundActive = true;
else if (pOut->parTab[i].driveNum != 0x00)
return false; // must be 0x00 or 0x80
}
// CFFA cards don't seem to set the "active" flag
if (false && !foundActive)
return false;
return true;
}
/*
* Unpack the boot sector.
*
* Returns "true" if this looks like a boot sector, "false" otherwise.
*/
/*static*/ bool DiskFSFAT::UnpackBootSector(const uint8_t* buf, BootSector* pOut)
{
memcpy(pOut->jump, &buf[0x00], sizeof(pOut->jump));
memcpy(pOut->oemName, &buf[0x03], sizeof(pOut->oemName));
pOut->bytesPerSector = GetShortLE(&buf[0x0b]);
pOut->sectPerCluster = buf[0x0d];
pOut->reservedSectors = GetShortLE(&buf[0x0e]);
pOut->numFAT = buf[0x10];
pOut->numRootDirEntries = GetShortLE(&buf[0x11]);
pOut->numSectors = GetShortLE(&buf[0x13]);
pOut->mediaType = buf[0x15];
pOut->numFATSectors = GetShortLE(&buf[0x16]);
pOut->sectorsPerTrack = GetShortLE(&buf[0x18]);
pOut->numHeads = GetShortLE(&buf[0x1a]);
pOut->numHiddenSectors = GetLongLE(&buf[0x1c]);
pOut->numSectorsHuge = GetLongLE(&buf[0x20]);
if (pOut->jump[0] != kOpcodeBranch && pOut->jump[0] != kOpcodeSetInt)
return false;
if (pOut->bytesPerSector != 512)
return false;
return true;
}
/*
* See if this looks like a FAT volume.
*/
/*static*/ DIError DiskFSFAT::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t blkBuf[kBlkSize];
MasterBootRecord mbr;
BootSector bs;
dierr = pImg->ReadBlockSwapped(kBootBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
/*
* Both MBR and boot sectors have the same signature in block 0.
*/
if (GetShortLE(&blkBuf[kSignatureOffset]) != kSignature) {
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/*
* Decode it as an MBR and as a partition table. Figure out which
* one makes sense. If neither make sense, fail.
*/
bool hasMBR, hasBS;
hasMBR = UnpackMBR(blkBuf, &mbr);
hasBS = UnpackBootSector(blkBuf, &bs);
LOGI(" FAT hasMBR=%d hasBS=%d", hasMBR, hasBS);
if (!hasMBR && !hasBS) {
dierr = kDIErrFilesystemNotFound;
goto bail;
}
if (hasMBR) {
LOGI(" FAT partition table found:");
for (int i = 0; i < 4; i++) {
LOGI(" %d: type=0x%02x start LBA=%-9u size=%u",
i, mbr.parTab[i].type,
mbr.parTab[i].startLBA, mbr.parTab[i].size);
}
}
if (hasBS) {
LOGI(" FAT boot sector found:");
LOGI(" OEMName is '%.8s'", bs.oemName);
}
// looks good!
bail:
return dierr;
}
/*
* Test to see if the image is a FAT disk.
*/
/*static*/ DIError DiskFSFAT::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* must be block format, should be at least 360K */
if (!pImg->GetHasBlocks() || pImg->GetNumBlocks() < kExpectedMinBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for FAT inside CFFA!
return kDIErrFilesystemNotFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
*pOrder = ordering[i];
*pFormat = DiskImg::kFormatMSDOS;
return kDIErrNone;
}
}
LOGI(" FAT didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Get things rolling.
*/
DIError DiskFSFAT::Initialize(void)
{
DIError dierr = kDIErrNone;
strcpy(fVolumeName, "[MS-DOS]"); // max 11 chars
strcpy(fVolumeID, "FATxx [MS-DOS]");
// take the easy way out
fTotalBlocks = fpImg->GetNumBlocks();
CreateFakeFile();
SetVolumeUsageMap();
return dierr;
}
/*
* Blank out the volume usage map.
*/
void DiskFSFAT::SetVolumeUsageMap(void)
{
VolumeUsage::ChunkState cstate;
long block;
fVolumeUsage.Create(fpImg->GetNumBlocks());
cstate.isUsed = true;
cstate.isMarkedUsed = true;
cstate.purpose = VolumeUsage::kChunkPurposeUnknown;
for (block = fTotalBlocks-1; block >= 0; block--)
fVolumeUsage.SetChunkState(block, &cstate);
}
/*
* Fill a buffer with some interesting stuff, and add it to the file list.
*/
void DiskFSFAT::CreateFakeFile(void)
{
A2FileFAT* pFile;
char buf[768]; // currently running about 430
static const char* kFormatMsg =
"The FAT12/16/32 and NTFS filesystems are not supported. CiderPress knows\r\n"
"how to recognize MS-DOS and Windows volumes so that it can identify\r\n"
"PC data on removable media, but it does not know how to view or extract\r\n"
"files from them.\r\n"
"\r\n"
"Some information about this FAT volume:\r\n"
"\r\n"
" Volume name : '%s'\r\n"
" Volume size : %ld blocks (%.2fMB)\r\n"
"\r\n"
"(CiderPress limits itself to 8GB, so larger volume sizes may not be shown.)\r\n"
;
long capacity;
capacity = fTotalBlocks;
memset(buf, 0, sizeof(buf));
snprintf(buf, NELEM(buf)-1, kFormatMsg,
fVolumeName,
capacity,
(double) capacity / 2048.0);
buf[sizeof(buf) - 1] = '\0';
pFile = new A2FileFAT(this);
pFile->SetFakeFile(buf, strlen(buf));
strcpy(pFile->fFileName, "(not supported)");
AddFileToList(pFile);
}
/*
* ===========================================================================
* A2FileFAT
* ===========================================================================
*/
/*
* Dump the contents of the A2File structure.
*/
void A2FileFAT::Dump(void) const
{
LOGD("A2FileFAT '%s'", fFileName);
}
/*
* Not a whole lot to do.
*/
DIError A2FileFAT::Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork /*=false*/)
{
A2FDFAT* pOpenFile = NULL;
if (fpOpenFile != NULL)
return kDIErrAlreadyOpen;
if (rsrcFork)
return kDIErrForkNotFound;
assert(readOnly == true);
pOpenFile = new A2FDFAT(this);
fpOpenFile = pOpenFile;
*ppOpenFile = pOpenFile;
return kDIErrNone;
}
/*
* ===========================================================================
* A2FDFAT
* ===========================================================================
*/
/*
* Read a chunk of data from the fake file.
*/
DIError A2FDFAT::Read(void* buf, size_t len, size_t* pActual)
{
LOGD(" FAT reading %lu bytes from '%s' (offset=%ld)",
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
A2FileFAT* pFile = (A2FileFAT*) fpFile;
/* don't allow them to read past the end of the file */
if (fOffset + (long)len > pFile->fLength) {
if (pActual == NULL)
return kDIErrDataUnderrun;
len = (size_t) (pFile->fLength - fOffset);
}
if (pActual != NULL)
*pActual = len;
memcpy(buf, pFile->GetFakeFileBuf(), len);
fOffset += len;
return kDIErrNone;
}
/*
* Write data at the current offset.
*/
DIError A2FDFAT::Write(const void* buf, size_t len, size_t* pActual)
{
return kDIErrNotSupported;
}
/*
* Seek to a new offset.
*/
DIError A2FDFAT::Seek(di_off_t offset, DIWhence whence)
{
di_off_t fileLen = ((A2FileFAT*) fpFile)->fLength;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset > fileLen)
return kDIErrInvalidArg;
fOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fileLen)
return kDIErrInvalidArg;
fOffset = fileLen + offset;
break;
case kSeekCur:
if (offset < -fOffset ||
offset >= (fileLen - fOffset))
{
return kDIErrInvalidArg;
}
fOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fOffset >= 0 && fOffset <= fileLen);
return kDIErrNone;
}
/*
* Return current offset.
*/
di_off_t A2FDFAT::Tell(void)
{
return fOffset;
}
/*
* Release file state, and tell our parent to destroy us.
*/
DIError A2FDFAT::Close(void)
{
fpFile->CloseDescr(this);
return kDIErrNone;
}
/*
* Return the #of sectors/blocks in the file.
*/
long A2FDFAT::GetSectorCount(void) const
{
A2FileFAT* pFile = (A2FileFAT*) fpFile;
return (long) ((pFile->fLength+255) / 256);
}
long A2FDFAT::GetBlockCount(void) const
{
A2FileFAT* pFile = (A2FileFAT*) fpFile;
return (long) ((pFile->fLength+511) / 512);
}
/*
* Return the Nth track/sector in this file.
*/
DIError A2FDFAT::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
{
return kDIErrNotSupported;
}
/*
* Return the Nth 512-byte block in this file.
*/
DIError A2FDFAT::GetStorage(long blockIdx, long* pBlock) const
{
return kDIErrNotSupported;
}

1522
diskimg/FDI.cpp Normal file

File diff suppressed because it is too large Load Diff

353
diskimg/FocusDrive.cpp Normal file
View File

@ -0,0 +1,353 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* This is a container for the Parsons Engineering FocusDrive.
*
* The format was reverse-engineered by Ranger Harke.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
const int kBlkSize = 512;
const int kPartMapBlock = 0; // partition map lives here
const int kMaxPartitions = 30; // max allowed partitions on a drive
const int kPartNameStart = 1; // partition names start here (2 blocks)
const int kPartNameLen = 32; // max length of partition name
static const char* kSignature = "Parsons Engin.";
const int kSignatureLen = 14;
/*
* Format of partition map. It resides in the first 256 bytes of block 0.
* All values are in little-endian order.
*
* We also make space here for the partition names, which live on blocks 1+2.
*/
typedef struct DiskFSFocusDrive::PartitionMap {
uint8_t signature[kSignatureLen];
uint8_t unknown1;
uint8_t partCount; // could be ushort, combined w/unknown1
uint8_t unknown2[16];
struct Entry {
uint32_t startBlock;
uint32_t blockCount;
uint32_t unknown1;
uint32_t unknown2;
uint8_t name[kPartNameLen+1];
} entry[kMaxPartitions];
} PartitionMap;
/*
* Figure out if this is a FocusDrive partition.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*/
/*static*/ DIError DiskFSFocusDrive::TestImage(DiskImg* pImg,
DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t blkBuf[kBlkSize];
int partCount;
/*
* See if block 0 is a FocusDrive partition map.
*/
dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
if (memcmp(blkBuf, kSignature, kSignatureLen) != 0) {
LOGI(" FocusDrive partition signature not found in first part block");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
partCount = blkBuf[0x0f];
if (partCount == 0 || partCount > kMaxPartitions) {
LOGI(" FocusDrive partition count looks bad (%d)", partCount);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
// success!
LOGI(" Looks like FocusDrive with %d partitions", partCount);
bail:
return dierr;
}
/*
* Unpack a partition map block into a partition map data structure.
*/
/*static*/ void DiskFSFocusDrive::UnpackPartitionMap(const uint8_t* buf,
const uint8_t* nameBuf, PartitionMap* pMap)
{
const uint8_t* ptr;
const uint8_t* namePtr;
int i;
memcpy(pMap->signature, &buf[0x00], kSignatureLen);
pMap->unknown1 = buf[0x0e];
pMap->partCount = buf[0x0f];
memcpy(pMap->unknown2, &buf[0x10], 16);
ptr = &buf[0x20];
namePtr = &nameBuf[kPartNameLen]; // not sure what first 32 bytes are
for (i = 0; i < kMaxPartitions; i++) {
pMap->entry[i].startBlock = GetLongLE(ptr);
pMap->entry[i].blockCount = GetLongLE(ptr+4);
pMap->entry[i].unknown1 = GetLongLE(ptr+8);
pMap->entry[i].unknown2 = GetLongLE(ptr+12);
memcpy(pMap->entry[i].name, namePtr, kPartNameLen);
pMap->entry[i].name[kPartNameLen] = '\0';
ptr += 0x10;
namePtr += kPartNameLen;
}
assert(ptr == buf + kBlkSize);
}
/*
* Debug: dump the contents of the partition map.
*/
/*static*/ void DiskFSFocusDrive::DumpPartitionMap(const PartitionMap* pMap)
{
int i;
LOGI(" FocusDrive partition map (%d partitions):", pMap->partCount);
for (i = 0; i < pMap->partCount; i++) {
LOGI(" %2d: %8d %8d '%s'", i, pMap->entry[i].startBlock,
pMap->entry[i].blockCount, pMap->entry[i].name);
}
}
/*
* Open up a sub-volume.
*/
DIError DiskFSFocusDrive::OpenSubVolume(long startBlock, long numBlocks,
const char* name)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
//bool tweaked = false;
LOGI("Adding %ld +%ld", startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
LOGI("FocusDrive start block out of range (%ld vs %ld)",
startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
LOGI("FocusDrive partition too large (%ld vs %ld avail)",
numBlocks, fpImg->GetNumBlocks() - startBlock);
fpImg->AddNote(DiskImg::kNoteInfo,
"Reduced partition from %ld blocks to %ld.\n",
numBlocks, fpImg->GetNumBlocks() - startBlock);
numBlocks = fpImg->GetNumBlocks() - startBlock;
//tweaked = true;
}
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" FocusDriveSub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
/* figure out what the format is */
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" FocusDriveSub: analysis failed (err=%d)", dierr);
goto bail;
}
/* we allow unrecognized partitions */
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" FocusDriveSub (%ld,%ld): unable to identify filesystem",
startBlock, numBlocks);
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
if (pUnknownFS == NULL) {
dierr = kDIErrInternal;
goto bail;
}
//pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
pNewFS = pUnknownFS;
} else {
/* open a DiskFS for the sub-image */
LOGI(" FocusDriveSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == NULL) {
LOGI(" FocusDriveSub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
}
pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s'.", name);
/* we encapsulate arbitrary stuff, so encourage child to scan */
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
/*
* Load the files from the sub-image. When doing our initial tests,
* or when loading data for the volume copier, we don't want to dig
* into our sub-volumes, just figure out what they are and where.
*
* If "initialize" fails, the sub-volume won't get added to the list.
* It's important that a failure at this stage doesn't cause the whole
* thing to fall over.
*/
InitMode initMode;
if (GetScanForSubVolumes() == kScanSubContainerOnly)
initMode = kInitHeaderOnly;
else
initMode = kInitFull;
dierr = pNewFS->Initialize(pNewImg, initMode);
if (dierr != kDIErrNone) {
LOGE(" FocusDriveSub: error %d reading list of files from disk", dierr);
goto bail;
}
/* add it to the list */
AddSubVolumeToList(pNewImg, pNewFS);
pNewImg = NULL;
pNewFS = NULL;
bail:
delete pNewFS;
delete pNewImg;
return dierr;
}
/*
* Check to see if this is a FocusDrive volume.
*/
/*static*/ DIError DiskFSFocusDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for partitions inside
return kDIErrFilesystemNotFound;
/* assume ProDOS -- shouldn't matter, since it's embedded */
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
*pFormat = DiskImg::kFormatFocusDrive;
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
LOGI(" FS didn't find valid FocusDrive");
return kDIErrFilesystemNotFound;
}
/*
* Prep the FocusDrive "container" for use.
*/
DIError DiskFSFocusDrive::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("FocusDrive initializing (scanForSub=%d)", fScanForSubVolumes);
/* seems pointless *not* to, but we just do what we're told */
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = FindSubVolumes();
if (dierr != kDIErrNone)
return dierr;
}
/* blank out the volume usage map */
SetVolumeUsageMap();
return dierr;
}
/*
* Find the various sub-volumes and open them.
*/
DIError DiskFSFocusDrive::FindSubVolumes(void)
{
DIError dierr = kDIErrNone;
uint8_t buf[kBlkSize];
uint8_t nameBuf[kBlkSize*2];
PartitionMap map;
int i;
dierr = fpImg->ReadBlock(kPartMapBlock, buf);
if (dierr != kDIErrNone)
goto bail;
dierr = fpImg->ReadBlock(kPartNameStart, nameBuf);
if (dierr != kDIErrNone)
goto bail;
dierr = fpImg->ReadBlock(kPartNameStart+1, nameBuf+kBlkSize);
if (dierr != kDIErrNone)
goto bail;
UnpackPartitionMap(buf, nameBuf, &map);
DumpPartitionMap(&map);
for (i = 0; i < map.partCount; i++) {
dierr = OpenVol(i, map.entry[i].startBlock, map.entry[i].blockCount,
(const char*)map.entry[i].name);
if (dierr != kDIErrNone)
goto bail;
}
bail:
return dierr;
}
/*
* Open the volume. If it fails, open a placeholder instead. (If *that*
* fails, return with an error.)
*/
DIError DiskFSFocusDrive::OpenVol(int idx, long startBlock, long numBlocks,
const char* name)
{
DIError dierr;
dierr = OpenSubVolume(startBlock, numBlocks, name);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
LOGI(" FocusDrive failed opening sub-volume %d", idx);
dierr = CreatePlaceholder(startBlock, numBlocks, name, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGI(" FocusDrive unable to create placeholder (err=%d)",
dierr);
// fall out with error
}
}
bail:
return dierr;
}

866
diskimg/GenericFD.cpp Normal file
View File

@ -0,0 +1,866 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Generic file descriptor class.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* GenericFD utility functions
* ===========================================================================
*/
/*
* Copy "length" bytes from "pSrc" to "pDst". Both GenericFDs should be
* seeked to their initial positions.
*
* If "pCRC" is non-NULL, this computes a CRC32 as it goes, using the zlib
* library function.
*/
/*static*/ DIError GenericFD::CopyFile(GenericFD* pDst, GenericFD* pSrc,
di_off_t length, uint32_t* pCRC)
{
DIError dierr = kDIErrNone;
const int kCopyBufSize = 32768;
uint8_t* copyBuf = NULL;
int copySize;
LOGD("+++ CopyFile: %ld bytes", (long) length);
if (pDst == NULL || pSrc == NULL || length < 0)
return kDIErrInvalidArg;
if (length == 0)
return kDIErrNone;
copyBuf = new uint8_t[kCopyBufSize];
if (copyBuf == NULL)
return kDIErrMalloc;
if (pCRC != NULL)
*pCRC = crc32(0L, Z_NULL, 0);
while (length != 0) {
copySize = kCopyBufSize;
if (copySize > length)
copySize = (int) length;
dierr = pSrc->Read(copyBuf, copySize);
if (dierr != kDIErrNone)
goto bail;
if (pCRC != NULL)
*pCRC = crc32(*pCRC, copyBuf, copySize);
dierr = pDst->Write(copyBuf, copySize);
if (dierr != kDIErrNone)
goto bail;
length -= copySize;
}
bail:
delete[] copyBuf;
return dierr;
}
/*
* ===========================================================================
* GFDFile
* ===========================================================================
*/
/*
* The stdio functions (fopen/fread/fwrite/fseek/ftell) are buffered and,
* therefore, faster for small operations. Unfortunately we need 64-bit
* file offsets, and it doesn't look like the Windows stdio stuff will
* support it cleanly (e.g. even the _FPOSOFF macro returns a "long").
*
* Recent versions of Linux have "fseeko", which is like fseek but takes
* an off_t, so we can continue to use the FILE* functions there. Under
* Windows "lseek" takes a long, so we have to use their specific 64-bit
* variant.
*
* TODO: Visual Studio 2005 added _fseeki64. We should be able to merge
* the bulk of the implementation now.
*/
#ifdef HAVE_FSEEKO
DIError GFDFile::Open(const char* filename, bool readOnly)
{
DIError dierr = kDIErrNone;
if (fFp != NULL)
return kDIErrAlreadyOpen;
if (filename == NULL)
return kDIErrInvalidArg;
if (filename[0] == '\0')
return kDIErrInvalidArg;
delete[] fPathName;
fPathName = new char[strlen(filename) +1];
strcpy(fPathName, filename);
fFp = fopen(filename, readOnly ? "rb" : "r+b");
if (fFp == NULL) {
if (errno == EACCES)
dierr = kDIErrAccessDenied;
else
dierr = ErrnoOrGeneric();
LOGI(" GDFile Open failed opening '%s', ro=%d (err=%d)",
filename, readOnly, dierr);
return dierr;
}
fReadOnly = readOnly;
return dierr;
}
DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
{
DIError dierr = kDIErrNone;
size_t actual;
if (fFp == NULL)
return kDIErrNotReady;
actual = ::fread(buf, 1, length, fFp);
if (actual == 0) {
if (feof(fFp))
return kDIErrEOF;
if (ferror(fFp)) {
dierr = ErrnoOrGeneric();
return dierr;
}
LOGI("MYSTERY FREAD RESULT");
return kDIErrInternal;
}
if (pActual == NULL) {
if (actual != length) {
dierr = ErrnoOrGeneric();
LOGW(" GDFile Read failed on %lu bytes (actual=%lu, err=%d)",
(unsigned long) length, (unsigned long) actual, dierr);
return dierr;
}
} else {
*pActual = actual;
}
return dierr;
}
DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
{
DIError dierr = kDIErrNone;
if (fFp == NULL)
return kDIErrNotReady;
if (fReadOnly)
return kDIErrAccessDenied;
assert(pActual == NULL); // not handling this yet
if (::fwrite(buf, length, 1, fFp) != 1) {
dierr = ErrnoOrGeneric();
LOGW(" GDFile Write failed on %lu bytes (err=%d)",
(unsigned long) length, dierr);
return dierr;
}
return dierr;
}
DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
{
DIError dierr = kDIErrNone;
//static const long kOneGB = 1024*1024*1024;
//static const long kAlmostTwoGB = kOneGB + (kOneGB -1);
if (fFp == NULL)
return kDIErrNotReady;
//assert(offset <= kAlmostTwoGB);
//if (::fseek(fFp, (long) offset, whence) != 0) {
if (::fseeko(fFp, offset, whence) != 0) {
dierr = ErrnoOrGeneric();
LOGI(" GDFile Seek failed (err=%d)", dierr);
return dierr;
}
return dierr;
}
di_off_t GFDFile::Tell(void)
{
DIError dierr = kDIErrNone;
di_off_t result;
if (fFp == NULL)
return kDIErrNotReady;
//result = ::ftell(fFp);
result = ::ftello(fFp);
if (result == -1) {
dierr = ErrnoOrGeneric();
LOGI(" GDFile Tell failed (err=%d)", dierr);
return result;
}
return result;
}
DIError GFDFile::Truncate(void)
{
#if defined(HAVE_FTRUNCATE)
int cc;
cc = ::ftruncate(fileno(fFp), (long) Tell());
if (cc != 0)
return kDIErrWriteFailed;
#elif defined(HAVE_CHSIZE)
assert(false); // not tested
int cc;
cc = ::chsize(fFd, (long) Tell());
if (cc != 0)
return kDIErrWriteFailed;
#else
# error "missing truncate"
#endif
return kDIErrNone;
}
DIError GFDFile::Close(void)
{
if (fFp == NULL)
return kDIErrNotReady;
LOGI(" GFDFile closing '%s'", fPathName);
fclose(fFp);
fFp = NULL;
return kDIErrNone;
}
#else /*HAVE_FSEEKO*/
DIError GFDFile::Open(const char* filename, bool readOnly)
{
DIError dierr = kDIErrNone;
if (fFd >= 0)
return kDIErrAlreadyOpen;
if (filename == NULL)
return kDIErrInvalidArg;
if (filename[0] == '\0')
return kDIErrInvalidArg;
delete[] fPathName;
fPathName = new char[strlen(filename) +1];
strcpy(fPathName, filename);
fFd = open(filename, readOnly ? O_RDONLY|O_BINARY : O_RDWR|O_BINARY, 0);
if (fFd < 0) {
if (errno == EACCES) {
dierr = kDIErrAccessDenied;
} else if (errno == EINVAL) {
// Happens on Win32 if a Unicode filename conversion failed,
// because non-converting chars turn into '?', which is illegal.
dierr = kDIErrInvalidArg;
} else {
dierr = ErrnoOrGeneric();
}
LOGW(" GDFile Open failed opening '%s', ro=%d (err=%d)",
filename, readOnly, dierr);
return dierr;
}
fReadOnly = readOnly;
return dierr;
}
DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
{
DIError dierr;
ssize_t actual;
if (fFd < 0)
return kDIErrNotReady;
actual = ::read(fFd, buf, length);
if (actual == 0)
return kDIErrEOF;
if (actual < 0) {
dierr = ErrnoOrGeneric();
LOGW(" GDFile Read failed on %d bytes (actual=%d, err=%d)",
length, actual, dierr);
return dierr;
}
if (pActual == NULL) {
if (actual != (ssize_t) length) {
LOGI(" GDFile Read partial (wanted=%d actual=%d)",
length, actual);
return kDIErrReadFailed;
}
} else {
*pActual = actual;
}
return kDIErrNone;
}
DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
{
DIError dierr;
ssize_t actual;
if (fFd < 0)
return kDIErrNotReady;
if (fReadOnly)
return kDIErrAccessDenied;
assert(pActual == NULL); // not handling partial writes yet
actual = ::write(fFd, buf, length);
if (actual != (ssize_t) length) {
dierr = ErrnoOrGeneric();
LOGI(" GDFile Write failed on %d bytes (actual=%d err=%d)",
length, actual, dierr);
return dierr;
}
return kDIErrNone;
}
DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
{
DIError dierr = kDIErrNone;
if (fFd < 0)
return kDIErrNotReady;
#ifdef WIN32
__int64 newPosn;
const __int64 kFailure = (__int64) -1;
newPosn = ::_lseeki64(fFd, (__int64) offset, whence);
#else
di_off_t newPosn;
const di_off_t kFailure = (di_off_t) -1;
newPosn = lseek(fFd, offset, whence);
#endif
if (newPosn == kFailure) {
assert((uint32_t) offset != 0xccccccccUL); // uninitialized data!
dierr = ErrnoOrGeneric();
LOGI(" GDFile Seek %ld-%lu failed (err=%d)",
(long) (offset >> 32), (uint32_t) offset, dierr);
}
return dierr;
}
di_off_t GFDFile::Tell(void)
{
DIError dierr = kDIErrNone;
di_off_t result;
if (fFd < 0)
return kDIErrNotReady;
#ifdef WIN32
result = ::_lseeki64(fFd, 0, SEEK_CUR);
#else
result = lseek(fFd, 0, SEEK_CUR);
#endif
if (result == -1) {
dierr = ErrnoOrGeneric();
LOGI(" GDFile Tell failed (err=%d)", dierr);
return result;
}
return result;
}
DIError GFDFile::Truncate(void)
{
#if defined(HAVE_FTRUNCATE)
int cc;
cc = ::ftruncate(fFd, (long) Tell());
if (cc != 0)
return kDIErrWriteFailed;
#elif defined(HAVE_CHSIZE)
int cc;
cc = ::chsize(fFd, (long) Tell());
if (cc != 0)
return kDIErrWriteFailed;
#else
# error "missing truncate"
#endif
return kDIErrNone;
}
DIError GFDFile::Close(void)
{
if (fFd < 0)
return kDIErrNotReady;
LOGI(" GFDFile closing '%s'", fPathName);
::close(fFd);
fFd = -1;
return kDIErrNone;
}
#endif /*HAVE_FSEEKO else*/
/*
* ===========================================================================
* GFDBuffer
* ===========================================================================
*/
DIError GFDBuffer::Open(void* buffer, di_off_t length, bool doDelete,
bool doExpand, bool readOnly)
{
if (fBuffer != NULL)
return kDIErrAlreadyOpen;
if (length <= 0)
return kDIErrInvalidArg;
if (length > kMaxReasonableSize) {
// be reasonable
LOGI(" GFDBuffer refusing to allocate buffer size(long)=%ld bytes",
(long) length);
return kDIErrInvalidArg;
}
/* if buffer is NULL, allocate it ourselves */
if (buffer == NULL) {
fBuffer = (void*) new uint8_t[(int) length];
if (fBuffer == NULL)
return kDIErrMalloc;
} else
fBuffer = buffer;
fLength = (long) length;
fAllocLength = (long) length;
fDoDelete = doDelete;
fDoExpand = doExpand;
fReadOnly = readOnly;
fCurrentOffset = 0;
return kDIErrNone;
}
DIError GFDBuffer::Read(void* buf, size_t length, size_t* pActual)
{
if (fBuffer == NULL)
return kDIErrNotReady;
if (length == 0)
return kDIErrInvalidArg;
if (fCurrentOffset + (long)length > fLength) {
if (pActual == NULL) {
LOGW(" GFDBuffer underrrun off=%ld len=%lu flen=%ld",
(long) fCurrentOffset, (unsigned long) length, (long) fLength);
return kDIErrDataUnderrun;
} else {
/* set *pActual and adjust "length" */
assert(fLength >= fCurrentOffset);
length = (size_t) (fLength - fCurrentOffset);
*pActual = length;
if (length == 0)
return kDIErrEOF;
}
}
if (pActual != NULL)
*pActual = length;
memcpy(buf, (const char*)fBuffer + fCurrentOffset, length);
fCurrentOffset += length;
return kDIErrNone;
}
DIError GFDBuffer::Write(const void* buf, size_t length, size_t* pActual)
{
if (fBuffer == NULL)
return kDIErrNotReady;
assert(pActual == NULL); // not handling this yet
if (fCurrentOffset + (long)length > fLength) {
if (!fDoExpand) {
LOGI(" GFDBuffer overrun off=%ld len=%lu flen=%ld",
(long) fCurrentOffset, (unsigned long) length, (long) fLength);
return kDIErrDataOverrun;
}
/*
* Expand the buffer as needed.
*
* We delete the old buffer unless "doDelete" is not set, in
* which case we just drop the pointer. Anything we allocate
* here can and will be deleted; "doDelete" only applies to the
* pointer initially passed in.
*/
if (fCurrentOffset + (long)length <= fAllocLength) {
/* fits inside allocated space, so just extend length */
fLength = (long) fCurrentOffset + (long)length;
} else {
/* does not fit, realloc buffer */
fAllocLength = (long) fCurrentOffset + (long)length + 8*1024;
LOGI("Reallocating buffer (new size = %ld)", fAllocLength);
assert(fAllocLength < kMaxReasonableSize);
char* newBuf = new char[(int) fAllocLength];
if (newBuf == NULL)
return kDIErrMalloc;
memcpy(newBuf, fBuffer, fLength);
if (fDoDelete)
delete[] (char*)fBuffer;
else
fDoDelete = true; // future deletions are okay
fBuffer = newBuf;
fLength = (long) fCurrentOffset + (long)length;
}
}
memcpy((char*)fBuffer + fCurrentOffset, buf, length);
fCurrentOffset += length;
return kDIErrNone;
}
DIError GFDBuffer::Seek(di_off_t offset, DIWhence whence)
{
if (fBuffer == NULL)
return kDIErrNotReady;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset >= fLength)
return kDIErrInvalidArg;
fCurrentOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fLength)
return kDIErrInvalidArg;
fCurrentOffset = fLength + offset;
break;
case kSeekCur:
if (offset < -fCurrentOffset ||
offset >= (fLength - fCurrentOffset))
{
return kDIErrInvalidArg;
}
fCurrentOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fCurrentOffset >= 0 && fCurrentOffset <= fLength);
return kDIErrNone;
}
di_off_t GFDBuffer::Tell(void)
{
if (fBuffer == NULL)
return (di_off_t) -1;
return fCurrentOffset;
}
DIError GFDBuffer::Close(void)
{
if (fBuffer == NULL)
return kDIErrNone;
if (fDoDelete) {
LOGI(" GFDBuffer closing and deleting");
delete[] (char*) fBuffer;
} else {
LOGI(" GFDBuffer closing");
}
fBuffer = NULL;
return kDIErrNone;
}
#ifdef _WIN32
/*
* ===========================================================================
* GFDWinVolume
* ===========================================================================
*/
/*
* This class is intended for use with logical volumes under Win32. Such
* devices must be accessed on 512-byte boundaries, which means no arbitrary
* seeks or reads. The device driver doesn't seem too adept at figuring
* out how large the device is, either, so we need to work that out for
* ourselves. (The standard approach appears to involve examining the
* partition map for the logical or physical volume, but we don't have a
* partition map to look at.)
*/
/*
* Prepare a logical volume device for reading or writing. "deviceName"
* must have the form "N:\" for a logical volume or "80:\" for a physical
* volume.
*/
DIError GFDWinVolume::Open(const char* deviceName, bool readOnly)
{
DIError dierr = kDIErrNone;
HANDLE handle = NULL;
//uint32_t kTwoGBBlocks;
if (fVolAccess.Ready())
return kDIErrAlreadyOpen;
if (deviceName == NULL)
return kDIErrInvalidArg;
if (deviceName[0] == '\0')
return kDIErrInvalidArg;
delete[] fPathName;
fPathName = new char[strlen(deviceName) +1];
strcpy(fPathName, deviceName);
// Create a UNICODE representation of the device name. We may want
// to make the argument UNICODE instead, but most of diskimg is 8-bit
// character oriented.
size_t srcLen = strlen(deviceName) + 1;
WCHAR* wdeviceName = new WCHAR[srcLen];
size_t convertedChars;
mbstowcs_s(&convertedChars, wdeviceName, srcLen, deviceName, _TRUNCATE);
dierr = fVolAccess.Open(wdeviceName, readOnly);
delete[] wdeviceName;
if (dierr != kDIErrNone)
goto bail;
fBlockSize = fVolAccess.GetBlockSize(); // must be power of 2
assert(fBlockSize > 0);
//kTwoGBBlocks = kTwoGB / fBlockSize;
long totalBlocks;
totalBlocks = fVolAccess.GetTotalBlocks();
fVolumeEOF = (di_off_t)totalBlocks * fBlockSize;
assert(fVolumeEOF > 0);
fReadOnly = readOnly;
bail:
return dierr;
}
DIError GFDWinVolume::Read(void* buf, size_t length, size_t* pActual)
{
DIError dierr = kDIErrNone;
uint8_t* blkBuf = NULL;
//LOGI(" GFDWinVolume: reading %ld bytes from offset %ld", length,
// fCurrentOffset);
if (!fVolAccess.Ready())
return kDIErrNotReady;
// don't allow reading past the end of file
if (fCurrentOffset + (long) length > fVolumeEOF) {
if (pActual == NULL)
return kDIErrDataUnderrun;
length = (size_t) (fVolumeEOF - fCurrentOffset);
}
if (pActual != NULL)
*pActual = length;
if (length == 0)
return kDIErrNone;
long advanceLen = length;
blkBuf = new uint8_t[fBlockSize]; // get this off the heap??
long blockIndex = (long) (fCurrentOffset / fBlockSize);
int bufOffset = (int) (fCurrentOffset % fBlockSize); // req power of 2
assert(blockIndex >= 0);
/*
* When possible, do multi-block reads directly into "buf". The first
* and last block may require special handling.
*/
while (length) {
assert(length > 0);
if (bufOffset != 0 || length < (size_t) fBlockSize) {
assert(bufOffset >= 0 && bufOffset < fBlockSize);
size_t thisCount;
dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
if (dierr != kDIErrNone)
goto bail;
thisCount = fBlockSize - bufOffset;
if (thisCount > length)
thisCount = length;
//LOGI(" Copying %d bytes from block %d",
// thisCount, blockIndex);
memcpy(buf, blkBuf + bufOffset, thisCount);
length -= thisCount;
buf = (char*) buf + thisCount;
bufOffset = 0;
blockIndex++;
} else {
assert(bufOffset == 0);
long blockCount = length / fBlockSize;
assert(blockCount < 32768);
dierr = fVolAccess.ReadBlocks(blockIndex, (short) blockCount, buf);
if (dierr != kDIErrNone)
goto bail;
length -= blockCount * fBlockSize;
buf = (char*) buf + blockCount * fBlockSize;
blockIndex += blockCount;
}
}
fCurrentOffset += advanceLen;
bail:
delete[] blkBuf;
return dierr;
}
DIError GFDWinVolume::Write(const void* buf, size_t length, size_t* pActual)
{
DIError dierr = kDIErrNone;
uint8_t* blkBuf = NULL;
//LOGI(" GFDWinVolume: writing %ld bytes at offset %ld", length,
// fCurrentOffset);
if (!fVolAccess.Ready())
return kDIErrNotReady;
if (fReadOnly)
return kDIErrAccessDenied;
// don't allow writing past the end of the volume
if (fCurrentOffset + (long) length > fVolumeEOF) {
if (pActual == NULL)
return kDIErrDataOverrun;
length = (size_t) (fVolumeEOF - fCurrentOffset);
}
if (pActual != NULL)
*pActual = length;
if (length == 0)
return kDIErrNone;
long advanceLen = length;
blkBuf = new uint8_t[fBlockSize]; // get this out of the heap??
long blockIndex = (long) (fCurrentOffset / fBlockSize);
int bufOffset = (int) (fCurrentOffset % fBlockSize); // req power of 2
assert(blockIndex >= 0);
/*
* When possible, do multi-block writes directly from "buf". The first
* and last block may require special handling.
*/
while (length) {
assert(length > 0);
if (bufOffset != 0 || length < (size_t) fBlockSize) {
assert(bufOffset >= 0 && bufOffset < fBlockSize);
size_t thisCount;
dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
if (dierr != kDIErrNone)
goto bail;
thisCount = fBlockSize - bufOffset;
if (thisCount > length)
thisCount = length;
//LOGI(" Copying %d bytes into block %d (off=%d)",
// thisCount, blockIndex, bufOffset);
memcpy(blkBuf + bufOffset, buf, thisCount);
length -= thisCount;
buf = (char*) buf + thisCount;
dierr = fVolAccess.WriteBlocks(blockIndex, 1, blkBuf);
if (dierr != kDIErrNone)
goto bail;
bufOffset = 0;
blockIndex++;
} else {
assert(bufOffset == 0);
long blockCount = length / fBlockSize;
assert(blockCount < 32768);
dierr = fVolAccess.WriteBlocks(blockIndex, (short) blockCount, buf);
if (dierr != kDIErrNone)
goto bail;
length -= blockCount * fBlockSize;
buf = (char*) buf + blockCount * fBlockSize;
blockIndex += blockCount;
}
}
fCurrentOffset += advanceLen;
bail:
delete[] blkBuf;
return dierr;
}
DIError GFDWinVolume::Seek(di_off_t offset, DIWhence whence)
{
if (!fVolAccess.Ready())
return kDIErrNotReady;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset >= fVolumeEOF)
return kDIErrInvalidArg;
fCurrentOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fVolumeEOF)
return kDIErrInvalidArg;
fCurrentOffset = fVolumeEOF + offset;
break;
case kSeekCur:
if (offset < -fCurrentOffset ||
offset >= (fVolumeEOF - fCurrentOffset))
{
return kDIErrInvalidArg;
}
fCurrentOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fCurrentOffset >= 0 && fCurrentOffset <= fVolumeEOF);
return kDIErrNone;
}
di_off_t GFDWinVolume::Tell(void)
{
if (!fVolAccess.Ready())
return (di_off_t) -1;
return fCurrentOffset;
}
DIError GFDWinVolume::Close(void)
{
if (!fVolAccess.Ready())
return kDIErrNotReady;
LOGI(" GFDWinVolume closing");
fVolAccess.Close();
return kDIErrNone;
}
#endif /*_WIN32*/

328
diskimg/GenericFD.h Normal file
View File

@ -0,0 +1,328 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Declarations for GenericFD class and sub-classes.
*/
#ifndef DISKIMG_GENERICFD_H
#define DISKIMG_GENERICFD_H
#include "Win32BlockIO.h"
namespace DiskImgLib {
#if 0
/*
* Embedded file descriptor class, representing an open file on a disk image.
*
* Useful for opening disk images that are stored as files inside of other
* disk images. For stuff like UNIDOS images, which don't have a file
* associated with them, we can either open them as raw blocks, or create
* a "fake" file to access them. The latter is more general, and will work
* for sub-volumes of sub-volumes.
*/
class DISKIMG_API EmbeddedFD {
public:
EmbeddedFD(void) {
fpDiskFS = NULL;
fpA2File = NULL;
}
virtual ~EmbeddedFD(void) {}
typedef enum Fork { kForkData = 0, kForkRsrc = 1 } Fork;
// bit-flag values for Open call's "access" parameter
enum {
kAccessNone = 0, // somewhat useless
kAccessRead = 0x01, // O_RDONLY
kAccessWrite = 0x02, // O_WRONLY
kAccessCreate = 0x04, // O_CREAT
kAccessMustNotExist = 0x08, // O_EXCL, pointless w/o O_CREAT
kAccessReadWrite = (kAccessRead | kAccessWrite),
};
/*
* Standard set of calls.
*/
DIError Open(DiskFS* pDiskFS, const char* filename, Fork fork = kForkData,
int access = kAccessRead, int fileCreatePerms = 0);
DIError OpenBlocks(DiskFS* pDiskFS, long blockStart, long blockCount,
int access = kAccessRead);
DIError Read(void* buf, size_t length);
DIError Write(const void* buf, size_t length);
DIError Seek(di_off_t offset, DIWhence whence);
DIError Close(void);
private:
// prevent bitwise copying behavior
EmbeddedFD& operator=(const EmbeddedFD&);
EmbeddedFD(const EmbeddedFD&);
DiskFS* fpDiskFS;
A2File* fpA2File;
};
#endif
/*
* Generic file source base class. Allows us to treat files on disk, memory
* buffers, and files embedded inside disk images equally.
*
* The file represented by the class is available in its entirety; skipping
* past "wrapper headers" is expected to be done by the caller.
*
* The Read and Write calls take an optional parameter that allows the caller
* to see how much data was actually read or written. If the parameter is
* not specified (or specified as NULL), then failure to return the exact
* amount of data requested results an error.
*
* This is not meant to be the end-all of file wrapper classes; in
* particular, it does not support file creation.
*
* Some libraries, such as NufxLib, require an actual filename to operate
* (bad architecture?). The GetPathName call will return the original
* filename if one exists, or NULL if there isn't one. (At which point the
* caller has the option of creating a temp file, copying the data into
* it, and cranking up NufxLib or zlib on that.)
*
* NOTE to self: see fsopen() to control sharing.
*
* NOTE: the Seek() implementations currently do not consistently allow or
* disallow seeking past the current EOF of a file. When writing a file this
* can be very useful, so someday we should implement it for all classes.
*/
class GenericFD {
public:
GenericFD(void) : fReadOnly(true) {}
virtual ~GenericFD(void) {} /* = 0 */
// All sub-classes must provide these, plus a type-specific Open call.
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL) = 0;
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL) = 0;
virtual DIError Seek(di_off_t offset, DIWhence whence) = 0;
virtual di_off_t Tell(void) = 0;
virtual DIError Truncate(void) = 0;
virtual DIError Close(void) = 0;
virtual const char* GetPathName(void) const = 0;
// Flush-data call, only needed for physical devices
virtual DIError Flush(void) { return kDIErrNone; }
// Utility functions.
virtual DIError Rewind(void) { return Seek(0, kSeekSet); }
virtual bool GetReadOnly(void) const { return fReadOnly; }
/*
typedef enum {
kGFDTypeUnknown = 0,
kGFDTypeFile,
kGFDTypeBuffer,
kGFDTypeWinVolume,
kGFDTypeGFD
} GFDType;
virtual GFDType GetGFDType(void) const = 0;
*/
/*
* Utility function to copy data from one GFD to another. Both GFDs must
* be seeked to their initial positions. "length" bytes will be copied.
*/
static DIError CopyFile(GenericFD* pDst, GenericFD* pSrc, di_off_t length,
uint32_t* pCRC = NULL);
protected:
GenericFD& operator=(const GenericFD&);
GenericFD(const GenericFD&);
bool fReadOnly; // set when file is opened
};
class GFDFile : public GenericFD {
public:
#ifdef HAVE_FSEEKO
GFDFile(void) : fPathName(NULL), fFp(NULL) {}
#else
GFDFile(void) : fPathName(NULL), fFd(-1) {}
#endif
virtual ~GFDFile(void) { Close(); delete[] fPathName; }
virtual DIError Open(const char* filename, bool readOnly);
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Seek(di_off_t offset, DIWhence whence);
virtual di_off_t Tell(void);
virtual DIError Truncate(void);
virtual DIError Close(void);
virtual const char* GetPathName(void) const { return fPathName; }
private:
char* fPathName;
#ifdef HAVE_FSEEKO
FILE* fFp;
#else
int fFd;
#endif
};
#ifdef _WIN32
class GFDWinVolume : public GenericFD {
public:
GFDWinVolume(void) :
fPathName(NULL),
fCurrentOffset(0),
fVolumeEOF(-1),
fBlockSize(0)
{}
virtual ~GFDWinVolume(void) { delete[] fPathName; }
virtual DIError Open(const char* deviceName, bool readOnly);
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Seek(di_off_t offset, DIWhence whence);
virtual di_off_t Tell(void);
virtual DIError Truncate(void) { return kDIErrNotSupported; }
virtual DIError Close(void);
virtual const char* GetPathName(void) const { return fPathName; }
virtual DIError Flush(void) { return fVolAccess.FlushCache(false); }
private:
char* fPathName; // for display only
Win32VolumeAccess fVolAccess;
di_off_t fCurrentOffset;
di_off_t fVolumeEOF;
int fBlockSize; // usually 512
};
#endif
class GFDBuffer : public GenericFD {
public:
GFDBuffer(void) :
fBuffer(NULL),
fLength(0),
fAllocLength(0),
fDoDelete(false),
fDoExpand(false),
fCurrentOffset(0)
{}
virtual ~GFDBuffer(void) { Close(); }
// If "doDelete" is set, the buffer will be freed with delete[] when
// Close is called. This should ONLY be used for storage allocated
// by the DiskImg library, as under Windows it can cause problems
// because DLLs can have their own heap.
//
// "doExpand" will cause writing past the end of the buffer to
// reallocate the buffer. Again, for internally-allocated storage
// only. We expect the initial size to be close to accurate, so we
// don't aggressively expand the buffer.
virtual DIError Open(void* buffer, di_off_t length, bool doDelete,
bool doExpand, bool readOnly);
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Seek(di_off_t offset, DIWhence whence);
virtual di_off_t Tell(void);
virtual DIError Truncate(void) {
fLength = (long) Tell();
return kDIErrNone;
}
virtual DIError Close(void);
virtual const char* GetPathName(void) const { return NULL; }
// Back door; try not to use this.
void* GetBuffer(void) const { return fBuffer; }
private:
enum { kMaxReasonableSize = 256 * 1024 * 1024 };
void* fBuffer;
long fLength; // these sit in memory, so there's no
long fAllocLength; // value in using di_off_t here
bool fDoDelete;
bool fDoExpand;
di_off_t fCurrentOffset; // actually limited to (long)
};
#if 0
class GFDEmbedded : public GenericFD {
public:
GFDEmbedded(void) : fEFD(NULL) {}
virtual ~GFDEmbedded(void) { Close(); }
virtual DIError Open(EmbeddedFD* pEFD, bool readOnly);
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL);
virtual DIError Seek(di_off_t offset, DIWhence whence);
virtual di_off_t Tell(void);
virtual DIError Close(void);
virtual const char* GetPathName(void) const { return NULL; }
private:
EmbeddedFD* fEFD;
};
#endif
/* pass all requests straight through to another GFD (with offset bias) */
class GFDGFD : public GenericFD {
public:
GFDGFD(void) : fpGFD(NULL), fOffset(0) {}
virtual ~GFDGFD(void) { Close(); }
virtual DIError Open(GenericFD* pGFD, di_off_t offset, bool readOnly) {
if (pGFD == NULL)
return kDIErrInvalidArg;
if (!readOnly && pGFD->GetReadOnly())
return kDIErrAccessDenied; // can't convert to read-write
fpGFD = pGFD;
fOffset = offset;
fReadOnly = readOnly;
Seek(0, kSeekSet);
return kDIErrNone;
}
virtual DIError Read(void* buf, size_t length,
size_t* pActual = NULL)
{
return fpGFD->Read(buf, length, pActual);
}
virtual DIError Write(const void* buf, size_t length,
size_t* pActual = NULL)
{
return fpGFD->Write(buf, length, pActual);
}
virtual DIError Seek(di_off_t offset, DIWhence whence) {
return fpGFD->Seek(offset + fOffset, whence);
}
virtual di_off_t Tell(void) {
return fpGFD->Tell() -fOffset;
}
virtual DIError Truncate(void) {
return fpGFD->Truncate();
}
virtual DIError Close(void) {
/* do NOT close underlying descriptor */
fpGFD = NULL;
return kDIErrNone;
}
virtual const char* GetPathName(void) const { return fpGFD->GetPathName(); }
private:
GenericFD* fpGFD;
di_off_t fOffset;
};
}; // namespace DiskImgLib
#endif /*__GENERIC_FD__*/

191
diskimg/Global.cpp Normal file
View File

@ -0,0 +1,191 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of DiskImgLib globals.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
#include "ASPI.h"
/*static*/ bool Global::fAppInitCalled = false;
/*static*/ ASPI* Global::fpASPI = NULL;
/* global constant */
const char* DiskImgLib::kASPIDev = "ASPI:";
/*
* Perform one-time DLL initialization.
*/
/*static*/ DIError Global::AppInit(void)
{
NuError nerr;
int32_t major, minor, bug;
if (fAppInitCalled) {
LOGW("DiskImg AppInit already called");
return kDIErrNone;
}
LOGI("Initializing DiskImg library v%d.%d.%d",
kDiskImgVersionMajor, kDiskImgVersionMinor, kDiskImgVersionBug);
#ifdef _WIN32
HMODULE hModule;
WCHAR fileNameBuf[256];
hModule = ::GetModuleHandle(L"DiskImg4.dll");
if (hModule != NULL &&
::GetModuleFileName(hModule, fileNameBuf,
sizeof(fileNameBuf) / sizeof(WCHAR)) != 0)
{
// GetModuleHandle does not increase ref count, so no need to release
LOGD("DiskImg DLL loaded from '%ls'", fileNameBuf);
} else {
LOGW("Unable to get DiskImg DLL filename");
}
#endif
/*
* Make sure we're linked against a compatible version of NufxLib.
*/
nerr = NuGetVersion(&major, &minor, &bug, NULL, NULL);
if (nerr != kNuErrNone) {
LOGE("Unable to get version number from NufxLib.");
return kDIErrNufxLibInitFailed;
}
if (major != kNuVersionMajor || minor < kNuVersionMinor) {
LOGE("Unexpected NufxLib version %d.%d.%d",
major, minor, bug);
return kDIErrNufxLibInitFailed;
}
/*
* Do one-time init over in the DiskImg class.
*/
DiskImg::CalcNibbleInvTables();
#if defined(HAVE_WINDOWS_CDROM) && defined(WANT_ASPI)
if (kAlwaysTryASPI || IsWin9x()) {
fpASPI = new ASPI;
if (fpASPI->Init() != kDIErrNone) {
delete fpASPI;
fpASPI = NULL;
}
}
#endif
LOGD("DiskImg HasSPTI=%d HasASPI=%d", GetHasSPTI(), GetHasASPI());
fAppInitCalled = true;
return kDIErrNone;
}
/*
* Perform cleanup at application shutdown time.
*/
/*static*/ DIError Global::AppCleanup(void)
{
LOGI("DiskImgLib cleanup");
delete fpASPI;
return kDIErrNone;
}
/*
* Simple getters.
*
* SPTI is enabled if we're in Win2K *and* ASPI isn't loaded. If ASPI is
* loaded, it can interfere with SPTI, so we want to stick with one or
* the other.
*/
#ifdef _WIN32
/*static*/ bool Global::GetHasSPTI(void) { return !IsWin9x() && fpASPI == NULL; }
/*static*/ bool Global::GetHasASPI(void) { return fpASPI != NULL; }
/*static*/ unsigned long Global::GetASPIVersion(void) {
assert(fpASPI != NULL);
#ifdef WANT_ASPI
return fpASPI->GetVersion();
#else
return 123456789;
#endif
}
#else
/*static*/ bool Global::GetHasSPTI(void) { return false; }
/*static*/ bool Global::GetHasASPI(void) { return false; }
/*static*/ unsigned long Global::GetASPIVersion(void) { assert(false); return 0; }
#endif
/*
* Return current library versions.
*/
/*static*/ void Global::GetVersion(int32_t* pMajor, int32_t* pMinor,
int32_t* pBug)
{
if (pMajor != NULL)
*pMajor = kDiskImgVersionMajor;
if (pMinor != NULL)
*pMinor = kDiskImgVersionMinor;
if (pBug != NULL)
*pBug = kDiskImgVersionBug;
}
/*
* Pointer to debug message handler function.
*/
/*static*/ Global::DebugMsgHandler Global::gDebugMsgHandler = NULL;
/*
* Change the debug message handler. The previous handler is returned.
*/
Global::DebugMsgHandler Global::SetDebugMsgHandler(DebugMsgHandler handler)
{
DebugMsgHandler oldHandler;
oldHandler = gDebugMsgHandler;
gDebugMsgHandler = handler;
return oldHandler;
}
/*
* Send a debug message to the debug message handler.
*
* Even if _DEBUG_MSGS is disabled we can still get here from the NuFX error
* handler.
*/
/*static*/ void Global::PrintDebugMsg(const char* file, int line, const char* fmt, ...)
{
if (gDebugMsgHandler == NULL) {
/*
* This can happen if the app decides to bail with an exit()
* call. I'm not sure what's zapping the pointer.
*
* We get here on "-install" or "-uninstall", which really
* should be using a more Windows-friendly exit strategy.
*/
DebugBreak();
return;
}
char buf[512];
va_list args;
va_start(args, fmt);
#if defined(HAVE_VSNPRINTF)
(void) vsnprintf(buf, sizeof(buf), fmt, args);
#elif defined(HAVE__VSNPRINTF)
(void) _vsnprintf(buf, sizeof(buf), fmt, args);
#else
# error "hosed"
#endif
va_end(args);
buf[sizeof(buf)-1] = '\0';
(*gDebugMsgHandler)(file, line, buf);
}

690
diskimg/Gutenberg.cpp Normal file
View File

@ -0,0 +1,690 @@
/*
* CiderPress
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of Gutenberg disk format (used by the Gutenberg and
* Gutenberg Jr. word processors).
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSGutenberg
* ===========================================================================
*/
/*
The Gutenberg disk format embeds the file structure meta-data into the disk
sectors themselves, rather than having a separate track/sector list. The
first six bytes in every sector are:
+00 previous track in this file (+$80 indicates link not valid?)
+01 previous sector
+02 current track (+$80 indicates start of file)
+03 current sector
+04 next track (+$80 indicates end of file)
+05 next sector
The files are circular -- the "next" and "previous" links will wrap around --
so you have to test the high bit to see if you've reached an end.
The disk catalog works the same way, and is present as the first file on
the disk (called "DIR"). (It's not quite the same -- the "previous" pointer
in the first sector just points to the first sector.) The catalog begins
at track 17 sector 7, and skips around the disk.
The boot area is not represented by a file, and does not include the
embedded T/S links.
Each directory entry is 16 bytes, and lays out rather nicely in the sector
editor:
00: 11 07 11 87 15 0e c7 c2 af cd c1 d3 d4 c5 d2 8d ......GB/MASTER.
10: c4 c9 d2 a0 a0 a0 a0 a0 a0 a0 a0 a0 11 07 cc 8d DIR ..L.
20: c3 cf d0 d9 a0 a0 a0 a0 a0 a0 a0 a0 10 40 d0 8d COPY .@P.
30: c3 cf d0 d9 c1 cc cc a0 a0 a0 a0 a0 10 04 d0 8d COPYALL ..P.
This shows the six T/S bytes, followed by a nine-character volume name.
The fact that each entry ends in 0x8d is likely a deliberate attempt to
make the file readable as high-ASCII text, with one entry per line.
The regular directory entries start at 0x10. The file name is 12 bytes,
followed by the track and sector of the start of the file. Note "DIR"
starts at track 17 sector 7, as expected. The next entry, COPY, has 0x40 for
its sector number, indicating that it has been deleted. (Some entries on
Gutenberg Jr. disks use 0x40 for the track number instead?)
The next value is one of 'L', 'P', 'M', or ' ' (0xcc, 0xd0, 0xcd, 0xa0).
Some files have text, some have fonts, some have executable code (a short
header followed by 6502 instructions). There's no apparent link between
the value and the type of data in the file.
*/
const int kMaxSectors = 32;
const int kMaxVolNameLen = 9;
const int kSctSize = 256;
const int kVTOCTrack = 17;
const int kVTOCSector = 7;
const int kCatalogEntryOffset = 0x10; // first entry in cat sect starts here
const int kCatalogEntrySize = 16; // length in bytes of catalog entries
const int kCatalogEntriesPerSect = 15; // #of entries per catalog sector
const int kEntryDeleted = 0x40; // this is used to designate deleted files
const int kEntryUnused = 0x00; // this is track# in never-used entries
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
const int kMaxTSIterations = 32;
/*
* Get a pointer to the Nth entry in a catalog sector.
*/
static inline uint8_t* GetCatalogEntryPtr(uint8_t* basePtr, int entryNum)
{
assert(entryNum >= 0 && entryNum < kCatalogEntriesPerSect);
return basePtr + kCatalogEntryOffset + entryNum * kCatalogEntrySize;
}
/*
* Test this image for Gutenberg-ness.
*
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
int* pGoodCount)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
int catTrack = kVTOCTrack;
int catSect = kVTOCSector;
int foundGood = 0;
int iterations = 0;
*pGoodCount = 0;
/*
* Walk through the catalog track to try to figure out ordering.
*/
while (iterations < DiskFSGutenberg::kMaxCatalogSectors)
{
dierr = pImg->ReadTrackSectorSwapped(catTrack, catSect, sctBuf,
imageOrder, DiskImg::kSectorOrderDOS);
if (dierr != kDIErrNone) {
dierr = kDIErrNone;
break; /* allow it if earlier stuff was okay */
}
if (catTrack == (sctBuf[2] & 0x7f) && catSect == (sctBuf[3] & 0x7f)) {
// current-sector values matched, check for the end-of-entry bits
foundGood++;
if (sctBuf[0x0f] == 0x8d && sctBuf[0x1f] == 0x8d &&
sctBuf[0x2f] == 0x8d && sctBuf[0x3f] == 0x8d &&
sctBuf[0x4f] == 0x8d && sctBuf[0x5f] == 0x8d &&
sctBuf[0x6f] == 0x8d && sctBuf[0x7f] == 0x8d &&
sctBuf[0x8f] == 0x8d && sctBuf[0x9f] == 0x8d)
{
foundGood++;
}
}
catTrack = sctBuf[0x04];
catSect = sctBuf[0x05];
if ((catTrack & 0x80) != 0) {
// full circle
break;
}
iterations++; // watch for infinite loops
}
if (iterations >= DiskFSGutenberg::kMaxCatalogSectors) {
/* possible cause: LF->CR conversion screws up link to sector $0a */
dierr = kDIErrDirectoryLoop;
LOGI(" Gutenberg directory links cause a loop (order=%d)", imageOrder);
goto bail;
}
LOGI(" Gutenberg foundGood=%d order=%d", foundGood, imageOrder);
*pGoodCount = foundGood;
bail:
return dierr;
}
/*
* Test to see if the image is a Gutenberg word processor data disk.
*/
/*static*/ DIError DiskFSGutenberg::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumTracks() > kMaxInterestingTracks)
return kDIErrFilesystemNotFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
int bestCount = 0;
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
int goodCount = 0;
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
if (goodCount > bestCount) {
bestCount = goodCount;
bestOrder = ordering[i];
}
}
}
if (bestCount >= 2 ||
(leniency == kLeniencyVery && bestCount >= 1))
{
LOGI(" Gutenberg test: bestCount=%d for order=%d", bestCount, bestOrder);
assert(bestOrder != DiskImg::kSectorOrderUnknown);
*pOrder = bestOrder;
*pFormat = DiskImg::kFormatGutenberg;
return kDIErrNone;
}
LOGI(" Gutenberg didn't find a valid filesystem.");
return kDIErrFilesystemNotFound;
}
/*
* Get things rolling.
*
* Since we're assured that this is a valid disk, errors encountered from here
* on out must be handled somehow, possibly by claiming that the disk is
* completely full and has no files on it.
*/
DIError DiskFSGutenberg::Initialize(InitMode initMode)
{
DIError dierr = kDIErrNone;
fVolumeUsage.Create(fpImg->GetNumTracks(), fpImg->GetNumSectPerTrack());
/* read the contents of the catalog, creating our A2File list */
dierr = ReadCatalog();
if (dierr != kDIErrNone)
goto bail;
/* run through and get file lengths and data offsets */
dierr = GetFileLengths();
if (dierr != kDIErrNone)
goto bail;
sprintf(fDiskVolumeID, "Gutenberg: %s", fDiskVolumeName);
fDiskIsGood = CheckDiskIsGood();
fVolumeUsage.Dump();
bail:
return dierr;
}
/*
* Get the amount of free space remaining.
*/
DIError DiskFSGutenberg::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
int* pUnitSize) const
{
*pTotalUnits = fpImg->GetNumTracks() * fpImg->GetNumSectPerTrack();
*pFreeUnits = 0;
*pUnitSize = kSectorSize;
return kDIErrNone;
}
/*
* Read the disk's catalog.
*/
DIError DiskFSGutenberg::ReadCatalog(void)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
int catTrack, catSect;
int iterations;
catTrack = kVTOCTrack;
catSect = kVTOCSector;
iterations = 0;
memset(fCatalogSectors, 0, sizeof(fCatalogSectors));
while (catTrack < 35 && catSect < 16 && iterations < kMaxCatalogSectors)
{
LOGD(" Gutenberg reading catalog sector T=%d S=%d", catTrack, catSect);
dierr = fpImg->ReadTrackSector(catTrack, catSect, sctBuf);
if (dierr != kDIErrNone)
goto bail;
memcpy(fDiskVolumeName, &sctBuf[6], kMaxVolNameLen); // Copy out the volume name; it should be the same on all catalog sectors.
fDiskVolumeName[kMaxVolNameLen] = 0x00;
DiskFSGutenberg::LowerASCII((uint8_t*)fDiskVolumeName, kMaxVolNameLen);
A2FileGutenberg::TrimTrailingSpaces(fDiskVolumeName);
dierr = ProcessCatalogSector(catTrack, catSect, sctBuf);
if (dierr != kDIErrNone)
goto bail;
fCatalogSectors[iterations].track = catTrack;
fCatalogSectors[iterations].sector = catSect;
catTrack = sctBuf[0x04];
catSect = sctBuf[0x05];
iterations++; // watch for infinite loops
}
if (iterations >= kMaxCatalogSectors) {
dierr = kDIErrDirectoryLoop;
goto bail;
}
bail:
return dierr;
}
/*
* Process the list of files in one sector of the catalog.
*
* Pass in the track, sector, and the contents of that track and sector.
* (We only use "catTrack" and "catSect" to fill out some fields.)
*/
DIError DiskFSGutenberg::ProcessCatalogSector(int catTrack, int catSect,
const uint8_t* sctBuf)
{
A2FileGutenberg* pFile;
const uint8_t* pEntry;
int i;
pEntry = &sctBuf[kCatalogEntryOffset];
for (i = 0; i < kCatalogEntriesPerSect; i++) {
if (pEntry[0x0c] != kEntryDeleted && pEntry[0x0d] != kEntryDeleted &&
pEntry[0x00] != 0xa0 && pEntry[0x00] != 0x00)
{
pFile = new A2FileGutenberg(this);
pFile->SetQuality(A2File::kQualityGood);
pFile->fTrack = pEntry[0x0c];
pFile->fSector = pEntry[0x0d];
memcpy(pFile->fFileName, &pEntry[0x00], A2FileGutenberg::kMaxFileName);
pFile->fFileName[A2FileGutenberg::kMaxFileName] = '\0';
pFile->FixFilename();
//pFile->fCatTS.track = catTrack;
//pFile->fCatTS.sector = catSect;
pFile->fCatEntryNum = i;
/* can't do these yet, so just set to defaults */
pFile->fLength = 0;
pFile->fSparseLength = 0;
pFile->fDataOffset = 0;
pFile->fLengthInSectors = 0;
pFile->fLengthInSectors = 0;
AddFileToList(pFile);
}
//if (pEntry[0x00] == 0xa0)
// break;
pEntry += kCatalogEntrySize;
}
return kDIErrNone;
}
/*
* Perform consistency checks on the filesystem.
*
* Returns "true" if disk appears to be perfect, "false" otherwise.
*/
bool DiskFSGutenberg::CheckDiskIsGood(void)
{
bool result = true;
return result;
}
/*
* Run through our list of files, computing the lengths and marking file
* usage in the VolumeUsage object.
*/
DIError DiskFSGutenberg::GetFileLengths(void)
{
A2FileGutenberg* pFile;
uint8_t sctBuf[kSctSize];
int tsCount = 0;
uint16_t currentTrack, currentSector;
pFile = (A2FileGutenberg*) GetNextFile(NULL);
while (pFile != NULL) {
DIError dierr;
tsCount = 0;
currentTrack = pFile->fTrack;
currentSector = pFile->fSector;
while (currentTrack < 0x80) {
tsCount ++;
dierr = fpImg->ReadTrackSector(currentTrack, currentSector, sctBuf);
if (dierr != kDIErrNone) {
LOGI("Gutenberg failed loading track/sector for '%s'",
pFile->GetPathName());
goto bail;
}
currentTrack = sctBuf[0x04];
currentSector = sctBuf[0x05];
}
pFile->fLengthInSectors = tsCount;
pFile->fLength = tsCount * 250; // First six bytes of sector are t/s pointers
pFile = (A2FileGutenberg*) GetNextFile(pFile);
}
bail:
return kDIErrNone;
}
/*
* Convert high ASCII to low ASCII.
*
* Some people put inverse and flashing text into filenames, not to mention
* control characters, so we have to cope with those too.
*
* We modify the first "len" bytes of "buf" in place.
*/
/*static*/ void DiskFSGutenberg::LowerASCII(uint8_t* buf, long len)
{
while (len--) {
if (*buf & 0x80) {
if (*buf >= 0xa0)
*buf &= 0x7f;
else
*buf = (*buf & 0x7f) + 0x20;
} else
*buf = ((*buf & 0x3f) ^ 0x20) + 0x20;
buf++;
}
}
/*
* ===========================================================================
* A2FileGutenberg
* ===========================================================================
*/
/*
* Constructor.
*/
A2FileGutenberg::A2FileGutenberg(DiskFS* pDiskFS) : A2File(pDiskFS)
{
fTrack = -1;
fSector = -1;
fLengthInSectors = 0;
fLocked = true;
fFileName[0] = '\0';
fFileType = kTypeText;
fCatTS.track = fCatTS.sector = 0;
fCatEntryNum = -1;
fAuxType = 0;
fDataOffset = 0;
fLength = -1;
fSparseLength = -1;
fpOpenFile = NULL;
}
/*
* Destructor. Make sure an "open" file gets "closed".
*/
A2FileGutenberg::~A2FileGutenberg(void)
{
delete fpOpenFile;
}
/*
* Convert the filetype enum to a ProDOS type.
*
*/
uint32_t A2FileGutenberg::GetFileType(void) const
{
return 0x04; // TXT;
}
/*
* "Fix" a filename. Convert DOS-ASCII to normal ASCII, and strip
* trailing spaces.
*/
void A2FileGutenberg::FixFilename(void)
{
DiskFSGutenberg::LowerASCII((uint8_t*)fFileName, kMaxFileName);
TrimTrailingSpaces(fFileName);
}
/*
* Trim the spaces off the end of a filename.
*
* Assumes the filename has already been converted to low ASCII.
*/
/*static*/ void A2FileGutenberg::TrimTrailingSpaces(char* filename)
{
char* lastspc = filename + strlen(filename);
assert(*lastspc == '\0');
while (--lastspc) {
if (*lastspc != ' ')
break;
}
*(lastspc+1) = '\0';
}
/*
* Encode a filename into high ASCII, padded out with spaces to
* kMaxFileName chars. Lower case is converted to upper case. This
* does not filter out control characters or other chunk.
*
* "buf" must be able to hold kMaxFileName+1 chars.
*/
/*static*/ void A2FileGutenberg::MakeDOSName(char* buf, const char* name)
{
for (int i = 0; i < kMaxFileName; i++) {
if (*name == '\0')
*buf++ = (char) 0xa0;
else
*buf++ = toupper(*name++) | 0x80;
}
*buf = '\0';
}
/*
* Set up state for this file.
*/
DIError A2FileGutenberg::Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork /*=false*/)
{
DIError dierr = kDIErrNone;
A2FDGutenberg* pOpenFile = NULL;
if (!readOnly) {
if (fpDiskFS->GetDiskImg()->GetReadOnly())
return kDIErrAccessDenied;
if (fpDiskFS->GetFSDamaged())
return kDIErrBadDiskImage;
}
if (fpOpenFile != NULL) {
dierr = kDIErrAlreadyOpen;
goto bail;
}
if (rsrcFork)
return kDIErrForkNotFound;
pOpenFile = new A2FDGutenberg(this);
pOpenFile->fOffset = 0;
pOpenFile->fOpenEOF = fLength;
pOpenFile->fOpenSectorsUsed = fLengthInSectors;
fpOpenFile = pOpenFile; // add it to our single-member "open file set"
*ppOpenFile = pOpenFile;
pOpenFile = NULL;
bail:
delete pOpenFile;
return dierr;
}
/*
* Dump the contents of an A2FileGutenberg.
*/
void A2FileGutenberg::Dump(void) const
{
LOGI("A2FileGutenberg '%s'", fFileName);
LOGI(" TS T=%-2d S=%-2d", fTrack, fSector);
LOGI(" Cat T=%-2d S=%-2d", fCatTS.track, fCatTS.sector);
LOGI(" type=%d lck=%d slen=%d", fFileType, fLocked, fLengthInSectors);
LOGI(" auxtype=0x%04x length=%ld",
fAuxType, (long) fLength);
}
/*
* ===========================================================================
* A2FDGutenberg
* ===========================================================================
*/
/*
* Read data from the current offset.
*
*/
DIError A2FDGutenberg::Read(void* buf, size_t len, size_t* pActual)
{
LOGD(" Gutenberg reading %lu bytes from '%s' (offset=%ld)",
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
A2FileGutenberg* pFile = (A2FileGutenberg*) fpFile;
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
short currentTrack, currentSector;
//di_off_t actualOffset = fOffset + pFile->fDataOffset; // adjust for embedded len
int bufOffset = 6;
size_t thisCount;
if (len == 0)
return kDIErrNone;
assert(fOpenEOF != 0);
currentTrack = pFile->fTrack;
currentSector = pFile->fSector;
/* could be more clever in here and avoid double-buffering */
while (len) {
dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(
currentTrack,
currentSector,
sctBuf);
if (dierr != kDIErrNone) {
LOGI(" Gutenberg error reading file '%s'", pFile->GetPathName());
return dierr;
}
thisCount = kSctSize - bufOffset;
if (thisCount > len)
thisCount = len;
memcpy(buf, sctBuf + bufOffset, thisCount);
len -= thisCount;
buf = (char*)buf + thisCount;
currentTrack = sctBuf[0x04];
currentSector = sctBuf[0x05];
}
return dierr;
}
/*
* Writing Gutenberg files isn't supported.
*/
DIError A2FDGutenberg::Write(const void* buf, size_t len, size_t* pActual)
{
return kDIErrNotSupported;
}
/*
* Seek to the specified offset.
*/
DIError A2FDGutenberg::Seek(di_off_t offset, DIWhence whence)
{
return kDIErrNotSupported;
}
/*
* Return current offset.
*/
di_off_t A2FDGutenberg::Tell(void)
{
return kDIErrNotSupported;
}
/*
* Release file state.
*
* If the file was modified, we need to update the sector usage count in
* the catalog track, and possibly a length word in the first sector of
* the file (for A/I/B).
*
* Given the current "write all at once" implementation of Write, we could
* have handled the length word back when initially writing the data, but
* someday we may fix that and I don't want to have to rewrite this part.
*
* Most applications don't check the value of "Close", or call it from a
* destructor, so we call CloseDescr whether we succeed or not.
*/
DIError A2FDGutenberg::Close(void)
{
DIError dierr = kDIErrNone;
fpFile->CloseDescr(this);
return dierr;
}
/*
* Return the #of sectors/blocks in the file.
*/
long A2FDGutenberg::GetSectorCount(void) const
{
return fTSCount;
}
long A2FDGutenberg::GetBlockCount(void) const
{
return (fTSCount+1)/2;
}
/*
* Return the Nth track/sector in this file.
*
* Returns (0,0) for a sparse sector.
*/
DIError A2FDGutenberg::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
{
return kDIErrInvalidIndex;
}
/*
* Unimplemented
*/
DIError A2FDGutenberg::GetStorage(long blockIdx, long* pBlock) const
{
return kDIErrInvalidIndex;
}

2298
diskimg/HFS.cpp Normal file

File diff suppressed because it is too large Load Diff

2476
diskimg/ImageWrapper.cpp Normal file

File diff suppressed because it is too large Load Diff

470
diskimg/MacPart.cpp Normal file
View File

@ -0,0 +1,470 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* The "MacPart" DiskFS is a container class for multiple ProDOS and HFS
* volumes. It represents a partitioned disk device, such as a hard
* drive or CD-ROM.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
const int kBlkSize = 512;
const int kDDRBlock = 0; // Driver Descriptor Record block
const int kPartMapStart = 1; // start of partition map
/*
* Format of DDR (block 0).
*/
typedef struct DiskFSMacPart::DriverDescriptorRecord {
uint16_t sbSig; // {device signature}
uint16_t sbBlkSize; // {block size of the device}
uint32_t sbBlkCount; // {number of blocks on the device}
uint16_t sbDevType; // {reserved}
uint16_t sbDevId; // {reserved}
uint32_t sbData; // {reserved}
uint16_t sbDrvrCount; // {number of driver descriptor entries}
uint16_t hiddenPad; // implicit in specification
uint32_t ddBlock; // {first driver's starting block}
uint16_t ddSize; // {size of the driver, in 512-byte blocks}
uint16_t ddType; // {operating system type (MacOS = 1)}
uint16_t ddPad[242]; // {additional drivers, if any}
} DriverDescriptorRecord;
/*
* Format of partition map blocks. The partition map is an array of these.
*/
typedef struct DiskFSMacPart::PartitionMap {
uint16_t pmSig; // {partition signature}
uint16_t pmSigPad; // {reserved}
uint32_t pmMapBlkCnt; // {number of blocks in partition map}
uint32_t pmPyPartStart; // {first physical block of partition}
uint32_t pmPartBlkCnt; // {number of blocks in partition}
uint8_t pmPartName[32]; // {partition name}
uint8_t pmParType[32]; // {partition type}
uint32_t pmLgDataStart; // {first logical block of data area}
uint32_t pmDataCnt; // {number of blocks in data area}
uint32_t pmPartStatus; // {partition status information}
uint32_t pmLgBootStart; // {first logical block of boot code}
uint32_t pmBootSize; // {size of boot code, in bytes}
uint32_t pmBootAddr; // {boot code load address}
uint32_t pmBootAddr2; // {reserved}
uint32_t pmBootEntry; // {boot code entry point}
uint32_t pmBootEntry2; // {reserved}
uint32_t pmBootCksum; // {boot code checksum}
uint8_t pmProcessor[16]; // {processor type}
uint16_t pmPad[188]; // {reserved}
} PartitionMap;
/*
* Figure out if this is a Macintosh-style partition.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*
* It would be difficult to guess the block order based on the partition
* structure, because the partition map entries can appear in any order.
*/
/*static*/ DIError DiskFSMacPart::TestImage(DiskImg* pImg,
DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t blkBuf[kBlkSize];
DriverDescriptorRecord ddr;
long pmMapBlkCnt;
assert(sizeof(PartitionMap) == kBlkSize);
assert(sizeof(DriverDescriptorRecord) == kBlkSize);
/* check the DDR block */
dierr = pImg->ReadBlockSwapped(kDDRBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
UnpackDDR(blkBuf, &ddr);
if (ddr.sbSig != kDDRSignature) {
dierr = kDIErrFilesystemNotFound;
goto bail;
}
if (ddr.sbBlkSize != kBlkSize || ddr.sbBlkCount == 0) {
if (ddr.sbBlkSize == 0 && ddr.sbBlkCount == 0) {
/*
* This is invalid, but it's the way floptical images formatted
* by the C.V.Tech format utilities look.
*/
LOGI(" MacPart NOTE: found zeroed-out DDR, continuing anyway");
} else if (ddr.sbBlkSize == kBlkSize && ddr.sbBlkCount == 0) {
/*
* This showed up on a disc, so handle it too.
*/
LOGI(" MacPart NOTE: found partially-zeroed-out DDR, continuing");
} else {
LOGI(" MacPart found 'ER' signature but blkSize=%d blkCount=%d",
ddr.sbBlkSize, ddr.sbBlkCount);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
}
DumpDDR(&ddr);
/* make sure block 1 is a partition */
dierr = pImg->ReadBlockSwapped(kPartMapStart, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
if (GetShortBE(&blkBuf[0x00]) != kPartitionSignature) {
LOGI(" MacPart partition signature not found in first part block");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
pmMapBlkCnt = GetLongBE(&blkBuf[0x04]);
if (pmMapBlkCnt <= 0 || pmMapBlkCnt > 256) {
LOGI(" MacPart unreasonable pmMapBlkCnt value %ld",
pmMapBlkCnt);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/* could test the rest -- might fix "imageOrder", might not -- but
the format is pretty unambiguous, and we don't care about the order */
// success!
LOGI(" MacPart partition map block count = %ld", pmMapBlkCnt);
bail:
return dierr;
}
/*
* Unpack a DDR disk block into a DDR data structure.
*/
/*static*/ void DiskFSMacPart::UnpackDDR(const uint8_t* buf,
DriverDescriptorRecord* pDDR)
{
pDDR->sbSig = GetShortBE(&buf[0x00]);
pDDR->sbBlkSize = GetShortBE(&buf[0x02]);
pDDR->sbBlkCount = GetLongBE(&buf[0x04]);
pDDR->sbDevType = GetShortBE(&buf[0x08]);
pDDR->sbDevId = GetShortBE(&buf[0x0a]);
pDDR->sbData = GetLongBE(&buf[0x0c]);
pDDR->sbDrvrCount = GetShortBE(&buf[0x10]);
pDDR->hiddenPad = GetShortBE(&buf[0x12]);
pDDR->ddBlock = GetLongBE(&buf[0x14]);
pDDR->ddSize = GetShortBE(&buf[0x18]);
pDDR->ddType = GetShortBE(&buf[0x1a]);
int i;
for (i = 0; i < (int) NELEM(pDDR->ddPad); i++) {
pDDR->ddPad[i] = GetShortBE(&buf[0x1c] + i * sizeof(pDDR->ddPad[0]));
}
assert(0x1c + i * sizeof(pDDR->ddPad[0]) == (unsigned int) kBlkSize);
}
/*
* Debug: dump the contents of the DDR.
*/
/*static*/ void DiskFSMacPart::DumpDDR(const DriverDescriptorRecord* pDDR)
{
LOGI(" MacPart driver descriptor record");
LOGI(" sbSig=0x%04x sbBlkSize=%d sbBlkCount=%d",
pDDR->sbSig, pDDR->sbBlkSize, pDDR->sbBlkCount);
LOGI(" sbDevType=%d sbDevId=%d sbData=%d sbDrvrCount=%d",
pDDR->sbDevType, pDDR->sbDevId, pDDR->sbData, pDDR->sbDrvrCount);
LOGI(" (pad=%d) ddBlock=%d ddSize=%d ddType=%d",
pDDR->hiddenPad, pDDR->ddBlock, pDDR->ddSize, pDDR->ddType);
}
/*
* Unpack a partition map disk block into a partition map data structure.
*/
/*static*/ void DiskFSMacPart::UnpackPartitionMap(const uint8_t* buf,
PartitionMap* pMap)
{
pMap->pmSig = GetShortBE(&buf[0x00]);
pMap->pmSigPad = GetShortBE(&buf[0x02]);
pMap->pmMapBlkCnt = GetLongBE(&buf[0x04]);
pMap->pmPyPartStart = GetLongBE(&buf[0x08]);
pMap->pmPartBlkCnt = GetLongBE(&buf[0x0c]);
memcpy(pMap->pmPartName, &buf[0x10], sizeof(pMap->pmPartName));
pMap->pmPartName[sizeof(pMap->pmPartName)-1] = '\0';
memcpy(pMap->pmParType, &buf[0x30], sizeof(pMap->pmParType));
pMap->pmParType[sizeof(pMap->pmParType)-1] = '\0';
pMap->pmLgDataStart = GetLongBE(&buf[0x50]);
pMap->pmDataCnt = GetLongBE(&buf[0x54]);
pMap->pmPartStatus = GetLongBE(&buf[0x58]);
pMap->pmLgBootStart = GetLongBE(&buf[0x5c]);
pMap->pmBootSize = GetLongBE(&buf[0x60]);
pMap->pmBootAddr = GetLongBE(&buf[0x64]);
pMap->pmBootAddr2 = GetLongBE(&buf[0x68]);
pMap->pmBootEntry = GetLongBE(&buf[0x6c]);
pMap->pmBootEntry2 = GetLongBE(&buf[0x70]);
pMap->pmBootCksum = GetLongBE(&buf[0x74]);
memcpy((char*) pMap->pmProcessor, &buf[0x78], sizeof(pMap->pmProcessor));
pMap->pmProcessor[sizeof(pMap->pmProcessor)-1] = '\0';
int i;
for (i = 0; i < (int) NELEM(pMap->pmPad); i++) {
pMap->pmPad[i] = GetShortBE(&buf[0x88] + i * sizeof(pMap->pmPad[0]));
}
assert(0x88 + i * sizeof(pMap->pmPad[0]) == (unsigned int) kBlkSize);
}
/*
* Debug: dump the contents of the partition map.
*/
/*static*/ void DiskFSMacPart::DumpPartitionMap(long block, const PartitionMap* pMap)
{
LOGI(" MacPart partition map: block=%ld", block);
LOGI(" pmSig=0x%04x (pad=0x%04x) pmMapBlkCnt=%d",
pMap->pmSig, pMap->pmSigPad, pMap->pmMapBlkCnt);
LOGI(" pmPartName='%s' pmParType='%s'",
pMap->pmPartName, pMap->pmParType);
LOGI(" pmPyPartStart=%d pmPartBlkCnt=%d",
pMap->pmPyPartStart, pMap->pmPartBlkCnt);
LOGI(" pmLgDataStart=%d pmDataCnt=%d",
pMap->pmLgDataStart, pMap->pmDataCnt);
LOGI(" pmPartStatus=%d",
pMap->pmPartStatus);
LOGI(" pmLgBootStart=%d pmBootSize=%d",
pMap->pmLgBootStart, pMap->pmBootSize);
LOGI(" pmBootAddr=%d pmBootAddr2=%d pmBootEntry=%d pmBootEntry2=%d",
pMap->pmBootAddr, pMap->pmBootAddr2,
pMap->pmBootEntry, pMap->pmBootEntry2);
LOGI(" pmBootCksum=%d pmProcessor='%s'",
pMap->pmBootCksum, pMap->pmProcessor);
}
/*
* Open up a sub-volume.
*/
DIError DiskFSMacPart::OpenSubVolume(const PartitionMap* pMap)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
long startBlock, numBlocks;
bool tweaked = false;
assert(pMap != NULL);
startBlock = pMap->pmPyPartStart;
numBlocks = pMap->pmPartBlkCnt;
LOGI("Adding '%s' (%s) %ld +%ld",
pMap->pmPartName, pMap->pmParType, startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
LOGI("MacPart start block out of range (%ld vs %ld)",
startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
LOGI("MacPart partition too large (%ld vs %ld avail)",
numBlocks, fpImg->GetNumBlocks() - startBlock);
fpImg->AddNote(DiskImg::kNoteInfo,
"Reduced partition '%s' (%s) from %ld blocks to %ld.\n",
pMap->pmPartName, pMap->pmParType, numBlocks,
fpImg->GetNumBlocks() - startBlock);
numBlocks = fpImg->GetNumBlocks() - startBlock;
tweaked = true;
}
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
/*
* If "tweaked" is true, we want to make the volume read-only, so that the
* volume copier doesn't stomp on it (on the off chance we've got it
* wrong). However, that won't stop the volume copier from stomping on
* the entire thing, so we really need to change *all* members of the
* diskimg tree to be read-only. This seems counter-productive though.
*
* So far the only actual occurrence of tweakedness was from the first
* Apple "develop" CD-ROM, which had a bad Apple_Extra partition on the
* end.
*/
(void) tweaked;
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" MacPartSub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
/* the partition is typed; currently no way to give hints to analyzer */
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" MacPartSub: analysis failed (err=%d)", dierr);
goto bail;
}
/* we allow unrecognized partitions */
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" MacPartSub (%ld,%ld): unable to identify filesystem",
startBlock, numBlocks);
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
if (pUnknownFS == NULL) {
dierr = kDIErrInternal;
goto bail;
}
pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
pNewFS = pUnknownFS;
pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s' type='%s'.",
pMap->pmPartName, pMap->pmParType);
} else {
/* open a DiskFS for the sub-image */
LOGI(" MacPartSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == NULL) {
LOGI(" MacPartSub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
}
/* we encapsulate arbitrary stuff, so encourage child to scan */
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
/*
* Load the files from the sub-image. When doing our initial tests,
* or when loading data for the volume copier, we don't want to dig
* into our sub-volumes, just figure out what they are and where.
*
* If "initialize" fails, the sub-volume won't get added to the list.
* It's important that a failure at this stage doesn't cause the whole
* thing to fall over.
*/
InitMode initMode;
if (GetScanForSubVolumes() == kScanSubContainerOnly)
initMode = kInitHeaderOnly;
else
initMode = kInitFull;
dierr = pNewFS->Initialize(pNewImg, initMode);
if (dierr != kDIErrNone) {
LOGI(" MacPartSub: error %d reading list of files from disk", dierr);
goto bail;
}
/* add it to the list */
AddSubVolumeToList(pNewImg, pNewFS);
pNewImg = NULL;
pNewFS = NULL;
bail:
delete pNewFS;
delete pNewImg;
return dierr;
}
/*
* Check to see if this is a MacPart volume.
*/
/*static*/ DIError DiskFSMacPart::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for partitions inside
return kDIErrFilesystemNotFound;
/* assume ProDOS -- shouldn't matter, since it's embedded */
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
*pFormat = DiskImg::kFormatMacPart;
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
LOGI(" FS didn't find valid MacPart");
return kDIErrFilesystemNotFound;
}
/*
* Prep the MacPart "container" for use.
*/
DIError DiskFSMacPart::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("MacPart initializing (scanForSub=%d)", fScanForSubVolumes);
/* seems pointless *not* to, but we just do what we're told */
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = FindSubVolumes();
if (dierr != kDIErrNone)
return dierr;
}
/* blank out the volume usage map */
SetVolumeUsageMap();
return dierr;
}
/*
* Find the various sub-volumes and open them.
*
* Because the partitions are explicitly typed, we don't need to probe
* their contents. But we do anyway.
*/
DIError DiskFSMacPart::FindSubVolumes(void)
{
DIError dierr = kDIErrNone;
uint8_t buf[kBlkSize];
PartitionMap map;
int i, numMapBlocks;
dierr = fpImg->ReadBlock(kPartMapStart, buf);
if (dierr != kDIErrNone)
goto bail;
UnpackPartitionMap(buf, &map);
numMapBlocks = map.pmMapBlkCnt;
for (i = 0; i < numMapBlocks; i++) {
if (i != 0) {
dierr = fpImg->ReadBlock(kPartMapStart+i, buf);
if (dierr != kDIErrNone)
goto bail;
UnpackPartitionMap(buf, &map);
}
DumpPartitionMap(kPartMapStart+i, &map);
dierr = OpenSubVolume(&map);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
LOGI(" MacPart failed opening sub-volume %d", i);
dierr = CreatePlaceholder(map.pmPyPartStart, map.pmPartBlkCnt,
(const char*)map.pmPartName, (const char*)map.pmParType,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGI(" MacPart unable to create placeholder (err=%d)",
dierr);
break; // something's wrong -- bail out with error
}
}
}
bail:
return dierr;
}

49
diskimg/Makefile Normal file
View File

@ -0,0 +1,49 @@
#
# DiskImg makefile for Linux.
#
SHELL = /bin/sh
CC = gcc
CXX = g++
AR = ar
OPT = -g -D_DEBUG
#-DEXCISE_GPL_CODE
#OPT = -g -O2
GCC_FLAGS = -Wall -Wwrite-strings -Wpointer-arith -Wshadow
# -Wstrict-prototypes
CXXFLAGS = $(OPT) $(GCC_FLAGS) -D_FILE_OFFSET_BITS=64
SRCS = ASPI.cpp CFFA.cpp Container.cpp CPM.cpp DDD.cpp DiskFS.cpp \
DiskImg.cpp DIUtil.cpp DOS33.cpp DOSImage.cpp FAT.cpp FDI.cpp \
FocusDrive.cpp \GenericFD.cpp Global.cpp Gutenberg.cpp HFS.cpp \
ImageWrapper.cpp MacPart.cpp MicroDrive.cpp Nibble.cpp \
Nibble35.cpp OuterWrapper.cpp OzDOS.cpp Pascal.cpp ProDOS.cpp \
RDOS.cpp TwoImg.cpp UNIDOS.cpp VolumeUsage.cpp Win32BlockIO.cpp
OBJS = ASPI.o CFFA.o Container.o CPM.o DDD.o DiskFS.o \
DiskImg.o DIUtil.o DOS33.o DOSImage.o FDI.o \
FocusDrive.o FAT.o GenericFD.o Global.o Gutenberg.o HFS.o \
ImageWrapper.o MacPart.o MicroDrive.o Nibble.o \
Nibble35.o OuterWrapper.o OzDOS.o Pascal.o ProDOS.o \
RDOS.o TwoImg.o UNIDOS.o VolumeUsage.o Win32BlockIO.o
STATIC_PRODUCT = libdiskimg.a
PRODUCT = $(STATIC_PRODUCT)
all: $(PRODUCT)
@true
$(STATIC_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT)
$(AR) rcv $@ $(OBJS)
clean:
-rm -f *.o core
-rm -f $(STATIC_PRODUCT)
-rm -f Makefile.bak
tags::
@ctags -R --totals *
depend:
makedepend -- $(CFLAGS) -- $(SRCS)
# DO NOT DELETE THIS LINE -- make depend depends on it.

397
diskimg/MicroDrive.cpp Normal file
View File

@ -0,0 +1,397 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* The "MicroDrive" DiskFS is a container class for multiple ProDOS and HFS
* volumes. It represents a partitioned disk device, such as a hard
* drive or CF card, that has been formatted for use with ///SHH Systeme's
* MicroDrive card for the Apple II.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
const int kBlkSize = 512;
const int kPartMapBlock = 0; // partition map lives here
const uint32_t kPartSizeMask = 0x00ffffff;
/*
* Format of partition map. It resides in the first 256 bytes of block 0.
* All values are in little-endian order.
*
* The layout was discovered through reverse-engineering. Additional notes:
*
From Joachim Lange:
Below, this is the configuration block as it is used in all
MicroDrive cards. Please verify that my ID shortcut can be
found at offset 0, otherwise the partition info is not
valid. Most of the other parms are not useful, some are
historic and not useful anymore. As a second security
measure, verify that the first partition starts at
absolute block 256. This is also a fixed value used in all
MicroDrive cards. Of course the partition size is not two
bytes long but three (not four), the 4th byte is used for
switching drives in a two-drive configuration. So, for
completeness, when reading partition sizes, perform a
partitionLength[..] & 0x00FFFFFF, or at least issue a
warning that something may be wrong. The offset
(partitionStart) could reach into the 4th byte.
I have attached the config block in a zip file because
the mailer would probably re-format the source text.
*/
const int kMaxNumParts = 8;
typedef struct DiskFSMicroDrive::PartitionMap {
uint16_t magic; // partition signature
uint16_t cylinders; // #of cylinders
uint16_t reserved1; // ??
uint16_t heads; // #of heads/cylinder
uint16_t sectors; // #of sectors/track
uint16_t reserved2; // ??
uint8_t numPart1; // #of partitions in first chunk
uint8_t numPart2; // #of partitions in second chunk
uint8_t reserved3[10]; // bytes 0x0e-0x17
uint16_t romVersion; // IIgs ROM01 or ROM03
uint8_t reserved4[6]; // bytes 0x1a-0x1f
uint32_t partitionStart1[kMaxNumParts]; // bytes 0x20-0x3f
uint32_t partitionLength1[kMaxNumParts]; // bytes 0x40-0x5f
uint8_t reserved5[32]; // bytes 0x60-0x7f
uint32_t partitionStart2[kMaxNumParts]; // bytes 0x80-0x9f
uint32_t partitionLength2[kMaxNumParts]; // bytes 0xa0-0xbf
uint8_t padding[320];
} PartitionMap;
/*
* Figure out if this is a MicroDrive partition.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*/
/*static*/ DIError DiskFSMicroDrive::TestImage(DiskImg* pImg,
DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t blkBuf[kBlkSize];
int partCount1, partCount2;
assert(sizeof(PartitionMap) == kBlkSize);
/*
* See if block 0 is a MicroDrive partition map.
*/
dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
if (GetShortLE(&blkBuf[0x00]) != kPartitionSignature) {
LOGI(" MicroDrive partition signature not found in first part block");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/* might assert that partCount2 be zero unless partCount1 == 8? */
partCount1 = blkBuf[0x0c];
partCount2 = blkBuf[0x0d];
if (partCount1 == 0 || partCount1 > kMaxNumParts ||
partCount2 > kMaxNumParts)
{
LOGI(" MicroDrive unreasonable partCount values %d/%d",
partCount1, partCount2);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/* consider testing other fields */
// success!
LOGI(" MicroDrive partition map count = %d/%d", partCount1, partCount2);
bail:
return dierr;
}
/*
* Unpack a partition map block into a partition map data structure.
*/
/*static*/ void DiskFSMicroDrive::UnpackPartitionMap(const uint8_t* buf,
PartitionMap* pMap)
{
pMap->magic = GetShortLE(&buf[0x00]);
pMap->cylinders = GetShortLE(&buf[0x02]);
pMap->reserved1 = GetShortLE(&buf[0x04]);
pMap->heads = GetShortLE(&buf[0x06]);
pMap->sectors = GetShortLE(&buf[0x08]);
pMap->reserved2 = GetShortLE(&buf[0x0a]);
pMap->numPart1 = buf[0x0c];
pMap->numPart2 = buf[0x0d];
memcpy(pMap->reserved3, &buf[0x0e], sizeof(pMap->reserved3));
pMap->romVersion = GetShortLE(&buf[0x18]);
memcpy(pMap->reserved4, &buf[0x1a], sizeof(pMap->reserved4));
for (int i = 0; i < kMaxNumParts; i++) {
pMap->partitionStart1[i] = GetLongLE(&buf[0x20] + i * 4);
pMap->partitionLength1[i] = GetLongLE(&buf[0x40] + i * 4) & kPartSizeMask;
pMap->partitionStart2[i] = GetLongLE(&buf[0x80] + i * 4);
pMap->partitionLength2[i] = GetLongLE(&buf[0xa0] + i * 4) & kPartSizeMask;
}
memcpy(pMap->reserved5, &buf[0x60], sizeof(pMap->reserved5));
memcpy(pMap->padding, &buf[0x80], sizeof(pMap->padding));
}
/*
* Debug: dump the contents of the partition map.
*/
/*static*/ void DiskFSMicroDrive::DumpPartitionMap(const PartitionMap* pMap)
{
LOGI(" MicroDrive partition map:");
LOGI(" cyls=%d res1=%d heads=%d sects=%d",
pMap->cylinders, pMap->reserved1, pMap->heads, pMap->sectors);
LOGI(" res2=%d numPart1=%d numPart2=%d",
pMap->reserved2, pMap->numPart1, pMap->numPart2);
LOGI(" romVersion=ROM%02d", pMap->romVersion);
int i, parts;
parts = pMap->numPart1;
assert(parts <= kMaxNumParts);
for (i = 0; i < parts; i++) {
LOGI(" %2d: startLBA=%8d length=%d",
i, pMap->partitionStart1[i], pMap->partitionLength1[i]);
}
parts = pMap->numPart2;
assert(parts <= kMaxNumParts);
for (i = 0; i < parts; i++) {
LOGI(" %2d: startLBA=%8d length=%d",
i+8, pMap->partitionStart2[i], pMap->partitionLength2[i]);
}
}
/*
* Open up a sub-volume.
*/
DIError DiskFSMicroDrive::OpenSubVolume(long startBlock, long numBlocks)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
//bool tweaked = false;
LOGI("Adding %ld +%ld", startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
LOGI("MicroDrive start block out of range (%ld vs %ld)",
startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
LOGI("MicroDrive partition too large (%ld vs %ld avail)",
numBlocks, fpImg->GetNumBlocks() - startBlock);
fpImg->AddNote(DiskImg::kNoteInfo,
"Reduced partition from %ld blocks to %ld.\n",
numBlocks, fpImg->GetNumBlocks() - startBlock);
numBlocks = fpImg->GetNumBlocks() - startBlock;
//tweaked = true;
}
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" MicroDriveSub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
/* figure out what the format is */
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" MicroDriveSub: analysis failed (err=%d)", dierr);
goto bail;
}
/* we allow unrecognized partitions */
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" MicroDriveSub (%ld,%ld): unable to identify filesystem",
startBlock, numBlocks);
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
if (pUnknownFS == NULL) {
dierr = kDIErrInternal;
goto bail;
}
//pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
pNewFS = pUnknownFS;
//pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s' type='%s'.",
// pMap->pmPartName, pMap->pmParType);
} else {
/* open a DiskFS for the sub-image */
LOGI(" MicroDriveSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == NULL) {
LOGI(" MicroDriveSub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
}
/* we encapsulate arbitrary stuff, so encourage child to scan */
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
/*
* Load the files from the sub-image. When doing our initial tests,
* or when loading data for the volume copier, we don't want to dig
* into our sub-volumes, just figure out what they are and where.
*
* If "initialize" fails, the sub-volume won't get added to the list.
* It's important that a failure at this stage doesn't cause the whole
* thing to fall over.
*/
InitMode initMode;
if (GetScanForSubVolumes() == kScanSubContainerOnly)
initMode = kInitHeaderOnly;
else
initMode = kInitFull;
dierr = pNewFS->Initialize(pNewImg, initMode);
if (dierr != kDIErrNone) {
LOGI(" MicroDriveSub: error %d reading list of files from disk", dierr);
goto bail;
}
/* add it to the list */
AddSubVolumeToList(pNewImg, pNewFS);
pNewImg = NULL;
pNewFS = NULL;
bail:
delete pNewFS;
delete pNewImg;
return dierr;
}
/*
* Check to see if this is a MicroDrive volume.
*/
/*static*/ DIError DiskFSMicroDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
return kDIErrFilesystemNotFound;
if (pImg->GetIsEmbedded()) // don't look for partitions inside
return kDIErrFilesystemNotFound;
/* assume ProDOS -- shouldn't matter, since it's embedded */
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
*pFormat = DiskImg::kFormatMicroDrive;
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
LOGI(" FS didn't find valid MicroDrive");
return kDIErrFilesystemNotFound;
}
/*
* Prep the MicroDrive "container" for use.
*/
DIError DiskFSMicroDrive::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("MicroDrive initializing (scanForSub=%d)", fScanForSubVolumes);
/* seems pointless *not* to, but we just do what we're told */
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = FindSubVolumes();
if (dierr != kDIErrNone)
return dierr;
}
/* blank out the volume usage map */
SetVolumeUsageMap();
return dierr;
}
/*
* Find the various sub-volumes and open them.
*/
DIError DiskFSMicroDrive::FindSubVolumes(void)
{
DIError dierr = kDIErrNone;
uint8_t buf[kBlkSize];
PartitionMap map;
int i;
dierr = fpImg->ReadBlock(kPartMapBlock, buf);
if (dierr != kDIErrNone)
goto bail;
UnpackPartitionMap(buf, &map);
DumpPartitionMap(&map);
/* first part of the table */
for (i = 0; i < map.numPart1; i++) {
dierr = OpenVol(i,
map.partitionStart1[i], map.partitionLength1[i]);
if (dierr != kDIErrNone)
goto bail;
}
/* second part of the table */
for (i = 0; i < map.numPart2; i++) {
dierr = OpenVol(i + kMaxNumParts,
map.partitionStart2[i], map.partitionLength2[i]);
if (dierr != kDIErrNone)
goto bail;
}
bail:
return dierr;
}
/*
* Open the volume. If it fails, open a placeholder instead. (If *that*
* fails, return with an error.)
*/
DIError DiskFSMicroDrive::OpenVol(int idx, long startBlock, long numBlocks)
{
DIError dierr;
dierr = OpenSubVolume(startBlock, numBlocks);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
LOGW(" MicroDrive failed opening sub-volume %d", idx);
dierr = CreatePlaceholder(startBlock, numBlocks, NULL, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGE(" MicroDrive unable to create placeholder (err=%d)",
dierr);
// fall out with error
}
}
bail:
return dierr;
}

1021
diskimg/Nibble.cpp Normal file

File diff suppressed because it is too large Load Diff

550
diskimg/Nibble35.cpp Normal file
View File

@ -0,0 +1,550 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* GCR nibble image support for 3.5" disks.
*
* Each track has between 8 and 12 512-byte sectors. The encoding is similar
* to but different from that used on 5.25" disks.
*
* THOUGHT: this is currently designed for unpacking all blocks from a track.
* We really ought to allow the user to view the track in nibble form, which
* means reworking the interface to be more like the 5.25" nibble stuff. We
* should present it as a block interface rather than track/sector; the code
* here can convert the block # to track/sector, and just provide a raw
* interface for the nibble track viewer.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
Physical sector layout:
+00 self-sync 0xff pattern (36 10-bit bytes, or 45 8-bit bytes)
+36 addr prolog (0xd5 0xaa 0x96)
+39 6&2enc track number (0-79 mod 63)
+40 6&2enc sector number (0-N)
+41 6&2enc side (0x00 or 0x20, ORed with 0x01 for tracks >= 64)
+42 6&2enc format (0x22, 0x24, others?)
+43 6&2enc checksum (track ^ sector ^ side ^ format)
+44 addr epilog (0xde 0xaa)
+46 self-sync 0xff (6 10-bit bytes)
+52 data prolog (0xd5 0xaa 0xad)
+55 6&2enc sector number (another copy)
+56 6&2enc nibblized data (699 bytes)
+755 checksum, 3 bytes 6&2 encoded as 4 bytes
+759 data epilog (0xde 0xaa)
+761 0xff (pad byte)
Some sources say it starts with 42 10-bit self-sync bytes instead of 36.
*/
/*
* Basic disk geometry.
*/
const int kCylindersPerDisk = 80;
const int kHeadsPerCylinder = 2;
const int kMaxSectorsPerTrack = 12;
const int kSectorSize35 = 524; // 512 data bytes + 12 tag bytes
const int kTagBytesLen = 12;
const int kDataChecksumLen = 3;
const int kChunkSize35 = 175; // ceil(524 / 3)
const int kOffsetToChecksum = 699;
const int kNibblizedOutputLen = (kOffsetToChecksum + 4);
const int kMaxDataReach = 48; // should only be 6 bytes */
enum {
kAddrProlog0 = 0xd5,
kAddrProlog1 = 0xaa,
kAddrProlog2 = 0x96,
kAddrEpilog0 = 0xde,
kAddrEpilog1 = 0xaa,
kDataProlog0 = 0xd5,
kDataProlog1 = 0xaa,
kDataProlog2 = 0xad,
kDataEpilog0 = 0xde,
kDataEpilog1 = 0xaa,
};
/*
* There are 12 sectors per track for the first 16 cylinders, 11 sectors
* per track for the next 16, and so on until we're down to 8 per track.
*/
/*static*/ int DiskImg::SectorsPerTrack35(int cylinder)
{
return kMaxSectorsPerTrack - (cylinder / 16);
}
/*
* Convert cylinder/head/sector to a block number on a 3.5" disk.
*/
/*static*/ int DiskImg::CylHeadSect35ToBlock(int cyl, int head, int sect)
{
int i, block;
assert(cyl >= 0 && cyl < kCylindersPerDisk);
assert(head >= 0 && head < kHeadsPerCylinder);
assert(sect >= 0 && sect < SectorsPerTrack35(cyl));
block = 0;
for (i = 0; i < cyl; i++)
block += SectorsPerTrack35(i) * kHeadsPerCylinder;
if (head)
block += SectorsPerTrack35(i);
block += sect;
//LOGI("Nib35: c/h/s %d/%d/%d --> block %d", cyl, head, sect, block);
assert(block >= 0 && block < 1600);
return block;
}
/*
* Unpack a nibble track.
*
* "outputBuf" must be able to hold 512 * 12 sectors of decoded sector data.
*/
/*static*/ DIError DiskImg::UnpackNibbleTrack35(const uint8_t* nibbleBuf,
long nibbleLen, uint8_t* outputBuf, int cyl, int head,
LinearBitmap* pBadBlockMap)
{
CircularBufferAccess buffer(nibbleBuf, nibbleLen);
bool foundSector[kMaxSectorsPerTrack];
uint8_t sectorBuf[kSectorSize35];
uint8_t readSum[kDataChecksumLen];
uint8_t calcSum[kDataChecksumLen];
int i;
memset(&foundSector, 0, sizeof(foundSector));
i = 0;
while (i < nibbleLen) {
int sector;
i = FindNextSector35(buffer, i, cyl, head, &sector);
if (i < 0)
break;
assert(sector >= 0 && sector < SectorsPerTrack35(cyl));
if (foundSector[sector]) {
LOGI("Nib35: WARNING: found two copies of sect %d on cyl=%d head=%d",
sector, cyl, head);
} else {
memset(sectorBuf, 0xa9, sizeof(sectorBuf));
if (DecodeNibbleSector35(buffer, i, sectorBuf, readSum, calcSum))
{
/* successfully decoded sector, copy data & verify checksum */
foundSector[sector] = true;
memcpy(outputBuf + kBlockSize * sector,
sectorBuf + kTagBytesLen, kBlockSize);
if (calcSum[0] != readSum[0] ||
calcSum[1] != readSum[1] ||
calcSum[2] != readSum[2])
{
LOGI("Nib35: checksum mismatch: 0x%06x vs. 0x%06x",
calcSum[0] << 16 | calcSum[1] << 8 | calcSum[2],
readSum[0] << 16 | readSum[1] << 8 | readSum[2]);
LOGI("Nib35: marking cyl=%d head=%d sect=%d (block=%d)",
cyl, head, sector,
CylHeadSect35ToBlock(cyl, head, sector));
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, sector));
}
}
}
}
/*
* Check to see if we have all our parts. Anything missing sets
* a flag in the "bad block" map.
*/
for (i = SectorsPerTrack35(cyl)-1; i >= 0; i--) {
if (!foundSector[i]) {
LOGI("Nib35: didn't find cyl=%d head=%d sect=%d (block=%d)",
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
}
/*
// DEBUG test
if ((cyl == 0 || cyl == 12 || cyl == 79) &&
(head == (cyl & 0x01)) &&
(i == 1 || i == 7))
{
LOGI("DEBUG: setting bad %d/%d/%d (%d)",
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
}
*/
}
return kDIErrNone; // maybe return an error if nothing found?
}
/*
* Returns the offset of the next sector, or -1 if we went off the end.
*/
/*static*/ int DiskImg::FindNextSector35(const CircularBufferAccess& buffer,
int start, int cyl, int head, int* pSector)
{
int end = buffer.GetSize();
int i;
for (i = start; i < end; i++) {
bool foundAddr = false;
if (buffer[i] == kAddrProlog0 &&
buffer[i+1] == kAddrProlog1 &&
buffer[i+2] == kAddrProlog2)
{
foundAddr = true;
}
if (foundAddr) {
/* decode the address field */
int trackNum, sectNum, side, format, checksum;
trackNum = kInvDiskBytes62[buffer[i+3]];
sectNum = kInvDiskBytes62[buffer[i+4]];
side = kInvDiskBytes62[buffer[i+5]];
format = kInvDiskBytes62[buffer[i+6]];
checksum = kInvDiskBytes62[buffer[i+7]];
if (trackNum == kInvInvalidValue ||
sectNum == kInvInvalidValue ||
side == kInvInvalidValue ||
format == kInvInvalidValue ||
checksum == kInvInvalidValue)
{
LOGI("Nib35: garbled address header found");
continue;
}
//LOGI(" Nib35: got addr: track=%2d sect=%2d side=%d format=%d sum=0x%02x",
// trackNum, sectNum, side, format, checksum);
if (side != ((head * 0x20) | (cyl >> 6))) {
LOGI("Nib35: unexpected value for side: %d on cyl=%d head=%d",
side, cyl, head);
}
if (sectNum >= SectorsPerTrack35(cyl)) {
LOGI("Nib35: invalid value for sector: %d (cyl=%d)",
sectNum, cyl);
continue;
}
/* format seems to be 0x22 or 0x24 */
if (checksum != (trackNum ^ sectNum ^ side ^ format)) {
LOGI("Nib35: unexpected checksum: 0x%02x vs. 0x%02x",
checksum, trackNum ^ sectNum ^ side ^ format);
continue;
}
/* check the epilog bytes */
if (buffer[i+8] != kAddrEpilog0 ||
buffer[i+9] != kAddrEpilog1)
{
LOGI("Nib35: invalid address epilog");
/* maybe we allow this anyway? */
}
*pSector = sectNum;
return i+10; // move past address field
}
}
return -1;
}
/*
* Unpack a 524-byte sector from a 3.5" disk. Start with "start" pointed
* in the general vicinity of the data prolog bytes.
*
* "sectorBuf" must hold at least kSectorSize35 bytes. It will be filled
* with the decoded data.
* "readChecksum" and "calcChecksum" must each hold at least kDataChecksumLen
* bytes. The former holds the checksum read from the sector, the latter
* holds the checksum computed from the data.
*
* The 4 to 3 conversion is pretty straightforward. The checksum is
* a little crazy.
*
* Returns "true" if all goes well, "false" if there is a problem. Does
* not return false on a checksum mismatch -- it's up to the caller to
* verify the checksum if desired.
*/
/*static*/ bool DiskImg::DecodeNibbleSector35(const CircularBufferAccess& buffer,
int start, uint8_t* sectorBuf, uint8_t* readChecksum,
uint8_t* calcChecksum)
{
const int kMaxDataReach35 = 48; // fairly arbitrary
uint8_t* sectorBufStart = sectorBuf;
uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
unsigned int chk0, chk1, chk2;
uint8_t val, nib0, nib1, nib2, twos;
int i, off;
/*
* Find the start of the actual data. Adjust "start" to point at it.
*/
for (off = start; off < start + kMaxDataReach35; off++) {
if (buffer[off] == kDataProlog0 &&
buffer[off+1] == kDataProlog1 &&
buffer[off+2] == kDataProlog2)
{
start = off + 4; // 3 prolog bytes + sector number
break;
}
}
if (off == start + kMaxDataReach35) {
LOGI("nib25: could not find start of data field");
return false;
}
/*
* Assemble 8-bit bytes from 6&2 encoded values.
*/
off = start;
for (i = 0; i < kChunkSize35; i++) {
twos = kInvDiskBytes62[buffer[off++]];
nib0 = kInvDiskBytes62[buffer[off++]];
nib1 = kInvDiskBytes62[buffer[off++]];
if (i != kChunkSize35-1)
nib2 = kInvDiskBytes62[buffer[off++]];
else
nib2 = 0;
if (twos == kInvInvalidValue ||
nib0 == kInvInvalidValue ||
nib1 == kInvInvalidValue ||
nib2 == kInvInvalidValue)
{
// junk found
LOGI("Nib25: found invalid disk byte in sector data at %d",
off - start);
LOGI(" (one of 0x%02x 0x%02x 0x%02x 0x%02x)",
buffer[off-4], buffer[off-3], buffer[off-2], buffer[off-1]);
return false;
//if (twos == kInvInvalidValue)
// twos = 0;
//if (nib0 == kInvInvalidValue)
// nib0 = 0;
//if (nib1 == kInvInvalidValue)
// nib1 = 0;
//if (nib2 == kInvInvalidValue)
// nib2 = 0;
}
part0[i] = nib0 | ((twos << 2) & 0xc0);
part1[i] = nib1 | ((twos << 4) & 0xc0);
part2[i] = nib2 | ((twos << 6) & 0xc0);
}
assert(off == start + kOffsetToChecksum);
chk0 = chk1 = chk2 = 0;
i = 0;
while (true) {
chk0 = (chk0 & 0xff) << 1;
if (chk0 & 0x0100)
chk0++;
val = part0[i] ^ chk0;
chk2 += val;
if (chk0 & 0x0100) {
chk2++;
chk0 &= 0xff;
}
*sectorBuf++ = val;
val = part1[i] ^ chk2;
chk1 += val;
if (chk2 > 0xff) {
chk1++;
chk2 &= 0xff;
}
*sectorBuf++ = val;
if (sectorBuf - sectorBufStart == 524)
break;
val = part2[i] ^ chk1;
chk0 += val;
if (chk1 > 0xff) {
chk0++;
chk1 &= 0xff;
}
*sectorBuf++ = val;
i++;
assert(i < kChunkSize35);
//LOGI("i = %d, diff=%d", i, sectorBuf - sectorBufStart);
}
calcChecksum[0] = chk0;
calcChecksum[1] = chk1;
calcChecksum[2] = chk2;
if (!UnpackChecksum35(buffer, off, readChecksum)) {
LOGI("Nib35: failure reading checksum");
readChecksum[0] = calcChecksum[0] ^ 0xff; // force a failure
return false;
}
off += 4; // skip past checksum bytes
if (buffer[off] != kDataEpilog0 || buffer[off+1] != kDataEpilog1) {
LOGI("nib25: WARNING: data epilog not found");
// allow it, if the checksum matches
}
//#define TEST_ENC_35
#ifdef TEST_ENC_35
{
uint8_t nibBuf[kNibblizedOutputLen];
memset(nibBuf, 0xcc, sizeof(nibBuf));
/* encode what we just decoded */
EncodeNibbleSector35(sectorBufStart, nibBuf);
/* compare it to the original */
for (i = 0; i < kNibblizedOutputLen; i++) {
if (buffer[start + i] != nibBuf[i]) {
/*
* The very last "twos" entry may have undefined bits when
* written by a real drive. Peel it apart and ignore the
* two flaky bits.
*/
if (i == 696) {
uint8_t val1, val2;
val1 = kInvDiskBytes62[buffer[start + i]];
val2 = kInvDiskBytes62[nibBuf[i]];
if ((val1 & 0xfc) != (val2 & 0xfc)) {
LOGI("Nib35 DEBUG: output differs at byte %d"
" (0x%02x vs 0x%02x / 0x%02x vs 0x%02x)",
i, buffer[start+i], nibBuf[i], val1, val2);
}
} else {
// note: checksum is 699-702
LOGI("Nib35 DEBUG: output differs at byte %d (0x%02x vs 0x%02x)",
i, buffer[start+i], nibBuf[i]);
}
}
}
}
#endif /*TEST_ENC_35*/
return true;
}
/*
* Unpack the 6&2 encoded 3-byte checksum at the end of a sector.
*
* "offset" should point to the first byte of the checksum.
*
* Returns "true" if all goes well, "false" otherwise.
*/
/*static*/ bool DiskImg::UnpackChecksum35(const CircularBufferAccess& buffer,
int offset, uint8_t* checksumBuf)
{
uint8_t nib0, nib1, nib2, twos;
twos = kInvDiskBytes62[buffer[offset++]];
nib2 = kInvDiskBytes62[buffer[offset++]];
nib1 = kInvDiskBytes62[buffer[offset++]];
nib0 = kInvDiskBytes62[buffer[offset++]];
if (twos == kInvInvalidValue ||
nib0 == kInvInvalidValue ||
nib1 == kInvInvalidValue ||
nib2 == kInvInvalidValue)
{
LOGI("nib25: found invalid disk byte in checksum");
return false;
}
checksumBuf[0] = nib0 | ((twos << 6) & 0xc0);
checksumBuf[1] = nib1 | ((twos << 4) & 0xc0);
checksumBuf[2] = nib2 | ((twos << 2) & 0xc0);
return true;
}
/*
* Encode 524 bytes of sector data into 699 bytes of 6&2 nibblized data
* plus a 4-byte checksum.
*
* "outBuf" must be able to hold kNibblizedOutputLen bytes.
*/
/*static*/ void DiskImg::EncodeNibbleSector35(const uint8_t* sectorData,
uint8_t* outBuf)
{
const uint8_t* sectorDataStart = sectorData;
uint8_t* outBufStart = outBuf;
uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
unsigned int chk0, chk1, chk2;
uint8_t val, twos;
int i;
/*
* Compute checksum and split the input into 3 pieces.
*/
i = 0;
chk0 = chk1 = chk2 = 0;
while (true) {
chk0 = (chk0 & 0xff) << 1;
if (chk0 & 0x0100)
chk0++;
val = *sectorData++;
chk2 += val;
if (chk0 & 0x0100) {
chk2++;
chk0 &= 0xff;
}
part0[i] = (val ^ chk0) & 0xff;
val = *sectorData++;
chk1 += val;
if (chk2 > 0xff) {
chk1++;
chk2 &= 0xff;
}
part1[i] = (val ^ chk2) & 0xff;
if (sectorData - sectorDataStart == 524)
break;
val = *sectorData++;
chk0 += val;
if (chk1 > 0xff) {
chk0++;
chk1 &= 0xff;
}
part2[i] = (val ^ chk1) & 0xff;
i++;
}
part2[kChunkSize35-1] = 0; // gets merged into the "twos"
assert(i == kChunkSize35-1);
/*
* Output the nibble data.
*/
for (i = 0; i < kChunkSize35; i++) {
twos = ((part0[i] & 0xc0) >> 2) |
((part1[i] & 0xc0) >> 4) |
((part2[i] & 0xc0) >> 6);
*outBuf++ = kDiskBytes62[twos];
*outBuf++ = kDiskBytes62[part0[i] & 0x3f];
*outBuf++ = kDiskBytes62[part1[i] & 0x3f];
if (i != kChunkSize35 -1)
*outBuf++ = kDiskBytes62[part2[i] & 0x3f];
}
/*
* Output the checksum.
*/
twos = ((chk0 & 0xc0) >> 6) | ((chk1 & 0xc0) >> 4) | ((chk2 & 0xc0) >> 2);
*outBuf++ = kDiskBytes62[twos];
*outBuf++ = kDiskBytes62[chk2 & 0x3f];
*outBuf++ = kDiskBytes62[chk1 & 0x3f];
*outBuf++ = kDiskBytes62[chk0 & 0x3f];
assert(outBuf - outBufStart == kNibblizedOutputLen);
}

1504
diskimg/OuterWrapper.cpp Normal file

File diff suppressed because it is too large Load Diff

320
diskimg/OzDOS.cpp Normal file
View File

@ -0,0 +1,320 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of DiskFSOzDOS class.
*
* It would make life MUCH EASIER to have the DiskImg recognize this as
* a file format and just rearrange the blocks into linear order for us,
* but unfortunately that's not going to happen.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSOzDOS
* ===========================================================================
*/
const int kExpectedNumBlocks = 1600;
const int kExpectedTracks = 50; // 50 tracks of 32 sectors == 400K
const int kExpectedSectors = 32;
const int kVTOCTrack = 17;
const int kVTOCSector = 0;
const int kSctSize = 256;
const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries
const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
const int kMaxTSIterations = 32;
const int kMaxCatalogIterations = 64;
/*
* Read a track/sector, adjusting for 32-sector disks being treated as
* if they were 16-sector.
*/
static DIError ReadTrackSectorAdjusted(DiskImg* pImg, int track, int sector,
int sectorOffset, uint8_t* buf, DiskImg::SectorOrder imageOrder)
{
track *= 4;
sector = sector * 2 + sectorOffset;
while (sector >= 16) {
track++;
sector -= 16;
}
return pImg->ReadTrackSectorSwapped(track, sector, buf, imageOrder,
DiskImg::kSectorOrderDOS);
}
/*
* Test for presence of 400K OzDOS 3.3 volumes.
*/
static DIError TestImageHalf(DiskImg* pImg, int sectorOffset,
DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
int numTracks, numSectors;
int catTrack, catSect;
int foundGood = 0;
int iterations = 0;
assert(sectorOffset == 0 || sectorOffset == 1);
dierr = ReadTrackSectorAdjusted(pImg, kVTOCTrack, kVTOCSector,
sectorOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone)
goto bail;
catTrack = sctBuf[0x01];
catSect = sctBuf[0x02];
numTracks = sctBuf[0x34];
numSectors = sctBuf[0x35];
if (!(sctBuf[0x27] == kMaxTSPairs) ||
/*!(sctBuf[0x36] == 0 && sctBuf[0x37] == 1) ||*/ // bytes per sect
!(numTracks == kExpectedTracks) ||
!(numSectors == 32) ||
!(catTrack < numTracks && catSect < numSectors) ||
0)
{
LOGI(" OzDOS header test %d failed", sectorOffset);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/*
* Walk through the catalog track to try to figure out ordering.
*/
while (catTrack != 0 && catSect != 0 && iterations < kMaxCatalogIterations)
{
dierr = ReadTrackSectorAdjusted(pImg, catTrack, catSect,
sectorOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone)
goto bail_ok; /* allow it if not fully broken */
if (sctBuf[1] == catTrack && sctBuf[2] == catSect-1)
foundGood++;
catTrack = sctBuf[1];
catSect = sctBuf[2];
iterations++; // watch for infinite loops
}
if (iterations >= kMaxCatalogIterations) {
dierr = kDIErrDirectoryLoop;
goto bail;
}
bail_ok:
LOGI(" OzDOS foundGood=%d off=%d swap=%d", foundGood, sectorOffset,
imageOrder);
/* foundGood hits 3 even when swap is wrong */
if (foundGood > 4)
dierr = kDIErrNone;
else
dierr = kDIErrFilesystemNotFound;
bail:
return dierr;
}
/*
* Test both of the DOS partitions.
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
DIError dierr;
LOGI(" OzDOS checking first half (swap=%d)", imageOrder);
dierr = TestImageHalf(pImg, 0, imageOrder);
if (dierr != kDIErrNone)
return dierr;
LOGI(" OzDOS checking second half (swap=%d)", imageOrder);
dierr = TestImageHalf(pImg, 1, imageOrder);
if (dierr != kDIErrNone)
return dierr;
return kDIErrNone;
}
/*
* Test to see if the image is a OzDOS volume.
*/
/*static*/ DIError DiskFSOzDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* only on 800K disks (at the least, insist on numTracks being even) */
if (pImg->GetNumBlocks() != kExpectedNumBlocks)
return kDIErrFilesystemNotFound;
/* if a value is specified, try that first -- useful for OverrideFormat */
if (*pOrder != DiskImg::kSectorOrderUnknown) {
if (TestImage(pImg, *pOrder) == kDIErrNone) {
LOGI(" OzDOS accepted FirstTry value");
return kDIErrNone;
}
}
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
*pOrder = ordering[i];
*pFormat = DiskImg::kFormatOzDOS;
return kDIErrNone;
}
}
LOGI(" OzDOS didn't find valid FS");
return kDIErrFilesystemNotFound;
}
#if 0
/*
* Test to see if the image is a 'wide' (32-sector) DOS3.3 volume, i.e.
* half of a OzDOS volume.
*/
/*static*/ DIError DiskFS::TestOzWideDOS33(const DiskImg* pImg,
DiskImg::SectorOrder* pOrder)
{
DIError dierr = kDIErrNone;
/* only on 400K disks (at the least, insist on numTracks being even) */
if (pImg->GetNumBlocks() != kExpectedNumBlocks/2)
return kDIErrFilesystemNotFound;
/* if a value is specified, try that first -- useful for OverrideFormat */
if (*pOrder != DiskImg::kSectorOrderUnknown) {
if (TestImageHalf(pImg, 0, *pOrder) == kDIErrNone) {
LOGI(" WideDOS accepted FirstTry value");
return kDIErrNone;
}
}
if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderDOS) == kDIErrNone) {
*pOrder = DiskImg::kSectorOrderDOS;
} else if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
*pOrder = DiskImg::kSectorOrderProDOS;
} else if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderPhysical) == kDIErrNone) {
*pOrder = DiskImg::kSectorOrderPhysical;
} else {
LOGI(" FS didn't find valid 'wide' DOS3.3");
return kDIErrFilesystemNotFound;
}
return kDIErrNone;
}
#endif
/*
* Set up our sub-volumes.
*/
DIError DiskFSOzDOS::Initialize(void)
{
DIError dierr = kDIErrNone;
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = OpenSubVolume(0);
if (dierr != kDIErrNone)
return dierr;
dierr = OpenSubVolume(1);
if (dierr != kDIErrNone)
return dierr;
} else {
LOGI(" OzDOS not scanning for sub-volumes");
}
SetVolumeUsageMap();
return kDIErrNone;
}
/*
* Open up one of the DOS 3.3 sub-volumes.
*/
DIError DiskFSOzDOS::OpenSubVolume(int idx)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
// open the full 800K; SetPairedSectors cuts it in half
dierr = pNewImg->OpenImage(fpImg, 0, 0,
2 * kExpectedTracks * kExpectedSectors);
if (dierr != kDIErrNone) {
LOGI(" OzSub: OpenImage(%d,0,%d) failed (err=%d)",
0, 2 * kExpectedTracks * kExpectedSectors, dierr);
goto bail;
}
assert(idx == 0 || idx == 1);
pNewImg->SetPairedSectors(true, 1-idx);
LOGI(" OzSub: testing for recognizable volume in idx=%d", idx);
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" OzSub: analysis failed (err=%d)", dierr);
goto bail;
}
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" OzSub: unable to identify filesystem");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* open a DiskFS for the sub-image */
LOGI(" UNISub %d succeeded!", idx);
pNewFS = pNewImg->OpenAppropriateDiskFS();
if (pNewFS == NULL) {
LOGI(" OzSub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* load the files from the sub-image */
dierr = pNewFS->Initialize(pNewImg, kInitFull);
if (dierr != kDIErrNone) {
LOGE(" OzSub: error %d reading list of files from disk", dierr);
goto bail;
}
/* if this really is DOS 3.3, override the "volume name" */
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
DiskFSDOS33* pDOS = (DiskFSDOS33*) pNewFS; /* eek, a downcast */
pDOS->SetDiskVolumeNum(idx+1);
}
/*
* Success, add it to the sub-volume list.
*/
AddSubVolumeToList(pNewImg, pNewFS);
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
}
return dierr;
}

1863
diskimg/Pascal.cpp Normal file

File diff suppressed because it is too large Load Diff

5183
diskimg/ProDOS.cpp Normal file

File diff suppressed because it is too large Load Diff

732
diskimg/RDOS.cpp Normal file
View File

@ -0,0 +1,732 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of DiskFSRDOS class.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSRDOS
* ===========================================================================
*/
const int kSctSize = 256;
const int kCatTrack = 1;
const int kNumCatSectors = 11; // 0 through 10
const int kDirectoryEntryLen = 32;
const int kNumDirEntryPerSect = (256 / kDirectoryEntryLen); // 8
/*
* See if this looks like a RDOS volume.
*
* There are three variants:
* RDOS32 (e.g. ComputerAmbush.nib):
* 13-sector disk
* sector (1,0) starts with "RDOS 2"
* sector (1,12) has catalog code, CHAIN in (1,11)
* uses "physical" ordering
* NOTE: track 0 may be unreadable with RDOS 3.2 NibbleDescr
* RDOS33 (e.g. disk #199):
* 16-sector disk
* sector (1,0) starts with "RDOS 3"
* sector (1,12) has catalog code
* uses "ProDOS" ordering
* RDOS3 (e.g. disk #108):
* 16-sector disk, but only 13 sectors of each track are used
* sector (1,0) starts with "RDOS 2"
* sector (0,1) has catalog code
* uses "physical" orering
*
* In all cases:
* catalog found on (1,0) through (1,10)
*
* The initial value of "pFormatFound" is ignored, because we can reliably
* detect which variant we're looking at.
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
DiskImg::FSFormat* pFormatFound)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
if (pImg->GetNumSectPerTrack() == 13) {
/* must be a nibble image; check it for RDOS 3.2 */
dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf,
imageOrder, DiskImg::kSectorOrderPhysical);
if (dierr != kDIErrNone)
goto bail;
} else if (pImg->GetNumSectPerTrack() == 16) {
/* could be RDOS3 or RDOS 3.3 */
dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf,
imageOrder, DiskImg::kSectorOrderPhysical);
if (dierr != kDIErrNone)
goto bail;
} else {
LOGI(" RDOS neither 13 nor 16 sector, bailing");
goto bail;
}
/* check for RDOS string and correct #of blocks */
if (!( sctBuf[0] == 'R'+0x80 &&
sctBuf[1] == 'D'+0x80 &&
sctBuf[2] == 'O'+0x80 &&
sctBuf[3] == 'S'+0x80 &&
sctBuf[4] == ' '+0x80) ||
!(sctBuf[25] == 26 || sctBuf[25] == 32))
{
LOGI(" RDOS no signature found on (%d,0)", kCatTrack);
dierr = kDIErrGeneric;
goto bail;
}
/*
* Guess at the format based on the first catalog entry, which usually
* begins "RDOS 2.0", "RDOS 2.1", or "RDOS 3.3".
*/
if (pImg->GetNumSectPerTrack() == 13) {
*pFormatFound = DiskImg::kFormatRDOS32;
} else {
if (sctBuf[5] == '2'+0x80)
*pFormatFound = DiskImg::kFormatRDOS3;
else
*pFormatFound = DiskImg::kFormatRDOS33;
}
/*
* The above came from sector 0, which doesn't help us figure out the
* sector ordering. Look for the catalog code.
*/
{
int track, sector, offset;
uint8_t orMask;
static const char* kCompare = "<NAME>";
DiskImg::SectorOrder order;
if (*pFormatFound == DiskImg::kFormatRDOS32 ||
*pFormatFound == DiskImg::kFormatRDOS3)
{
track = 1;
sector = 12;
offset = 0xa2;
orMask = 0x80;
order = DiskImg::kSectorOrderPhysical;
} else {
track = 0;
sector = 1;
offset = 0x98;
orMask = 0;
order = DiskImg::kSectorOrderProDOS;
}
dierr = pImg->ReadTrackSectorSwapped(track, sector, sctBuf,
imageOrder, order);
if (dierr != kDIErrNone)
goto bail;
int i;
for (i = strlen(kCompare)-1; i >= 0; i--) {
if (sctBuf[offset+i] != ((uint8_t)kCompare[i] | orMask))
break;
}
if (i >= 0) {
dierr = kDIErrGeneric;
goto bail;
}
LOGI(" RDOS found '%s' signature (order=%d)", kCompare, imageOrder);
}
dierr = kDIErrNone;
bail:
return dierr;
}
/*
* Common RDOS test code.
*/
/*static*/ DIError DiskFSRDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
if (!pImg->GetHasSectors()) {
LOGI(" RDOS - image doesn't have sectors, not trying");
return kDIErrFilesystemNotFound;
}
if (pImg->GetNumTracks() != 35) {
LOGI(" RDOS - not a 35-track disk, not trying");
return kDIErrFilesystemNotFound;
}
DiskImg::FSFormat formatFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i], &formatFound) == kDIErrNone) {
*pFormat = formatFound;
*pOrder = ordering[i];
//*pFormat = DiskImg::kFormatXXX;
return kDIErrNone;
}
}
LOGI(" RDOS didn't find valid FS");
return kDIErrFilesystemNotFound;
}
#if 0
/*
* Test to see if the image is an RDOS 3.3 disk.
*/
/*static*/ DIError DiskFSRDOS::TestFS33(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
FSLeniency leniency)
{
DIError dierr;
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
if (dierr != kDIErrNone)
return dierr;
if (formatFound != DiskImg::kFormatRDOS33) {
LOGI(" RDOS found RDOS but wrong type");
return kDIErrFilesystemNotFound;
}
return kDIErrNone;
}
/*
* Test to see if the image is an RDOS 3.2 disk.
*/
/*static*/ DIError DiskFSRDOS::TestFS32(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
FSLeniency leniency)
{
DIError dierr;
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
if (dierr != kDIErrNone)
return dierr;
if (formatFound != DiskImg::kFormatRDOS32) {
LOGI(" RDOS found RDOS but wrong type");
return kDIErrFilesystemNotFound;
}
return kDIErrNone;
}
/*
* Test to see if the image is an RDOS 3 (cracked 3.2) disk.
*/
/*static*/ DIError DiskFSRDOS::TestFS3(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
FSLeniency leniency)
{
DIError dierr;
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
if (dierr != kDIErrNone)
return dierr;
if (formatFound != DiskImg::kFormatRDOS3) {
LOGI(" RDOS found RDOS but wrong type");
return kDIErrFilesystemNotFound;
}
return kDIErrNone;
}
#endif
/*
* Get things rolling.
*
* Since we're assured that this is a valid disk, errors encountered from here
* on out must be handled somehow, possibly by claiming that the disk is
* completely full and has no files on it.
*/
DIError DiskFSRDOS::Initialize(void)
{
DIError dierr = kDIErrNone;
const char* volStr;
switch (GetDiskImg()->GetFSFormat()) {
case DiskImg::kFormatRDOS33:
volStr = "RDOS 3.3";
fOurSectPerTrack = 16;
break;
case DiskImg::kFormatRDOS32:
volStr = "RDOS 3.2";
fOurSectPerTrack = 13;
break;
case DiskImg::kFormatRDOS3:
volStr = "RDOS 3";
fOurSectPerTrack = 13;
break;
default:
assert(false);
return kDIErrInternal;
}
assert(strlen(volStr) < sizeof(fVolumeName));
strcpy(fVolumeName, volStr);
dierr = ReadCatalog();
if (dierr != kDIErrNone)
goto bail;
fVolumeUsage.Create(fpImg->GetNumTracks(), fOurSectPerTrack);
dierr = ScanFileUsage();
if (dierr != kDIErrNone) {
/* this might not be fatal; just means that *some* files are bad */
goto bail;
}
fVolumeUsage.Dump();
//A2File* pFile;
//pFile = GetNextFile(NULL);
//while (pFile != NULL) {
// pFile->Dump();
// pFile = GetNextFile(pFile);
//}
bail:
return dierr;
}
/*
* Read the catalog from the disk.
*
* To make life easy we slurp the whole thing into memory.
*/
DIError DiskFSRDOS::ReadCatalog(void)
{
DIError dierr = kDIErrNone;
uint8_t* dir = NULL;
uint8_t* dirPtr;
int track, sector;
dir = new uint8_t[kSctSize * kNumCatSectors];
if (dir == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
track = kCatTrack;
dirPtr = dir;
for (sector = 0; sector < kNumCatSectors; sector++) {
dierr = fpImg->ReadTrackSector(track, sector, dirPtr);
if (dierr != kDIErrNone)
goto bail;
dirPtr += kSctSize;
}
int i;
A2FileRDOS* pFile;
dirPtr = dir;
for (i = 0; i < kNumCatSectors * kNumDirEntryPerSect;
i++, dirPtr += kDirectoryEntryLen)
{
if (dirPtr[0] == 0x80 || dirPtr[24] == 0xa0) // deleted file
continue;
if (dirPtr[24] == 0x00) // unused entry; must be at end of catalog
break;
pFile = new A2FileRDOS(this);
memcpy(pFile->fRawFileName, dirPtr, A2FileRDOS::kMaxFileName);
pFile->fRawFileName[A2FileRDOS::kMaxFileName] = '\0';
memcpy(pFile->fFileName, dirPtr, A2FileRDOS::kMaxFileName);
pFile->fFileName[A2FileRDOS::kMaxFileName] = '\0';
pFile->FixFilename();
switch (dirPtr[24]) {
case 'A'+0x80: pFile->fFileType = A2FileRDOS::kTypeApplesoft; break;
case 'B'+0x80: pFile->fFileType = A2FileRDOS::kTypeBinary; break;
case 'T'+0x80: pFile->fFileType = A2FileRDOS::kTypeText; break;
// 0x00 is end of catalog, ' '+0x80 is deleted file, both handled above
default: pFile->fFileType = A2FileRDOS::kTypeUnknown; break;
}
pFile->fNumSectors = dirPtr[25];
pFile->fLoadAddr = GetShortLE(&dirPtr[26]);
pFile->fLength = GetShortLE(&dirPtr[28]);
pFile->fStartSector = GetShortLE(&dirPtr[30]);
if (pFile->fStartSector + pFile->fNumSectors >
fpImg->GetNumTracks() * fOurSectPerTrack)
{
LOGI(" RDOS invalid start/count (%d + %d) (max %ld) '%s'",
pFile->fStartSector, pFile->fNumSectors, fpImg->GetNumBlocks(),
pFile->fFileName);
pFile->fStartSector = pFile->fNumSectors = 0;
pFile->fLength = 0;
pFile->SetQuality(A2File::kQualityDamaged);
}
AddFileToList(pFile);
}
bail:
delete[] dir;
return dierr;
}
/*
* Create the volume usage map. Since RDOS volumes have neither
* in-use maps nor index blocks, this is pretty straightforward.
*/
DIError DiskFSRDOS::ScanFileUsage(void)
{
int track, sector, block, count;
A2FileRDOS* pFile;
pFile = (A2FileRDOS*) GetNextFile(NULL);
while (pFile != NULL) {
block = pFile->fStartSector;
count = pFile->fNumSectors;
while (count--) {
track = block / fOurSectPerTrack;
sector = block % fOurSectPerTrack;
SetSectorUsage(track, sector, VolumeUsage::kChunkPurposeUserData);
block++;
}
pFile = (A2FileRDOS*) GetNextFile(pFile);
}
return kDIErrNone;
}
/*
* Update an entry in the usage map.
*/
void DiskFSRDOS::SetSectorUsage(long track, long sector,
VolumeUsage::ChunkPurpose purpose)
{
VolumeUsage::ChunkState cstate;
fVolumeUsage.GetChunkState(track, sector, &cstate);
if (cstate.isUsed) {
cstate.purpose = VolumeUsage::kChunkPurposeConflict;
LOGI(" RDOS conflicting uses for sct=(%ld,%ld)", track, sector);
} else {
cstate.isUsed = true;
cstate.isMarkedUsed = true;
cstate.purpose = purpose;
}
fVolumeUsage.SetChunkState(track, sector, &cstate);
}
/*
* ===========================================================================
* A2FileRDOS
* ===========================================================================
*/
/*
* Convert RDOS file type to ProDOS file type.
*/
uint32_t A2FileRDOS::GetFileType(void) const
{
uint32_t retval;
switch (fFileType) {
case kTypeText: retval = 0x04; break; // TXT
case kTypeApplesoft: retval = 0xfc; break; // BAS
case kTypeBinary: retval = 0x06; break; // BIN
case kTypeUnknown:
default: retval = 0x00; break; // NON
}
return retval;
}
/*
* Dump the contents of the A2File structure.
*/
void A2FileRDOS::Dump(void) const
{
LOGI("A2FileRDOS '%s' (type=%d)", fFileName, fFileType);
LOGI(" start=%d num=%d len=%d addr=0x%04x",
fStartSector, fNumSectors, fLength, fLoadAddr);
}
/*
* "Fix" an RDOS filename. Convert DOS-ASCII to normal ASCII, and strip
* trailing spaces.
*
* It's possible that RDOS 3.3 forces the filename to high-ASCII, because
* one disk (#938A) has a file left by the crackers whose name is in
* low-ASCII. The inverse-mode correction turns it into punctuation, but
* I don't see a good way around it. Or any particular need to fix it.
*/
void A2FileRDOS::FixFilename(void)
{
DiskFSDOS33::LowerASCII((uint8_t*)fFileName, kMaxFileName);
TrimTrailingSpaces(fFileName);
}
/*
* Trim the spaces off the end of a filename.
*
* Assumes the filename has already been converted to low ASCII.
*/
void A2FileRDOS::TrimTrailingSpaces(char* filename)
{
char* lastspc = filename + strlen(filename);
assert(*lastspc == '\0');
while (--lastspc) {
if (*lastspc != ' ')
break;
}
*(lastspc+1) = '\0';
}
/*
* Not a whole lot to do, since there's no fancy index blocks.
*/
DIError A2FileRDOS::Open(A2FileDescr** ppOpenFile, bool readOnly,
bool rsrcFork /*=false*/)
{
if (fpOpenFile != NULL)
return kDIErrAlreadyOpen;
if (rsrcFork)
return kDIErrForkNotFound;
assert(readOnly == true);
A2FDRDOS* pOpenFile = new A2FDRDOS(this);
pOpenFile->fOffset = 0;
//fOpen = true;
fpOpenFile = pOpenFile;
*ppOpenFile = pOpenFile;
pOpenFile = NULL;
return kDIErrNone;
}
/*
* Returns the raw filename.
*
* If a pointer to a size_t is passed in, it will be filled with the
* raw filename length.
*/
const char* A2FileRDOS::GetRawFileName(size_t* size) const {
if (size) {
*size = strlen(fRawFileName);
}
return fRawFileName;
}
/*
* ===========================================================================
* A2FDRDOS
* ===========================================================================
*/
/*
* Read a chunk of data from the current offset.
*/
DIError A2FDRDOS::Read(void* buf, size_t len, size_t* pActual)
{
LOGD(" RDOS reading %lu bytes from '%s' (offset=%ld)",
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
//if (!fOpen)
// return kDIErrNotReady;
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
/* don't allow them to read past the end of the file */
if (fOffset + (long)len > pFile->fLength) {
if (pActual == NULL)
return kDIErrDataUnderrun;
len = (size_t) (pFile->fLength - fOffset);
}
if (pActual != NULL)
*pActual = len;
long incrLen = len;
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
long block = pFile->fStartSector + (long) (fOffset / kSctSize);
int bufOffset = (int) (fOffset % kSctSize); // (& 0xff)
int ourSectPerTrack = GetOurSectPerTrack();
size_t thisCount;
if (len == 0) {
///* one block allocated for empty file */
//SetLastBlock(block, true);
return kDIErrNone;
}
assert(pFile->fLength != 0);
while (len) {
assert(block >= pFile->fStartSector &&
block < pFile->fStartSector + pFile->fNumSectors);
dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(block / ourSectPerTrack,
block % ourSectPerTrack, sctBuf);
if (dierr != kDIErrNone) {
LOGI(" RDOS error reading file '%s'", pFile->fFileName);
return dierr;
}
thisCount = kSctSize - bufOffset;
if (thisCount > len)
thisCount = len;
memcpy(buf, sctBuf + bufOffset, thisCount);
len -= thisCount;
buf = (char*)buf + thisCount;
bufOffset = 0;
block++;
}
fOffset += incrLen;
return dierr;
}
/*
* Write data at the current offset.
*/
DIError A2FDRDOS::Write(const void* buf, size_t len, size_t* pActual)
{
//if (!fOpen)
// return kDIErrNotReady;
return kDIErrNotSupported;
}
/*
* Seek to a new offset.
*/
DIError A2FDRDOS::Seek(di_off_t offset, DIWhence whence)
{
//if (!fOpen)
// return kDIErrNotReady;
long fileLen = ((A2FileRDOS*) fpFile)->fLength;
switch (whence) {
case kSeekSet:
if (offset < 0 || offset > fileLen)
return kDIErrInvalidArg;
fOffset = offset;
break;
case kSeekEnd:
if (offset > 0 || offset < -fileLen)
return kDIErrInvalidArg;
fOffset = fileLen + offset;
break;
case kSeekCur:
if (offset < -fOffset ||
offset >= (fileLen - fOffset))
{
return kDIErrInvalidArg;
}
fOffset += offset;
break;
default:
assert(false);
return kDIErrInvalidArg;
}
assert(fOffset >= 0 && fOffset <= fileLen);
return kDIErrNone;
}
/*
* Return current offset.
*/
di_off_t A2FDRDOS::Tell(void)
{
//if (!fOpen)
// return kDIErrNotReady;
return fOffset;
}
/*
* Release file state, such as it is.
*/
DIError A2FDRDOS::Close(void)
{
fpFile->CloseDescr(this);
return kDIErrNone;
}
/*
* Return the #of sectors/blocks in the file.
*/
long A2FDRDOS::GetSectorCount(void) const
{
//if (!fOpen)
// return kDIErrNotReady;
return ((A2FileRDOS*) fpFile)->fNumSectors;
}
long A2FDRDOS::GetBlockCount(void) const
{
//if (!fOpen)
// return kDIErrNotReady;
return ((A2FileRDOS*) fpFile)->fNumSectors / 2;
}
/*
* Return the Nth track/sector in this file.
*/
DIError A2FDRDOS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
{
//if (!fOpen)
// return kDIErrNotReady;
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
long rdosBlock = pFile->fStartSector + sectorIdx;
int ourSectPerTrack = GetOurSectPerTrack();
if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors)
return kDIErrInvalidIndex;
*pTrack = rdosBlock / ourSectPerTrack;
*pSector = rdosBlock % ourSectPerTrack;
return kDIErrNone;
}
/*
* Return the Nth 512-byte block in this file.
*/
DIError A2FDRDOS::GetStorage(long blockIdx, long* pBlock) const
{
//if (!fOpen)
// return kDIErrNotReady;
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
long rdosBlock = pFile->fStartSector + blockIdx*2;
if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors)
return kDIErrInvalidIndex;
*pBlock = rdosBlock / 2;
if (pFile->GetDiskFS()->GetDiskImg()->GetHasBlocks()) {
assert(*pBlock < pFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
}
return kDIErrNone;
}

245
diskimg/README.md Normal file
View File

@ -0,0 +1,245 @@
CiderPress Disk Image Library
=============================
This library provides access to files stored in Apple II disk images. It
was developed as part of CiderPress, but can be used independently. It
builds on Windows and Linux.
The MDC (Multi-Disk Catalog) application uses the DiskImg DLL (on Windows)
or library (on Linux) to examine disk image files.
Disk Image Structure
--------------------
The Apple II supported a number of different filesystems and physical
formats. There are several different disk image formats, some supported
by Apple II software, some only usable by emulators. This section
provides a quick summary.
#### Filesystems ####
- DOS 3.2/3.3. The classic Apple II filesystems. The typical DOS 3.3
disk has 35 tracks, 16 sectors per track, 256 bytes per sector, for a
total of 140K. The format allows up to 32 sectors and 50 tracks (400K).
It worked very well on 5.25" floppy disks, but was awkward to use on
larger volumes. Filenames could be up to 30 characters, and sometimes
embedded control characters or used flashing/inverse character values.
- ProDOS. Designed to work on larger media, ProDOS addresses data as
512-byte blocks, and leaves any track/sector mapping to device-specific
code. Block numbers were stored as unsigned 16-bit values, allowing
volumes up to 32MB. Filenames were limited to 15 upper-case or numeric
ASCII values. Later versions added support for forked files and
case-preserved (but still case-insensitive) filenames.
- UCSD Pascal. Another block-oriented filesystem, used with the UCSD
Pascal operating system. Very simple, and very efficient when reading,
but required explicit defragmentation from time to time.
- HFS. Originally developed for the Macintosh, it was often used on
the Apple IIgs as hard drive sizes increased. HFS supports forked files,
and "MacRoman" filenames up to 31 characters.
- CP/M. Z-80 cards allowed Apple II users to run CP/M software, using
the established CP/M filesystem layout. It featured 1K blocks and 8.3
filenames.
- SSI RDOS. A custom format developed by Strategic Simulations, Inc. for
use with their games. This was used on 13-sector and 16-sector 5.25"
disks. The operating system used Applesoft ampersand commands, and was
ported to ProDOS.
- Gutenberg. A custom format developed by Micromation Limited for use
with the Gutenberg word processor.
DOS, ProDOS, HFS, and UCSD Pascal are fully supported by the DiskImg
library. CP/M, RDOS, and Gutenberg are treated as read-only.
#### Disk Image Formats ####
Disk image files can be "unadorned", meaning they're just a series of
blocks or sectors, or they can have fancy headers and compressed contents.
Block-oriented images are easy to deal with, as they're generally just
the blocks in sequential order. Sector-oriented images can be tricky,
because the sector order is subject to interpretation.
The most common sector orderings are "DOS" and "ProDOS". If you read
a 5.25" disk sequentially from DOS, starting with track 0 sector 0, and
wrote the contents to a file, you would end up with a DOS-ordered image.
If you read that same disk from ProDOS, starting with block 0, you would
end up with a ProDOS-ordered image. The difference occurs because ProDOS
blocks are 512 bytes -- two DOS sectors -- and ProDOS interleaves the
sectors as an optimization. While you might expect ProDOS block 0 to be
comprised of DOS T0 S0 and T0 S1, it's actually T0 S0 and T0 S2.
There have been various attempts at defining storage formats for disk
images over the years. The library handles most of them.
- Unadorned block/sector files (.po, .do, .d13, .raw, .hdv, .iso, most .dc6).
The image file holds data from the file and nothing else.
- Unadorned nibble-format files (.nib, .nb2). Some 5.25" disks were a
bit "creative" with their physical format, so some image formats allow
for extraction of the data as bits directly off the disk. Such formats
are unusual in that it's possible to have "bad sectors" in a disk image.
- Universal Disk Image (.2mg, .2img). The format was designed specifically
for Apple II emulators. It supports DOS-order, ProDOS-order, and nibble
images.
- Copy ][ Plus (.img). Certain versions of the Copy ][ Plus Apple II
utility had the ability to create disk images. The format was simple
unadorned sectors, but with a twist: the sectors were in physical order,
which is different from DOS and ProDOS.
- Dalton's Disk Disintegrator (.ddd). DDD was developed as an alternative
to "disk slicer" programs for uploading disk images to BBS systems.
Because it used compression, 5.25" disk images could be held on 5.25" disks.
DOS and ProDOS versions of the program were available. A fancier version,
called DDD Deluxe, was developed later.
- ShrinkIt (.shk, .sdk). ShrinkIt was initially developed as an improved
version of DDD, incorporating LZW compression and CRC error checking. It
grew into a general-purpose file archiver.
- DiskCopy 4.2 (.dsk). The format used by the Mac DiskCopy program for
making images of 800K floppies. Includes a checksum, but no compression.
- TrackStar (.app). The TrackStar was essentially an Apple II built
into a PC ISA expansion card. The 5.25" disk images use a variable-length
nibble format with 40 tracks.
- Formatted Disk Image (.fdi). Files generated by the Disk2FDI program.
These contain raw signal data obtained from a PC floppy drive. It was
long said that reading an Apple II floppy from a PC drive was impossible;
this program proved otherwise.
- Sim //e HDV (.hdv). Used for images of ProDOS drives for use with a
specific emulator, this is just a ProDOS block image with a short header.
All of these, with the exception of DDD Deluxe, are fully supported by
the DiskImg library.
#### Meta-Formats ####
As disks got larger, older filesystems could no longer use all of the
available space with a single volume. Some "meta-formats" were developed.
These allow a single disk image to hold multiple filesystems.
- UNIDOS / AmDOS / OzDOS. These allow two 400K DOS 3.3 volumes to
exist on a single 800K disk.
- ProSel Uni-DOS / DOS Master. These allow multiple 140K DOS 3.3
volumes to reside on a ProDOS volume. You can, on a single 800K disk,
provide a small ProDOS launcher that will boot into DOS 3.3 and launch a
specific file from one of five 140K DOS volumes.
- Macintosh-style disk partitioning. This was widely used on hard
drives, CD-ROMs, and other large disks.
- CFFA-style disk partitioning. The CFFA card for the Apple II allows
the use of Compact Flash cards for storage. There is no partitioning
done on the CF card itself -- the CFFA card has a fixed arrangement.
- ///SHH Systeme MicroDrive partitioning. Another disk partition format,
developed for use with the MicroDrive. Allows up to 16 partitions on
an IDE hard drive.
- Parsons Engineering FocusDrive partitioning. Developed for use with
the FocusDrive. Allows up to 30 partitions.
It's also possible to create a "hybrid" DOS / ProDOS disk, because the
essential file catalog areas don't overlap. (See HYBRID.CREATE on the
Beagle Bros "Extra K" disk.)
All of these are fully supported by the DiskImg library.
#### Wrapper Formats ####
While 800K may not seem like a lot these days, it used to be a decent
chunk of data, so it was common for disk images that didn't use compression
to be compressed with another program. The most common are ZIP and gzip.
#### Apple II File Formats ####
For filesystems like ProDOS, the body of the file contains just the file
contents. The directory entry holds the file type, auxiliary type, and
the file's length in bytes.
For DOS 3.2/3.3, the first few bytes of the file specified details like
the load address, and for text files there is no reliable indication of
the file length. The DiskImg library does what it can to conceal
filesystem quirks.
DiskImg Library Classes
-----------------------
The library provides several C++ classes. This section gives an overview
of what each does.
The basic classes are defined in DiskImg.h. Specialized sub-classes are
declared in DiskImgDetail.h.
The API is a bit more complicated than it could be, and there's a bit of
redundancy in the filesystem code. Some of this is due to the way the
library evolved -- disk image access was originally intended to be read-only,
and that didn't change until CiderPress 2.0.
#### DiskImg ####
The `DiskImg` class represents a single disk image. It may have sub-images,
each of which is its own instance of DiskImg. Operations on a DiskImg
are similar to what you'd expect from a device driver, e.g. reading and
writing individual blocks.
The DiskImg has several characteristics:
- OuterFormat. This is the "outer wrapper", which may be ZIP (.zip) or
gzip (.gz). The wrapper is handled transparently -- the contents are
uncompressed when opened, and recompressed if necessary when closed.
- FileFormat. The disk image file's format, once the outer wrapper has
been stripped away. "Unadorned", 2MG, and ShrinkIt are examples.
- PhysicalFormat. Identifies whether the data is "raw" or "cooked", i.e.
if it's a series of blocks/sectors or raw nibbles.
- SectorOrder. ProDOS, DOS, Physical.
- FSFormat. What type of filesystem is in this image. This includes
common formats, like DOS 3.3 and ProDOS, as well as meta-formats like
UNIDOS and Macintosh partition.
#### DiskFS ####
A `DiskFS` instance is typically paired with a DiskImg. It represents
the filesystem, operating at a level roughly equivalent to a GS/OS
File System Translator (FST).
Using DiskFS, you can read, write, and rename files, format disks,
check how much free space is available, and search for sub-volumes.
#### A2File ####
One instance of `A2File` represents one file on disk. The object holds
the filename and attributes, and provides a call to open the file.
#### A2FileDescr ####
This is essentially a file descriptor for an Apple II file. You can
read or write data.
To make the implementation easier, files must be written with a single
write call, and only one fork of a forked file may be open.

308
diskimg/SCSIDefs.h Normal file
View File

@ -0,0 +1,308 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Definitions for SCSI (Small Computer System Interface).
*
* These structures and defines are passed to the SCSI driver, so they work
* equally well for ASPI and SPTI
*
* Consult the SCSI-2 and MMC-2 specifications for details.
*/
#ifndef DISKIMG_SCSIDEFS_H
#define DISKIMG_SCSIDEFS_H
/*
* SCSI-2 operation codes.
*/
typedef enum {
kScsiOpTestUnitReady = 0x00,
kScsiOpRezeroUnit = 0x01,
kScsiOpRewind = 0x01,
kScsiOpRequestBlockAddr = 0x02,
kScsiOpRequestSense = 0x03,
kScsiOpFormatUnit = 0x04,
kScsiOpReadBlockLimits = 0x05,
kScsiOpReassignBlocks = 0x07,
kScsiOpRead6 = 0x08,
kScsiOpReceive = 0x08,
kScsiOpWrite6 = 0x0a,
kScsiOpPrint = 0x0a,
kScsiOpSend = 0x0a,
kScsiOpSeek6 = 0x0b,
kScsiOpTrackSelect = 0x0b,
kScsiOpSlewPrint = 0x0b,
kScsiOpSeekBlock = 0x0c,
kScsiOpPartition = 0x0d,
kScsiOpReadReverse = 0x0f,
kScsiOpWriteFilemarks = 0x10,
kScsiOpFlushBuffer = 0x10,
kScsiOpSpace = 0x11,
kScsiOpInquiry = 0x12,
kScsiOpVerify6 = 0x13,
kScsiOpRecoverBufferedData = 0x14,
kScsiOpModeSelect = 0x15,
kScsiOpReserveUnit = 0x16,
kScsiOpReleaseUnit = 0x17,
kScsiOpCopy = 0x18,
kScsiOpErase = 0x19,
kScsiOpModeSense = 0x1a,
kScsiOpStartStopUnit = 0x1b,
kScsiOpStopPrint = 0x1b,
kScsiOpLoadUnload = 0x1b,
kScsiOpReceiveDiagnosticResults = 0x1c,
kScsiOpSendDiagnostic = 0x1d,
kScsiOpMediumRemoval = 0x1e,
kScsiOpReadFormattedCapacity = 0x23,
kScsiOpReadCapacity = 0x25,
kScsiOpRead = 0x28, // READ(10)
kScsiOpWrite = 0x2a, // WRITE(10)
kScsiOpSeek = 0x2b,
kScsiOpLocate = 0x2b,
kScsiOpPositionToElement = 0x2b,
kScsiOpWriteVerify = 0x2e,
kScsiOpVerify = 0x2f, // VERIFY(10)
kScsiOpSearchDataHigh = 0x30,
kScsiOpSearchDataEqual = 0x31,
kScsiOpSearchDataLow = 0x32,
kScsiOpSetLimits = 0x33,
kScsiOpReadPosition = 0x34,
kScsiOpSynchronizeCache = 0x35,
kScsiOpCompare = 0x39,
kScsiOpCopyAndVerify = 0x3a,
kScsiOpWriteBuffer = 0x3b,
kScsiOpReadBuffer = 0x3c,
kScsiOpChangeDefinition = 0x40,
kScsiOpReadSubChannel = 0x42,
kScsiOpReadTOC = 0x43, // READ TOC/PMA/ATIP
kScsiOpReadHeader = 0x44,
kScsiOpPlayAudio = 0x45,
kScsiOpPlayAudioMSF = 0x47,
kScsiOpPlayTrackIndex = 0x48,
kScsiOpPlayTrackRelative = 0x49,
kScsiOpPauseResume = 0x4b,
kScsiOpLogSelect = 0x4c,
kScsiOpLogSense = 0x4c,
kScsiOpStopPlayScan = 0x4e,
kScsiOpReadDiscInformation = 0x51,
kScsiOpReadTrackInformation = 0x52,
kScsiOpSendOPCInformation = 0x54,
kScsiOpModeSelect10 = 0x55,
kScsiOpRepairTrack = 0x58,
kScsiOpModeSense10 = 0x5a,
kScsiOpReportLuns = 0xa0,
kScsiOpVerify12 = 0xa2,
kScsiOpSendKey = 0xa3,
kScsiOpReportKey = 0xa4,
kScsiOpMoveMedium = 0xa5,
kScsiOpLoadUnloadSlot = 0xa6,
kScsiOpExchangeMedium = 0xa6,
kScsiOpSetReadAhead = 0xa7,
kScsiOpReadDVDStructure = 0xad,
kScsiOpWriteAndVerify = 0xae,
kScsiOpRequestVolElement = 0xb5,
kScsiOpSendVolumeTag = 0xb6,
kScsiOpReadElementStatus = 0xb8,
kScsiOpReadCDMSF = 0xb9,
kScsiOpScanCD = 0xba,
kScsiOpSetCDSpeed = 0xbb,
kScsiOpPlayCD = 0xbc,
kScsiOpMechanismStatus = 0xbd,
kScsiOpReadCD = 0xbe,
kScsiOpInitElementRange = 0xe7,
} SCSIOperationCode;
/*
* SCSI status codes.
*/
typedef enum {
kScsiStatGood = 0x00,
kScsiStatCheckCondition = 0x02,
kScsiStatConditionMet = 0x04,
kScsiStatBusy = 0x08,
kScsiStatIntermediate = 0x10,
kScsiStatIntermediateCondMet = 0x14,
kScsiStatReservationConflict = 0x18,
kScsiStatCommandTerminated = 0x22,
kScsiStatQueueFull = 0x28,
} SCSIStatus;
/*
* SCSI sense codes.
*/
typedef enum {
kScsiSenseNoSense = 0x00,
kScsiSenseRecoveredError = 0x01,
kScsiSenseNotReady = 0x02,
kScsiSenseMediumError = 0x03,
kScsiSenseHardwareError = 0x04,
kScsiSenseIllegalRequest = 0x05,
kScsiSenseUnitAttention = 0x06,
kScsiSenseDataProtect = 0x07,
kScsiSenseBlankCheck = 0x08,
kScsiSenseUnqiue = 0x09,
kScsiSenseCopyAborted = 0x0a,
kScsiSenseAbortedCommand = 0x0b,
kScsiSenseEqual = 0x0c,
kScsiSenseVolOverflow = 0x0d,
kScsiSenseMiscompare = 0x0e,
kScsiSenseReserved = 0x0f,
} SCSISenseCode;
/*
* SCSI additional sense codes.
*/
typedef enum {
kScsiAdSenseNoSense = 0x00,
kScsiAdSenseInvalidMedia = 0x30,
kScsiAdSenseNoMediaInDevice = 0x3a,
} SCSIAdSenseCode;
/*
* SCSI device types.
*/
typedef enum {
kScsiDevTypeDASD = 0x00, // Disk Device
kScsiDevTypeSEQD = 0x01, // Tape Device
kScsiDevTypePRNT = 0x02, // Printer
kScsiDevTypePROC = 0x03, // Processor
kScsiDevTypeWORM = 0x04, // Write-once read-multiple
kScsiDevTypeCDROM = 0x05, // CD-ROM device
kScsiDevTypeSCAN = 0x06, // Scanner device
kScsiDevTypeOPTI = 0x07, // Optical memory device
kScsiDevTypeJUKE = 0x08, // Medium Changer device
kScsiDevTypeCOMM = 0x09, // Communications device
kScsiDevTypeRESL = 0x0a, // Reserved (low)
kScsiDevTypeRESH = 0x1e, // Reserved (high)
kScsiDevTypeUNKNOWN = 0x1f, // Unknown or no device type
} SCSIDeviceType;
/*
* Generic 6-byte request block.
*/
typedef struct CDB6 {
unsigned char operationCode;
unsigned char immediate : 1;
unsigned char commandUniqueBits : 4;
unsigned char logicalUnitNumber : 3;
unsigned char commandUniqueBytes[3];
unsigned char link : 1;
unsigned char flag : 1;
unsigned char reserved : 4;
unsigned char vendorUnique : 2;
} CDB6;
/*
* Generic 10-byte request block.
*
* Use for READ(10), READ CAPACITY.
*/
typedef struct CDB10 {
unsigned char operationCode;
unsigned char relativeAddress : 1;
unsigned char reserved1 : 2;
unsigned char forceUnitAccess : 1;
unsigned char disablePageOut : 1;
unsigned char logicalUnitNumber : 3;
unsigned char logicalBlockAddr0; // MSB
unsigned char logicalBlockAddr1;
unsigned char logicalBlockAddr2;
unsigned char logicalBlockAddr3; // LSB
unsigned char reserved2;
unsigned char transferLength0; // MSB
unsigned char transferLength1; // LSB
unsigned char control;
} CDB10;
/*
* INQUIRY request block.
*/
typedef struct CDB6Inquiry {
unsigned char operationCode;
unsigned char EVPD : 1;
unsigned char reserved1 : 4;
unsigned char logicalUnitNumber : 3;
unsigned char pageCode;
unsigned char reserved2;
unsigned char allocationLength;
unsigned char control;
} CDB6Inquiry;
/*
* Sense data (ASPI SenseArea).
*/
typedef struct CDB_SenseData {
unsigned char errorCode:7;
unsigned char valid:1;
unsigned char segmentNumber;
unsigned char senseKey:4;
unsigned char reserved:1;
unsigned char incorrectLength:1;
unsigned char endOfMedia:1;
unsigned char fileMark:1;
unsigned char information[4];
unsigned char additionalSenseLength;
unsigned char commandSpecificInformation[4];
unsigned char additionalSenseCode; // ASC
unsigned char additionalSenseCodeQualifier; // ASCQ
unsigned char fieldReplaceableUnitCode;
unsigned char senseKeySpecific[3];
} CDB_SenseData;
/*
* Default sense buffer size.
*/
#define kSenseBufferSize 18
//#define INQUIRYDATABUFFERSIZE 36
/*
* Result from INQUIRY.
*/
typedef struct CDB_InquiryData {
unsigned char deviceType : 5;
unsigned char deviceTypeQualifier : 3;
unsigned char deviceTypeModifier : 7;
unsigned char removableMedia : 1;
unsigned char versions;
unsigned char responseDataFormat : 4;
unsigned char reserved1 : 2;
unsigned char trmIOP : 1;
unsigned char AENC : 1;
unsigned char additionalLength;
unsigned char reserved2[2];
unsigned char softReset : 1;
unsigned char commandQueue : 1;
unsigned char reserved3 : 1;
unsigned char linkedCommands : 1;
unsigned char synchronous : 1;
unsigned char wide16Bit : 1;
unsigned char wide32Bit : 1;
unsigned char relativeAddressing : 1;
unsigned char vendorId[8];
unsigned char productId[16];
unsigned char productRevisionLevel[4];
unsigned char vendorSpecific[20];
unsigned char reserved4[40];
} CDB_InquiryData;
/*
* Result from READ CAPACITY.
*/
typedef struct CDB_ReadCapacityData {
unsigned char logicalBlockAddr0; // MSB
unsigned char logicalBlockAddr1;
unsigned char logicalBlockAddr2;
unsigned char logicalBlockAddr3; // LSB
unsigned char bytesPerBlock0; // MSB
unsigned char bytesPerBlock1;
unsigned char bytesPerBlock2;
unsigned char bytesPerBlock3; // LSB
} CDB_ReadCapacityData;
#endif /*DISKIMG_SCSIDEFS_H*/

137
diskimg/SPTI.cpp Normal file
View File

@ -0,0 +1,137 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of some SPTI functions.
*/
#include "StdAfx.h"
#ifdef _WIN32
#include "DiskImgPriv.h"
#include "SCSIDefs.h"
#include "CP_ntddscsi.h"
#include "SPTI.h"
/*
* Get the capacity of the device.
*
* Returns the LBA of the last valid block and the device's block size.
*/
/*static*/ DIError SPTI::GetDeviceCapacity(HANDLE handle, uint32_t* pLastBlock,
uint32_t* pBlockSize)
{
SCSI_PASS_THROUGH_DIRECT sptd;
uint32_t lba, blockLen;
CDB_ReadCapacityData dataBuf;
DWORD cb;
BOOL status;
assert(sizeof(dataBuf) == 8); // READ CAPACITY returns two longs
memset(&sptd, 0, sizeof(sptd));
sptd.Length = sizeof(sptd);
sptd.PathId = 0; // SCSI card ID filled in by ioctl
sptd.TargetId = 0; // SCSI target ID filled in by ioctl
sptd.Lun = 0; // SCSI lun ID filled in by ioctl
sptd.CdbLength = 10; // CDB size is 10 for READ CAPACITY
sptd.SenseInfoLength = 0; // don't return any sense data
sptd.DataIn = SCSI_IOCTL_DATA_IN; // will be data from drive
sptd.DataTransferLength = sizeof(dataBuf);
sptd.TimeOutValue = 10; // SCSI timeout value, in seconds
sptd.DataBuffer = (PVOID) &dataBuf;
sptd.SenseInfoOffset = 0; // offset to request-sense buffer
CDB10* pCdb = (CDB10*) &sptd.Cdb;
pCdb->operationCode = kScsiOpReadCapacity;
// rest of CDB is zero
status = ::DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptd, sizeof(sptd), NULL, 0, &cb, NULL);
if (!status) {
DWORD lastError = ::GetLastError();
LOGE("DeviceIoControl(SCSI READ CAPACITY) failed, err=%ld",
::GetLastError());
if (lastError == ERROR_IO_DEVICE) // no disc in drive
return kDIErrDeviceNotReady;
else
return kDIErrSPTIFailure;
}
lba = (uint32_t) dataBuf.logicalBlockAddr0 << 24 |
(uint32_t) dataBuf.logicalBlockAddr1 << 16 |
(uint32_t) dataBuf.logicalBlockAddr2 << 8 |
(uint32_t) dataBuf.logicalBlockAddr3;
blockLen = (uint32_t) dataBuf.bytesPerBlock0 << 24 |
(uint32_t) dataBuf.bytesPerBlock1 << 16 |
(uint32_t) dataBuf.bytesPerBlock2 << 8 |
(uint32_t) dataBuf.bytesPerBlock3;
*pLastBlock = lba;
*pBlockSize = blockLen;
return kDIErrNone;
}
/*
* Read one or more blocks from the specified SCSI device.
*
* "buf" must be able to hold (numBlocks * blockSize) bytes.
*/
/*static*/ DIError SPTI::ReadBlocks(HANDLE handle, long startBlock,
short numBlocks, long blockSize, void* buf)
{
SCSI_PASS_THROUGH_DIRECT sptd;
DWORD cb;
BOOL status;
assert(startBlock >= 0);
assert(numBlocks > 0);
assert(buf != NULL);
LOGD(" SPTI phys read block (%ld) %d", startBlock, numBlocks);
memset(&sptd, 0, sizeof(sptd));
sptd.Length = sizeof(sptd); // size of struct (+ request-sense buffer)
sptd.ScsiStatus = 0;
sptd.PathId = 0; // SCSI card ID filled in by ioctl
sptd.TargetId = 0; // SCSI target ID filled in by ioctl
sptd.Lun = 0; // SCSI lun ID filled in by ioctl
sptd.CdbLength = 10; // CDB size is 10 for READ CAPACITY
sptd.SenseInfoLength = 0; // don't return any sense data
sptd.DataIn = SCSI_IOCTL_DATA_IN; // will be data from drive
sptd.DataTransferLength = blockSize * numBlocks;
sptd.TimeOutValue = 10; // SCSI timeout value (in seconds)
sptd.DataBuffer = (PVOID) buf;
sptd.SenseInfoOffset = 0; // offset from start of struct to request-sense
CDB10* pCdb = (CDB10*) &sptd.Cdb;
pCdb->operationCode = kScsiOpRead;
pCdb->logicalBlockAddr0 = (uint8_t) (startBlock >> 24); // MSB
pCdb->logicalBlockAddr1 = (uint8_t) (startBlock >> 16);
pCdb->logicalBlockAddr2 = (uint8_t) (startBlock >> 8);
pCdb->logicalBlockAddr3 = (uint8_t) startBlock; // LSB
pCdb->transferLength0 = (uint8_t) (numBlocks >> 8); // MSB
pCdb->transferLength1 = (uint8_t) numBlocks; // LSB
status = ::DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptd, sizeof(sptd), NULL, 0, &cb, NULL);
if (!status) {
LOGE("DeviceIoControl(SCSI READ(10)) failed, err=%ld",
::GetLastError());
return kDIErrReadFailed; // close enough
}
if (sptd.ScsiStatus != 0) {
LOGE("SCSI READ(10) failed, status=%d", sptd.ScsiStatus);
return kDIErrReadFailed;
}
return kDIErrNone;
}
#endif /*_WIN32*/

40
diskimg/SPTI.h Normal file
View File

@ -0,0 +1,40 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Declarations for the Win32 SCSI Pass-Through Interface.
*/
#ifndef DISKIMG_SPTI_H
#define DISKIMG_SPTI_H
#ifdef _WIN32
namespace DiskImgLib {
/*
* This is currently implemented as a set of static functions. Do not
* instantiate the class.
*/
class DISKIMG_API SPTI {
public:
// Read blocks from the device.
static DIError ReadBlocks(HANDLE handle, long startBlock, short numBlocks,
long blockSize, void* buf);
// Get the capacity, expressed as the highest-available LBA and the device
// block size.
static DIError GetDeviceCapacity(HANDLE handle, uint32_t* pLastBlock,
uint32_t* pBlockSize);
private:
SPTI(void) {}
~SPTI(void) {}
};
} // namespace DiskImgLib
#endif /*_WIN32*/
#endif /*DISKIMG_SPTI_H*/

13
diskimg/StdAfx.cpp Normal file
View File

@ -0,0 +1,13 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
// stdafx.cpp : source file that includes just the standard includes
// diskimg.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "StdAfx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

75
diskimg/StdAfx.h Normal file
View File

@ -0,0 +1,75 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#ifndef _WIN32
/* UNIX includes */
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <fcntl.h>
#include <ctype.h>
#define O_BINARY 0
#define HAVE_VSNPRINTF
#define HAVE_FSEEKO
#define HAVE_FTRUNCATE
// gcc wants special compile options; just ignore this for now
#define override
#else /*_WIN32*/
#if !defined(AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_)
#define AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define HAVE_WINDOWS_CDROM // enable CD-ROM access under Windows
#define HAVE_CHSIZE
// Insert your headers here
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include "../app/targetver.h"
#include <windows.h>
#include <atlstr.h>
#include <io.h>
#include <fcntl.h>
#ifdef HAVE_WINDOWS_CDROM
# include <winioctl.h>
#endif
#ifndef _SSIZE_T_DEFINED
typedef unsigned int ssize_t;
#define _SSIZE_T_DEFINED
#endif
#define HAVE__VSNPRINTF
#define strcasecmp stricmp
#define snprintf _snprintf
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_)
#endif /*_WIN32*/

567
diskimg/TwoImg.cpp Normal file
View File

@ -0,0 +1,567 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Support for 2MG/2IMG wrapper files.
*
* This needs to be directly accessible from external applications, so try not
* to hook into private DiskImg state.
*/
#include "StdAfx.h"
#include "TwoImg.h"
#include "DiskImgPriv.h"
///*static*/ const char* TwoImgHeader::kMagic = "2IMG"; // file magic #
///*static*/ const char* TwoImgHeader::kCreator = "CdrP"; // our "creator" ID
/*
* Initialize a header to default values, using the supplied image length
* where appropriate.
*
* Sets up a header for a 2MG file with the disk data and nothing else.
*
* Returns 0 on success, -1 if one of the arguments was bad.
*/
int TwoImgHeader::InitHeader(int imageFormat, uint32_t imageSize,
uint32_t imageBlockCount)
{
if (imageSize == 0)
return -1;
if (imageFormat < (int) kImageFormatDOS || imageFormat > (int) kImageFormatNibble)
return -1;
if (imageFormat != kImageFormatNibble &&
imageSize != imageBlockCount * 512)
{
LOGW("2MG InitHeader: bad sizes %d %u %u", imageFormat,
imageSize, imageBlockCount);
return -1;
}
assert(fComment == NULL);
//memcpy(fMagic, kMagic, 4);
//memcpy(fCreator, kCreator, 4);
fMagic = kMagic;
fCreator = kCreatorCiderPress;
fHeaderLen = kOurHeaderLen;
fVersion = kOurVersion;
fImageFormat = imageFormat;
fFlags = 0;
fNumBlocks = imageBlockCount;
fDataOffset = kOurHeaderLen;
fDataLen = imageSize;
fCmtOffset = 0;
fCmtLen = 0;
fCreatorOffset = 0;
fCreatorLen = 0;
fSpare[0] = fSpare[1] = fSpare[2] = fSpare[3] = 0;
return 0;
}
/*
* Get the DOS volume number.
*
* If not set, we currently return the initial value (-1), rather than the
* default volume number. For the way we currently make use of this, this
* makes the most sense.
*/
int16_t TwoImgHeader::GetDOSVolumeNum(void) const
{
assert(fFlags & kDOSVolumeSet);
return fDOSVolumeNum;
}
/*
* Set the DOS volume number.
*/
void TwoImgHeader::SetDOSVolumeNum(short dosVolumeNum)
{
assert(dosVolumeNum >= 0 && dosVolumeNum < 256);
fFlags |= dosVolumeNum;
fFlags |= kDOSVolumeSet;
}
/*
* Set the comment.
*/
void TwoImgHeader::SetComment(const char* comment)
{
delete[] fComment;
if (comment == NULL) {
fComment = NULL;
} else {
fComment = new char[strlen(comment)+1];
if (fComment != NULL)
strcpy(fComment, comment);
// else throw alloc failure
}
if (fComment == NULL) {
fCmtLen = 0;
fCmtOffset = 0;
if (fCreatorOffset > 0)
fCreatorOffset = fDataOffset + fDataLen;
} else {
fCmtLen = strlen(fComment);
fCmtOffset = fDataOffset + fDataLen;
if (fCreatorOffset > 0)
fCreatorOffset = fCmtOffset + fCmtLen;
}
}
/*
* Set the creator chunk.
*/
void TwoImgHeader::SetCreatorChunk(const void* chunk, long len)
{
assert(len >= 0);
delete[] fCreatorChunk;
if (chunk == NULL || len == 0) {
fCreatorChunk = NULL;
} else {
fCreatorChunk = new char[len];
if (fCreatorChunk != NULL)
memcpy(fCreatorChunk, chunk, len);
// else throw alloc failure
}
if (fCreatorChunk == NULL) {
fCreatorLen = 0;
fCreatorOffset = 0;
} else {
fCreatorLen = len;
if (fCmtOffset > 0)
fCreatorOffset = fCmtOffset + fCmtLen;
else
fCreatorOffset = fDataOffset + fDataLen;
}
}
/*
* Read the header from a 2IMG file. Pass in "totalLength" as a sanity check.
*
* THOUGHT: provide a simple GenericFD conversion for FILE*, and then just
* call the GenericFD version of ReadHeader.
*
* Returns 0 on success, nonzero on error or invalid header.
*/
int TwoImgHeader::ReadHeader(FILE* fp, uint32_t totalLength)
{
uint8_t buf[kOurHeaderLen];
fread(buf, kOurHeaderLen, 1, fp);
if (ferror(fp))
return errno ? errno : -1;
if (UnpackHeader(buf, totalLength) != 0)
return -1;
/*
* Extract the comment, if any.
*/
if (fCmtOffset > 0 && fCmtLen > 0) {
if (GetChunk(fp, fCmtOffset - kOurHeaderLen, fCmtLen,
(void**) &fComment) != 0)
{
LOGI("Throwing comment away");
fCmtLen = 0;
fCmtOffset = 0;
} else {
LOGI("Got comment: '%s'", fComment);
}
}
/*
* Extract the creator chunk, if any.
*/
if (fCreatorOffset > 0 && fCreatorLen > 0) {
if (GetChunk(fp, fCreatorOffset - kOurHeaderLen, fCreatorLen,
(void**) &fCreatorChunk) != 0)
{
LOGI("Throwing creator chunk away");
fCreatorLen = 0;
fCreatorOffset = 0;
} else {
//LOGI("Got creator chunk: '%s'", fCreatorChunk);
}
}
return 0;
}
/*
* Read the header from a 2IMG file. Pass in "totalLength" as a sanity check.
*
* Returns 0 on success, nonzero on error or invalid header.
*/
int TwoImgHeader::ReadHeader(GenericFD* pGFD, uint32_t totalLength)
{
DIError dierr;
uint8_t buf[kOurHeaderLen];
dierr = pGFD->Read(buf, kOurHeaderLen);
if (dierr != kDIErrNone)
return -1;
if (UnpackHeader(buf, totalLength) != 0)
return -1;
/*
* Extract the comment, if any.
*/
if (fCmtOffset > 0 && fCmtLen > 0) {
if (GetChunk(pGFD, fCmtOffset - kOurHeaderLen, fCmtLen,
(void**) &fComment) != 0)
{
LOGI("Throwing comment away");
fCmtLen = 0;
fCmtOffset = 0;
} else {
LOGI("Got comment: '%s'", fComment);
}
}
/*
* Extract the creator chunk, if any.
*/
if (fCreatorOffset > 0 && fCreatorLen > 0) {
if (GetChunk(pGFD, fCreatorOffset - kOurHeaderLen, fCreatorLen,
(void**) &fCreatorChunk) != 0)
{
LOGI("Throwing creator chunk away");
fCreatorLen = 0;
fCreatorOffset = 0;
} else {
//LOGI("Got creator chunk: '%s'", fCreatorChunk);
}
}
return 0;
}
/*
* Grab a chunk of data from a relative offset.
*/
int TwoImgHeader::GetChunk(GenericFD* pGFD, di_off_t relOffset, long len,
void** pBuf)
{
DIError dierr;
di_off_t curPos;
/* remember current offset */
curPos = pGFD->Tell();
/* seek out to chunk and grab it */
dierr = pGFD->Seek(relOffset, kSeekCur);
if (dierr != kDIErrNone) {
LOGI("2MG seek to chunk failed");
return -1;
}
assert(*pBuf == NULL);
*pBuf = new char[len+1]; // one extra, for null termination
dierr = pGFD->Read(*pBuf, len);
if (dierr != kDIErrNone) {
LOGI("2MG chunk read failed");
delete[] (char*) (*pBuf);
*pBuf = NULL;
(void) pGFD->Seek(curPos, kSeekSet);
return -1;
}
/* null-terminate, in case this was a string */
((char*) *pBuf)[len] = '\0';
/* seek back to where we were */
(void) pGFD->Seek(curPos, kSeekSet);
return 0;
}
/*
* Grab a chunk of data from a relative offset.
*/
int TwoImgHeader::GetChunk(FILE* fp, di_off_t relOffset, long len,
void** pBuf)
{
long curPos;
int count;
/* remember current offset */
curPos = ftell(fp);
LOGI("Current offset=%ld", curPos);
/* seek out to chunk and grab it */
if (fseek(fp, (long) relOffset, SEEK_CUR) == -1) {
LOGI("2MG seek to chunk failed");
return errno ? errno : -1;;
}
assert(*pBuf == NULL);
*pBuf = new char[len+1]; // one extra, for null termination
count = fread(*pBuf, len, 1, fp);
if (!count || ferror(fp) || feof(fp)) {
LOGI("2MG chunk read failed");
delete[] (char*) (*pBuf);
*pBuf = NULL;
(void) fseek(fp, curPos, SEEK_SET);
clearerr(fp);
return errno ? errno : -1;;
}
/* null-terminate, in case this was a string */
((char*) *pBuf)[len] = '\0';
/* seek back to where we were */
(void) fseek(fp, curPos, SEEK_SET);
return 0;
}
/*
* Unpack the 64-byte 2MG header.
*
* Performs some sanity checks. Returns 0 on success, -1 on failure.
*/
int TwoImgHeader::UnpackHeader(const uint8_t* buf, uint32_t totalLength)
{
fMagic = GetLongBE(&buf[0x00]);
fCreator = GetLongBE(&buf[0x04]);
fHeaderLen = GetShortLE(&buf[0x08]);
fVersion = GetShortLE(&buf[0x0a]);
fImageFormat = GetLongLE(&buf[0x0c]);
fFlags = GetLongLE(&buf[0x10]);
fNumBlocks = GetLongLE(&buf[0x14]);
fDataOffset = GetLongLE(&buf[0x18]);
fDataLen = GetLongLE(&buf[0x1c]);
fCmtOffset = GetLongLE(&buf[0x20]);
fCmtLen = GetLongLE(&buf[0x24]);
fCreatorOffset = GetLongLE(&buf[0x28]);
fCreatorLen = GetLongLE(&buf[0x2c]);
fSpare[0] = GetLongLE(&buf[0x30]);
fSpare[1] = GetLongLE(&buf[0x34]);
fSpare[2] = GetLongLE(&buf[0x38]);
fSpare[3] = GetLongLE(&buf[0x3c]);
fMagicStr[0] = (char) (fMagic >> 24);
fMagicStr[1] = (char) (fMagic >> 16);
fMagicStr[2] = (char) (fMagic >> 8);
fMagicStr[3] = (char) fMagic;
fMagicStr[4] = '\0';
fCreatorStr[0] = (char) (fCreator >> 24);
fCreatorStr[1] = (char) (fCreator >> 16);
fCreatorStr[2] = (char) (fCreator >> 8);
fCreatorStr[3] = (char) fCreator;
fCreatorStr[4] = '\0';
if (fMagic != kMagic) {
LOGI("Magic number does not match 2IMG");
return -1;
}
if (fVersion > 1) {
LOGW("ERROR: unsupported version=%d", fVersion);
return -1; // bad header until I hear otherwise
}
if (fFlags & kDOSVolumeSet)
fDOSVolumeNum = fFlags & kDOSVolumeMask;
DumpHeader();
/* fix broken 'WOOF' images from Sweet-16 */
if (fCreator == kCreatorSweet16 && fDataLen == 0 &&
fImageFormat != kImageFormatNibble)
{
fDataLen = fNumBlocks * kBlockSize;
LOGI("NOTE: fixing zero dataLen in 'WOOF' image (set to %u)",
fDataLen);
}
/*
* Perform some sanity checks.
*/
if (fImageFormat != kImageFormatNibble &&
fNumBlocks * kBlockSize != fDataLen)
{
LOGW("numBlocks/dataLen mismatch (%u vs %u)",
fNumBlocks * kBlockSize, fDataLen);
return -1;
}
if (fDataLen + fDataOffset > totalLength) {
LOGW("Invalid dataLen/offset/fileLength (dl=%u, off=%u, tlen=%u)",
fDataLen, fDataOffset, totalLength);
return -1;
}
if (fImageFormat < kImageFormatDOS || fImageFormat > kImageFormatNibble) {
LOGW("Invalid image format %u", fImageFormat);
return -1;
}
if (fCmtOffset > 0 && fCmtOffset < fDataOffset + fDataLen) {
LOGW("2MG comment is inside the data section (off=%u, data end=%u)",
fCmtOffset, fDataOffset+fDataLen);
DebugBreak();
// ignore the comment
fCmtOffset = 0;
fCmtLen = 0;
}
if (fCreatorOffset > 0 && fCreatorLen > 0) {
uint32_t prevEnd = fDataOffset + fDataLen + fCmtLen;
if (fCreatorOffset < prevEnd) {
LOGW("2MG creator chunk is inside prev data (off=%u, data end=%u)",
fCreatorOffset, prevEnd);
DebugBreak();
// ignore the creator chunk
fCreatorOffset = 0;
fCreatorLen = 0;
}
}
return 0;
}
/*
* Write the header to a 2IMG file.
*
* Returns 0 on success, or an errno value on failure.
*/
int
TwoImgHeader::WriteHeader(FILE* fp) const
{
uint8_t buf[kOurHeaderLen];
PackHeader(buf);
if (fwrite(buf, kOurHeaderLen, 1, fp) != 1)
return errno ? errno : -1;
return 0;
}
/*
* Write the header to a 2IMG file.
*
* Returns 0 on success, or an errno value on failure.
*/
int
TwoImgHeader::WriteHeader(GenericFD* pGFD) const
{
uint8_t buf[kOurHeaderLen];
PackHeader(buf);
if (pGFD->Write(buf, kOurHeaderLen) != kDIErrNone)
return -1;
return 0;
}
/*
* Write the footer. File must be seeked to end of data chunk.
*/
int
TwoImgHeader::WriteFooter(FILE* fp) const
{
LOGI("Writing footer at offset=%ld", (long) ftell(fp));
if (fCmtLen) {
fwrite(fComment, fCmtLen, 1, fp);
}
if (fCreatorLen) {
fwrite(fCreatorChunk, fCreatorLen, 1, fp);
}
if (ferror(fp))
return errno ? errno : -1;
return 0;
}
/*
* Write the footer. File must be seeked to end of data chunk.
*/
int
TwoImgHeader::WriteFooter(GenericFD* pGFD) const
{
LOGI("Writing footer at offset=%ld", (long) pGFD->Tell());
if (fCmtLen) {
if (pGFD->Write(fComment, fCmtLen) != kDIErrNone)
return -1;
}
if (fCreatorLen) {
if (pGFD->Write(fCreatorChunk, fCreatorLen) != kDIErrNone)
return -1;
}
return 0;
}
/*
* Pack the header values into a 64-byte buffer.
*/
void
TwoImgHeader::PackHeader(uint8_t* buf) const
{
if (fCmtLen > 0 && fCmtOffset == 0) {
assert(false);
}
if (fCreatorLen > 0 && fCreatorOffset == 0) {
assert(false);
}
PutLongBE(&buf[0x00], fMagic);
PutLongBE(&buf[0x04], fCreator);
PutShortLE(&buf[0x08], fHeaderLen);
PutShortLE(&buf[0x0a], fVersion);
PutLongLE(&buf[0x0c], fImageFormat);
PutLongLE(&buf[0x10], fFlags);
PutLongLE(&buf[0x14], fNumBlocks);
PutLongLE(&buf[0x18], fDataOffset);
PutLongLE(&buf[0x1c], fDataLen);
PutLongLE(&buf[0x20], fCmtOffset);
PutLongLE(&buf[0x24], fCmtLen);
PutLongLE(&buf[0x28], fCreatorOffset);
PutLongLE(&buf[0x2c], fCreatorLen);
PutLongLE(&buf[0x30], fSpare[0]);
PutLongLE(&buf[0x34], fSpare[1]);
PutLongLE(&buf[0x38], fSpare[2]);
PutLongLE(&buf[0x3c], fSpare[3]);
}
/*
* Dump the contents of an ImgHeader.
*/
void
TwoImgHeader::DumpHeader(void) const
{
LOGI("--- header contents:");
LOGI("\tmagic = '%s' (0x%08x)", fMagicStr, fMagic);
LOGI("\tcreator = '%s' (0x%08x)", fCreatorStr, fCreator);
LOGI("\theaderLen = %u", fHeaderLen);
LOGI("\tversion = %u", fVersion);
LOGI("\timageFormat = %u", fImageFormat);
LOGI("\tflags = 0x%08x", fFlags);
LOGI("\t locked = %s",
(fFlags & kFlagLocked) ? "true" : "false");
LOGI("\t DOS volume = %s (%d)",
(fFlags & kDOSVolumeSet) ? "true" : "false",
fFlags & kDOSVolumeMask);
LOGI("\tnumBlocks = %u", fNumBlocks);
LOGI("\tdataOffset = %u", fDataOffset);
LOGI("\tdataLen = %u", fDataLen);
LOGI("\tcmtOffset = %u", fCmtOffset);
LOGI("\tcmtLen = %u", fCmtLen);
LOGI("\tcreatorOffset = %u", fCreatorOffset);
LOGI("\tcreatorLen = %u", fCreatorLen);
LOGI("---");
}

154
diskimg/TwoImg.h Normal file
View File

@ -0,0 +1,154 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Support for the "2MG"/"2IMG" disk image format.
*
* This gets its own header because CiderPress uses these definitions and
* functions directly.
*/
#ifndef DISKIMG_TWOIMG_H
#define DISKIMG_TWOIMG_H
#include "DiskImg.h"
namespace DiskImgLib {
/*
* 2IMG header definition (was on http://www.magnet.ch/emutech/Tech/,
* now on http://www.a2central.com/programming/filetypes/ftne00130.html
* as filetype $e0/$0130).
*
* Meaning of "flags":
* bit 31 : disk is "locked"; used by emulators as write-protect sticker.
* bit 8 : if set, bits 0-7 specify DOS 3.3 volume number
* bit 0-7: if bit 8 is set, use this as DOS volume; else use 254
*
* All values are stored little-endian.
*/
class DISKIMG_API TwoImgHeader {
public:
TwoImgHeader(void) :
fMagic(0),
fCreator(0),
fHeaderLen(0),
fVersion(0),
fImageFormat(0),
fFlags(0),
fNumBlocks(0),
fDataOffset(0),
fDataLen(0),
fCmtOffset(0),
fCmtLen(0),
fCreatorOffset(0),
fCreatorLen(0),
fSpare(),
fDOSVolumeNum(-1),
fMagicStr(),
fCreatorStr(),
fComment(NULL),
fCreatorChunk(NULL)
{}
virtual ~TwoImgHeader(void) {
delete[] fComment;
delete[] fCreatorChunk;
}
/*
* Header fields.
*/
//char fMagic[4];
//char fCreator[4];
uint32_t fMagic;
uint32_t fCreator;
uint16_t fHeaderLen;
uint16_t fVersion;
uint32_t fImageFormat;
uint32_t fFlags; // may include DOS volume num
uint32_t fNumBlocks; // 512-byte blocks
uint32_t fDataOffset;
uint32_t fDataLen;
uint32_t fCmtOffset;
uint32_t fCmtLen;
uint32_t fCreatorOffset;
uint32_t fCreatorLen;
uint32_t fSpare[4];
/*
* Related constants.
*/
enum {
// imageFormat
kImageFormatDOS = 0,
kImageFormatProDOS = 1,
kImageFormatNibble = 2,
// flags
kFlagLocked = (1L<<31),
kDOSVolumeSet = (1L<<8),
kDOSVolumeMask = (0xff),
kDefaultVolumeNum = 254,
// constants used when creating a new header
kOurHeaderLen = 64,
kOurVersion = 1,
kBlockSize = 512,
kMagic = 0x32494d47, // 2IMG
kCreatorCiderPress = 0x43647250, // CdrP
kCreatorSweet16 = 0x574f4f46, // WOOF
};
/*
* Basic functions.
*
* The read header function will read the comment, but the write
* header function will not. This is because the GenericFD functions
* don't allow seeking past the current EOF.
*
* ReadHeader/WriteHeader expect the file to be seeked to the initial
* offset. WriteFooter expects the file to be seeked just past the
* end of the data section. This is done in case the file has some
* sort of wrapper outside the 2MG header.
*/
int InitHeader(int imageFormat, uint32_t imageSize, uint32_t imageBlockCount);
int ReadHeader(FILE* fp, uint32_t totalLength);
int ReadHeader(GenericFD* pGFD, uint32_t totalLength);
int WriteHeader(FILE* fp) const;
int WriteHeader(GenericFD* pGFD) const;
int WriteFooter(FILE* fp) const;
int WriteFooter(GenericFD* pGFD) const;
void DumpHeader(void) const; // for debugging
/*
* Getters & setters.
*/
const char* GetMagicStr(void) const { return fMagicStr; }
const char* GetCreatorStr(void) const { return fCreatorStr; }
int16_t GetDOSVolumeNum(void) const;
void SetDOSVolumeNum(short dosVolumeNum);
const char* GetComment(void) const { return fComment; }
void SetComment(const char* comment);
const void* GetCreatorChunk(void) const { return fCreatorChunk; }
void SetCreatorChunk(const void* creatorBlock, long len);
private:
int UnpackHeader(const uint8_t* buf, uint32_t totalLength);
void PackHeader(uint8_t* buf) const;
int GetChunk(GenericFD* pGFD, di_off_t relOffset, long len, void** pBuf);
int GetChunk(FILE* fp, di_off_t relOffset, long len, void** pBuf);
int16_t fDOSVolumeNum; // 8-bit volume number, or -1
char fMagicStr[5];
char fCreatorStr[5];
char* fComment;
char* fCreatorChunk;
};
} // namespace DiskImgLib
#endif /*DISKIMG_TWOIMG_H*/

359
diskimg/UNIDOS.cpp Normal file
View File

@ -0,0 +1,359 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of DiskFSUNIDOS class.
*
* The "UNIDOS" filesystem doesn't actually hold files. Instead, it holds
* two 400K DOS 3.3 volumes on an 800K disk.
*
* We do have a test here for "wide" DOS 3.3, which is largely a clone of
* the standard DOS 3.3 test. The trick is that we have to adjust our
* detection to account for 32-sector tracks, and do so while the object
* is still in a state where it believes it has 16 sectors per track.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSUNIDOS
* ===========================================================================
*/
const int kExpectedNumBlocks = 1600;
const int kExpectedTracks = 50;
const int kExpectedSectors = 32;
const int kVTOCTrack = 17;
const int kVTOCSector = 0;
const int kSctSize = 256;
const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries
const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
const int kMaxTSIterations = 32;
const int kMaxCatalogIterations = 64;
/*
* Read a track/sector, adjusting for 32-sector disks being treated as
* if they were 16-sector.
*/
static DIError ReadTrackSectorAdjusted(DiskImg* pImg, int track, int sector,
int trackOffset, uint8_t* buf, DiskImg::SectorOrder imageOrder)
{
track += trackOffset;
track *= 2;
if (sector >= 16) {
track++;
sector -= 16;
}
return pImg->ReadTrackSectorSwapped(track, sector, buf, imageOrder,
DiskImg::kSectorOrderDOS);
}
/*
* Test for presence of 400K DOS 3.3 volumes.
*/
static DIError TestImageHalf(DiskImg* pImg, int trackOffset,
DiskImg::SectorOrder imageOrder, int* pGoodCount)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
int numTracks, numSectors;
int catTrack, catSect;
int foundGood = 0;
int iterations = 0;
*pGoodCount = 0;
dierr = ReadTrackSectorAdjusted(pImg, kVTOCTrack, kVTOCSector,
trackOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone)
goto bail;
catTrack = sctBuf[0x01];
catSect = sctBuf[0x02];
numTracks = sctBuf[0x34];
numSectors = sctBuf[0x35];
if (!(sctBuf[0x27] == kMaxTSPairs) ||
/*!(sctBuf[0x36] == 0 && sctBuf[0x37] == 1) ||*/ // bytes per sect
!(numTracks == kExpectedTracks) ||
!(numSectors == 32) ||
!(catTrack < numTracks && catSect < numSectors) ||
0)
{
LOGI(" UNI/Wide DOS header test failed");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
foundGood++; // score one for a valid-looking VTOC
/*
* Walk through the catalog track to try to figure out ordering.
*/
while (catTrack != 0 && catSect != 0 &&
iterations < DiskFSDOS33::kMaxCatalogSectors)
{
dierr = ReadTrackSectorAdjusted(pImg, catTrack, catSect,
trackOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone) {
dierr = kDIErrNone;
break; /* allow it if earlier stuff was okay */
}
if (catTrack == sctBuf[1] && catSect == sctBuf[2] +1)
foundGood++;
else if (catTrack == sctBuf[1] && catSect == sctBuf[2]) {
LOGI(" WideDOS detected self-reference on cat (%d,%d)",
catTrack, catSect);
break;
}
catTrack = sctBuf[1];
catSect = sctBuf[2];
iterations++; // watch for infinite loops
}
if (iterations >= DiskFSDOS33::kMaxCatalogSectors) {
dierr = kDIErrDirectoryLoop;
LOGI(" WideDOS directory links cause a loop");
goto bail;
}
LOGI(" WideDOS foundGood=%d off=%d swap=%d", foundGood,
trackOffset, imageOrder);
*pGoodCount = foundGood;
bail:
return dierr;
}
/*
* Test both of the DOS partitions.
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
int* pGoodCount)
{
DIError dierr;
int goodCount1, goodCount2;
*pGoodCount = 0;
LOGI(" UNIDOS checking first half (imageOrder=%d)", imageOrder);
dierr = TestImageHalf(pImg, 0, imageOrder, &goodCount1);
if (dierr != kDIErrNone)
return dierr;
LOGI(" UNIDOS checking second half (imageOrder=%d)", imageOrder);
dierr = TestImageHalf(pImg, kExpectedTracks, imageOrder, &goodCount2);
if (dierr != kDIErrNone)
return dierr;
if (goodCount1 > goodCount2)
*pGoodCount = goodCount1;
else
*pGoodCount = goodCount2;
return kDIErrNone;
}
/*
* Test to see if the image is a UNIDOS volume.
*/
/*static*/ DIError DiskFSUNIDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* only on 800K disks (at the least, insist on numTracks being even) */
if (pImg->GetNumBlocks() != kExpectedNumBlocks)
return kDIErrFilesystemNotFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
int bestCount = 0;
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
int goodCount = 0;
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
if (goodCount > bestCount) {
bestCount = goodCount;
bestOrder = ordering[i];
}
}
}
if (bestCount >= 4 ||
(leniency == kLeniencyVery && bestCount >= 2))
{
LOGI(" WideDOS test: bestCount=%d for order=%d", bestCount, bestOrder);
assert(bestOrder != DiskImg::kSectorOrderUnknown);
*pOrder = bestOrder;
*pFormat = DiskImg::kFormatUNIDOS;
return kDIErrNone;
}
LOGI(" UNIDOS didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Test to see if the image is a 'wide' (32-sector) DOS3.3 volume, i.e.
* half of a UNIDOS volume (usually found embedded in ProDOS).
*
* Trying all possible formats is important here, because the wrong value for
* swap can return a "good" value of 7 (much less than the expected 30, but
* above a threshold of reasonableness).
*/
/*static*/ DIError DiskFSUNIDOS::TestWideFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* only on 400K "disks" */
if (pImg->GetNumBlocks() != kExpectedNumBlocks/2) {
LOGI(" WideDOS ignoring volume (numBlocks=%ld)",
pImg->GetNumBlocks());
return kDIErrFilesystemNotFound;
}
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
int bestCount = 0;
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
int goodCount = 0;
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImageHalf(pImg, 0, ordering[i], &goodCount) == kDIErrNone) {
if (goodCount > bestCount) {
bestCount = goodCount;
bestOrder = ordering[i];
}
}
}
if (bestCount >= 4 ||
(leniency == kLeniencyVery && bestCount >= 2))
{
LOGI(" UNI/Wide test: bestCount=%d for order=%d", bestCount, bestOrder);
assert(bestOrder != DiskImg::kSectorOrderUnknown);
*pOrder = bestOrder;
*pFormat = DiskImg::kFormatDOS33;
// up to the caller to adjust numTracks/numSectPerTrack
return kDIErrNone;
}
LOGI(" UNI/Wide didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Set up our sub-volumes.
*/
DIError DiskFSUNIDOS::Initialize(void)
{
DIError dierr = kDIErrNone;
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = OpenSubVolume(0);
if (dierr != kDIErrNone)
return dierr;
dierr = OpenSubVolume(1);
if (dierr != kDIErrNone)
return dierr;
} else {
LOGI(" UNIDOS not scanning for sub-volumes");
}
SetVolumeUsageMap();
return kDIErrNone;
}
/*
* Open up one of the DOS 3.3 sub-volumes.
*/
DIError DiskFSUNIDOS::OpenSubVolume(int idx)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(fpImg, kExpectedTracks * idx, 0,
kExpectedTracks * kExpectedSectors);
if (dierr != kDIErrNone) {
LOGI(" UNISub: OpenImage(%d,0,%d) failed (err=%d)",
kExpectedTracks * idx, kExpectedTracks * kExpectedSectors, dierr);
goto bail;
}
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" UNISub: analysis failed (err=%d)", dierr);
goto bail;
}
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" UNISub: unable to identify filesystem");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* open a DiskFS for the sub-image */
LOGI(" UNISub %d succeeded!", idx);
pNewFS = pNewImg->OpenAppropriateDiskFS();
if (pNewFS == NULL) {
LOGI(" UNISub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* load the files from the sub-image */
dierr = pNewFS->Initialize(pNewImg, kInitFull);
if (dierr != kDIErrNone) {
LOGE(" UNISub: error %d reading list of files from disk", dierr);
goto bail;
}
/* if this really is DOS 3.3, override the "volume name" */
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
DiskFSDOS33* pDOS = (DiskFSDOS33*) pNewFS; /* eek, a downcast */
pDOS->SetDiskVolumeNum(idx+1);
}
/*
* Success, add it to the sub-volume list.
*/
AddSubVolumeToList(pNewImg, pNewFS);
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
}
return dierr;
}

269
diskimg/VolumeUsage.cpp Normal file
View File

@ -0,0 +1,269 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Support for the VolumeUsage sub-class in DiskFS.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* Initialize structures for a block-structured disk.
*/
DIError DiskFS::VolumeUsage::Create(long numBlocks)
{
if (numBlocks <= 0 || numBlocks > 32*1024*1024) // 16GB
return kDIErrInvalidArg;
fByBlocks = true;
fNumSectors = -1;
fTotalChunks = numBlocks;
fListSize = numBlocks;
fList = new unsigned char[fListSize];
if (fList == NULL)
return kDIErrMalloc;
memset(fList, 0, fListSize);
return kDIErrNone;
}
/*
* Initialize structures for a track/sector-structured disk.
*/
DIError DiskFS::VolumeUsage::Create(long numTracks, long numSectors)
{
long count = numTracks * numSectors;
if (numTracks <= 0 || count <= 0 || count > 32*1024*1024)
return kDIErrInvalidArg;
fByBlocks = false;
fNumSectors = numSectors;
fTotalChunks = count;
fListSize = count;
fList = new unsigned char[fListSize];
if (fList == NULL)
return kDIErrMalloc;
memset(fList, 0, fListSize);
return kDIErrNone;
}
/*
* Return the state of a particular chunk.
*/
DIError DiskFS::VolumeUsage::GetChunkState(long block, ChunkState* pState) const
{
if (!fByBlocks)
return kDIErrInvalidArg;
return GetChunkStateIdx(block, pState);
}
DIError DiskFS::VolumeUsage::GetChunkState(long track, long sector,
ChunkState* pState) const
{
if (fByBlocks)
return kDIErrInvalidArg;
if (track < 0 || sector < 0 || sector >= fNumSectors)
return kDIErrInvalidArg;
return GetChunkStateIdx(track * fNumSectors + sector, pState);
}
DIError DiskFS::VolumeUsage::GetChunkStateIdx(int idx, ChunkState* pState) const
{
if (fList == NULL || idx < 0 || idx >= fListSize) {
assert(false);
return kDIErrInvalidArg;
}
unsigned char val = fList[idx];
pState->isUsed = (val & kChunkUsedFlag) != 0;
pState->isMarkedUsed = (val & kChunkMarkedUsedFlag) != 0;
pState->purpose = (ChunkPurpose)(val & kChunkPurposeMask);
return kDIErrNone;
}
/*
* Set the state of a particular chunk.
*/
DIError DiskFS::VolumeUsage::SetChunkState(long block, const ChunkState* pState)
{
if (!fByBlocks)
return kDIErrInvalidArg;
return SetChunkStateIdx(block, pState);
}
DIError DiskFS::VolumeUsage::SetChunkState(long track, long sector,
const ChunkState* pState)
{
if (fByBlocks)
return kDIErrInvalidArg;
if (track < 0 || sector < 0 || sector >= fNumSectors)
return kDIErrInvalidArg;
return SetChunkStateIdx(track * fNumSectors + sector, pState);
}
DIError DiskFS::VolumeUsage::SetChunkStateIdx(int idx, const ChunkState* pState)
{
if (fList == NULL || idx < 0 || idx >= fListSize) {
assert(false);
return kDIErrInvalidArg;
}
unsigned char val = 0;
if (pState->isUsed) {
if ((pState->purpose & ~kChunkPurposeMask) != 0) {
assert(false);
return kDIErrInvalidArg;
}
val |= kChunkUsedFlag;
val |= (int)pState->purpose;
}
if (pState->isMarkedUsed)
val |= kChunkMarkedUsedFlag;
fList[idx] = val;
return kDIErrNone;
}
/*
* Count up the #of free chunks.
*/
long DiskFS::VolumeUsage::GetActualFreeChunks(void) const
{
ChunkState cstate; // could probably do this bitwise...
int freeCount = 0;
int funkyCount = 0;
for (int i = 0; i < fTotalChunks; i++) {
if (GetChunkStateIdx(i, &cstate) != kDIErrNone) {
assert(false);
return -1;
}
if (!cstate.isUsed && !cstate.isMarkedUsed)
freeCount++;
if ((!cstate.isUsed && cstate.isMarkedUsed) ||
(cstate.isUsed && !cstate.isMarkedUsed) ||
(cstate.isUsed && cstate.purpose == kChunkPurposeConflict))
{
funkyCount++;
}
}
LOGI(" VU total=%ld free=%d funky=%d",
fTotalChunks, freeCount, funkyCount);
return freeCount;
}
/*
* Convert a ChunkState into a single, hopefully meaningful, character.
*
* Possible states:
* '.' - !inuse, !marked (free space)
* 'X' - !inuse, marked (could be embedded volume)
* '!' - inuse, !marked (danger!)
* '#' - inuse, marked, used by more than one thing
* 'S' - inuse, marked, used by system (directories, volume bit map)
* 'I' - inuse, marked, used by file structure (index block)
* 'F' - inuse, marked, used by file
*/
char DiskFS::VolumeUsage::StateToChar(ChunkState* pState) const
{
if (!pState->isUsed && !pState->isMarkedUsed)
return '.';
if (!pState->isUsed && pState->isMarkedUsed)
return 'X';
if (pState->isUsed && !pState->isMarkedUsed)
return '!';
assert(pState->isUsed && pState->isMarkedUsed);
if (pState->purpose == kChunkPurposeUnknown)
return '?';
if (pState->purpose == kChunkPurposeConflict)
return '#';
if (pState->purpose == kChunkPurposeSystem)
return 'S';
if (pState->purpose == kChunkPurposeVolumeDir)
return 'V';
if (pState->purpose == kChunkPurposeSubdir)
return 'D';
if (pState->purpose == kChunkPurposeUserData)
return 'F';
if (pState->purpose == kChunkPurposeFileStruct)
return 'I';
if (pState->purpose == kChunkPurposeEmbedded)
return 'E';
assert(false);
return '?';
}
/*
* Dump the list.
*/
void DiskFS::VolumeUsage::Dump(void) const
{
#define kMapInit "--------------------------------"
if (fList == NULL) {
LOGI(" VU asked to dump empty list?");
return;
}
LOGI(" VU VolumeUsage dump (%ld free chunks):",
GetActualFreeChunks());
if (fByBlocks) {
ChunkState cstate;
char freemap[32+1] = kMapInit;
int block;
const int kEntriesPerLine = 32; // use 20 to match Copy][+
for (block = 0; block < fTotalChunks; block++) {
if (GetChunkState(block, &cstate) != kDIErrNone) {
assert(false);
return;
}
freemap[block % kEntriesPerLine] = StateToChar(&cstate);
if ((block % kEntriesPerLine) == kEntriesPerLine-1) {
LOGI(" 0x%04x: %s", block-(kEntriesPerLine-1), freemap);
}
}
if ((block % kEntriesPerLine) != 0) {
memset(freemap + (block % kEntriesPerLine), '-',
kEntriesPerLine - (block % kEntriesPerLine));
LOGI(" 0x%04x: %s", block-(kEntriesPerLine-1), freemap);
}
} else {
ChunkState cstate;
char freemap[32+1] = kMapInit;
long numTracks = fTotalChunks / fNumSectors;
int track, sector;
if (fNumSectors > 32) {
LOGI(" VU too many sectors (%ld)", fNumSectors);
return;
}
LOGI(" map 0123456789abcdef");
for (track = 0; track < numTracks; track++) {
for (sector = 0; sector < fNumSectors; sector++) {
if (GetChunkState(track, sector, &cstate) != kDIErrNone) {
assert(false);
return;
}
freemap[sector] = StateToChar(&cstate);
}
LOGI(" %2d: %s", track, freemap);
}
}
}

2127
diskimg/Win32BlockIO.cpp Normal file

File diff suppressed because it is too large Load Diff

413
diskimg/Win32BlockIO.h Normal file
View File

@ -0,0 +1,413 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
#ifdef _WIN32
/*
* Structures and functions for performing block-level I/O on Win32 logical
* and physical volumes.
*
* Under Win2K/XP this is pretty straightforward: open the volume and
* issue seek and read calls. It's not quite that simple -- reads need to
* be in 512-byte sectors for floppy and hard drives, seeks need to be on
* sector boundaries, and you can't seek from the end, which makes it hard
* to figure out how big the volume is -- but it's palatable.
*
* Under Win95/Win98/WinME, life is more difficult. You need to use the
* Int21h/7305h services to access logical volumes. Of course, those weren't
* available until Win95 OSR2, before which you used Int25h/6000h, but those
* don't work with FAT32 volumes. Access to physical devices requires Int13h,
* which is fine for floppies but requires 16-bit flat thunks for hard drives
* (see Q137176: "DeviceIoControl Int 13h Does Not Support Hard Disks").
*
* If Win98 can't recognize the volume on a floppy, it tries to reacquire
* the volume information every time you ask it to read a sector. This makes
* things *VERY* slow. The solution is to use the physical drive Int13h
* services. These come in two variants, one of which will work on just
* about any machine but only works with floppies. The other will work on
* anything built since about 1996.
*
* Figuring out whether something is or isn't a floppy requires yet
* another call. All things considered it's quite an ordeal. The block I/O
* functions are wrapped up in classes so nobody else has to worry about all
* this mess.
*
* Implementation note: this class is broken down by how the devices are
* opened, e.g. logical, physical, or ASPI address. Breaking it down by device
* type seems more appropriate, but Win98 vs Win2K can require completely
* different approaches (e.g. physical vs. logical for floppy disk, logical
* vs. ASPI for CD-ROM). There is no perfect decomposition.
*
* Summary:
* Win9x/ME physical drive: Int13h (doesn't work for hard drives)
* Win9x/ME logical drive: Int21h/7305h
* Win9x/ME SCSI drive or CD-ROM drive: ASPI
* Win2K/XP physical drive: CreateFile("\\.\PhysicalDriveN")
* Win2K/XP logical drive: CreateFile("\\.\X")
* Win2K/XP SCSI drive or CD-ROM drive: SPTI
*/
#ifndef DISKIMG_WIN32BLOCKIO_H
#define DISKIMG_WIN32BLOCKIO_H
namespace DiskImgLib {
extern bool IsWin9x(void);
/*
* Cache a contiguous set of blocks. This was originally motivated by poor
* write performance, but that problem was largely solved in other ways.
* It's still handy to write an entire track at once under Win98 though.
*
* Only storing continuous runs of blocks makes the cache less useful, but
* much easier to write, and hence less likely to break in unpleasant ways.
*
* This class just manages the blocks. The FlushCache() function in
* Win32LogicalVolume is responsible for actually pushing the writes through.
*
* (I'm not entirely happy with this, especially since it doesn't take into
* account the underlying device block size. This could've been a good place
* to handle the 2048-byte CD-ROM block size, rather than caching it again in
* the CD-ROM handler.)
*/
class CBCache {
public:
CBCache(void) : fFirstBlock(kEmpty), fNumBlocks(0)
{
for (int i = 0; i < kMaxCachedBlocks; i++)
fDirty[i] = false;
}
virtual ~CBCache(void) { Purge(); }
enum { kEmpty = -1 };
// is the block we want in the cache?
bool IsBlockInCache(long blockNum) const;
// read block out of cache (after verifying that it's present)
DIError GetFromCache(long blockNum, void* buf);
// can the cache store this block?
bool IsRoomInCache(long blockNum) const;
// write block to cache (after verifying that it will fit)
DIError PutInCache(long blockNum, const void* buf, bool isDirty);
// are there any dirty blocks in the cache?
bool IsDirty(void) const;
// get start, count, and buffer so we can write the cached data
void GetCachePointer(long* pFirstBlock, int* pNumBlocks, void** pBuf) const;
// clear all the dirty flags
void Scrub(void);
// purge all cache entries (ideally after writing w/help from GetCachePtr)
void Purge(void);
private:
enum {
kMaxCachedBlocks = 18, // one track on 1.4MB floppy
kBlockSize = 512, // must match with Win32LogicalVolume::
};
long fFirstBlock; // set to kEmpty when cache is empty
int fNumBlocks;
bool fDirty[kMaxCachedBlocks];
unsigned char fCache[kMaxCachedBlocks * kBlockSize];
};
/*
* This class encapsulates block access to a logical or physical volume.
*/
class Win32VolumeAccess {
public:
Win32VolumeAccess(void) :
fTotalBlocks(-1),
fpBlockAccess(NULL)
{}
virtual ~Win32VolumeAccess(void) {
if (fpBlockAccess != NULL) {
FlushCache(true);
fpBlockAccess->Close();
}
}
// "deviceName" has the form "X:\" (logical), "81:\" (physical), or
// "ASPI:x:y:z\" (ASPI)
DIError Open(const WCHAR* deviceName, bool readOnly);
// close the device
void Close(void);
// is the device open and working?
bool Ready(void) const { return fpBlockAccess != NULL; }
// return the volume's EOF
long GetTotalBlocks(void) const { return fTotalBlocks; }
// return the block size for this volume (always a power of 2)
int GetBlockSize(void) const { return BlockAccess::kBlockSize; }
// read one or more consecutive blocks
DIError ReadBlocks(long startBlock, short blockCount, void* buf);
// write one or more consecutive blocks
DIError WriteBlocks(long startBlock, short blockCount, const void* buf);
// flush our internal cache
DIError FlushCache(bool purge);
private:
/*
* Abstract base class with some handy functions.
*/
class BlockAccess {
public:
BlockAccess(void) { fIsWin9x = DiskImgLib::IsWin9x(); }
virtual ~BlockAccess(void) {}
typedef struct {
int numCyls;
int numHeads;
int numSectors;
long blockCount; // total #of blocks on this kind of disk
} DiskGeometry;
// generic interfaces
virtual DIError Open(const WCHAR* deviceName, bool readOnly) = 0;
virtual DIError DetectCapacity(long* pNumBlocks) = 0;
virtual DIError ReadBlocks(long startBlock, short blockCount,
void* buf) = 0;
virtual DIError WriteBlocks(long startBlock, short blockCount,
const void* buf) = 0;
virtual DIError Close(void) = 0;
static bool BlockToCylinderHeadSector(long blockNum,
const DiskGeometry* pGeometry, int* pCylinder, int* pHead,
int* pSector, long* pLastBlockOnTrack);
enum {
kNumLogicalVolumes = 26, // A-Z
kBlockSize = 512,
kCDROMSectorSize = 2048,
kMaxFloppyRetries = 3, // retry floppy reads/writes
};
// BIOS floppy disk drive type; doubles here as media type
typedef enum {
kFloppyUnknown = 0,
kFloppy525_360 = 1,
kFloppy525_1200 = 2,
kFloppy35_720 = 3,
kFloppy35_1440 = 4,
kFloppy35_2880 = 5,
kFloppyMax
} FloppyKind;
protected:
static DIError GetFloppyDriveKind(HANDLE handle, int unitNum,
FloppyKind* pKind);
// detect the #of blocks on the volume
static DIError ScanCapacity(BlockAccess* pThis, long* pNumBlocks);
// determine whether a block is readable
static bool CanReadBlock(BlockAccess* pThis, long blockNum);
// try to detect device capacity using SPTI
DIError DetectCapacitySPTI(HANDLE handle,
bool isCDROM, long* pNumBlocks);
static int ReadBlocksInt13h(HANDLE handle, int unitNum,
int cylinder, int head, int sector, short blockCount, void* buf);
static DIError ReadBlocksInt13h(HANDLE handle, int unitNum,
const DiskGeometry* pGeometry, long startBlock, short blockCount,
void* buf);
static int WriteBlocksInt13h(HANDLE handle, int unitNum,
int cylinder, int head, int sector, short blockCount,
const void* buf);
static DIError WriteBlocksInt13h(HANDLE handle, int unitNum,
const DiskGeometry* pGeometry, long startBlock, short blockCount,
const void* buf);
static DIError ReadBlocksInt21h(HANDLE handle, int driveNum,
long startBlock, short blockCount, void* buf);
static DIError WriteBlocksInt21h(HANDLE handle, int driveNum,
long startBlock, short blockCount, const void* buf);
static DIError ReadBlocksWin2K(HANDLE handle,
long startBlock, short blockCount, void* buf);
static DIError WriteBlocksWin2K(HANDLE handle,
long startBlock, short blockCount, const void* buf);
bool fIsWin9x; // Win9x/ME=true, Win2K/XP=false
};
/*
* Access to a logical volume (e.g. "C:\") under Win9x and Win2K/XP.
*/
class LogicalBlockAccess : public BlockAccess {
public:
LogicalBlockAccess(void) : fHandle(NULL), fIsCDROM(false),
fDriveNum(-1), fLastSectorCache(NULL), fLastSectorNum(-1)
{}
virtual ~LogicalBlockAccess(void) {
if (fHandle != NULL) {
//LOGI("HEY: LogicalBlockAccess: forcing close");
Close();
}
delete[] fLastSectorCache;
}
virtual DIError Open(const WCHAR* deviceName, bool readOnly);
virtual DIError DetectCapacity(long* pNumBlocks) {
/* use SCSI length value if at all possible */
DIError dierr;
dierr = DetectCapacitySPTI(fHandle, fIsCDROM, pNumBlocks);
if (fIsCDROM)
return dierr; // SPTI should always work for CD-ROM
if (dierr != kDIErrNone)
return ScanCapacity(this, pNumBlocks); // fall back on scan
else
return dierr;
}
virtual DIError ReadBlocks(long startBlock, short blockCount,
void* buf)
{
if (fIsCDROM)
return ReadBlocksCDROM(fHandle, startBlock, blockCount, buf);
if (fIsWin9x)
return ReadBlocksInt21h(fHandle, fDriveNum, startBlock,
blockCount, buf);
else
return ReadBlocksWin2K(fHandle, startBlock, blockCount, buf);
}
virtual DIError WriteBlocks(long startBlock, short blockCount,
const void* buf)
{
if (fIsCDROM)
return kDIErrWriteProtected;
if (fIsWin9x)
return WriteBlocksInt21h(fHandle, fDriveNum, startBlock,
blockCount, buf);
else
return WriteBlocksWin2K(fHandle, startBlock, blockCount, buf);
}
virtual DIError Close(void);
private:
//DIError DetectCapacitySPTI(long* pNumBlocks);
DIError ReadBlocksCDROM(HANDLE handle,
long startBlock, short numBlocks, void* buf);
// Win2K/XP and Win9x/ME
HANDLE fHandle;
bool fIsCDROM; // set for CD-ROM devices
// Win9x/ME
int fDriveNum; // 1=A, 3=C, etc
// CD-ROM goodies
unsigned char* fLastSectorCache;
long fLastSectorNum;
};
/*
* Access to a physical volume (e.g. 00h or 80h) under Win9x and
* Win2K/XP.
*/
class PhysicalBlockAccess : public BlockAccess {
public:
PhysicalBlockAccess(void) :
fHandle(NULL),
fInt13Unit(-1),
fFloppyKind(kFloppyUnknown)
{}
virtual ~PhysicalBlockAccess(void) {}
virtual DIError Open(const WCHAR* deviceName, bool readOnly);
virtual DIError DetectCapacity(long* pNumBlocks) {
/* try SPTI in case it happens to work */
DIError dierr;
dierr = DetectCapacitySPTI(fHandle, false, pNumBlocks);
if (dierr != kDIErrNone)
return ScanCapacity(this, pNumBlocks);
else
return dierr;
}
virtual DIError ReadBlocks(long startBlock, short blockCount,
void* buf)
{
if (fIsWin9x)
return ReadBlocksInt13h(fHandle, fInt13Unit,
&fGeometry, startBlock, blockCount, buf);
else
return ReadBlocksWin2K(fHandle,
startBlock, blockCount, buf);
}
virtual DIError WriteBlocks(long startBlock, short blockCount,
const void* buf)
{
if (fIsWin9x)
return WriteBlocksInt13h(fHandle, fInt13Unit,
&fGeometry, startBlock, blockCount, buf);
else
return WriteBlocksWin2K(fHandle,
startBlock, blockCount, buf);
}
virtual DIError Close(void);
private:
DIError DetectFloppyGeometry(void);
// Win2K/XP
HANDLE fHandle;
// Win9x/ME
int fInt13Unit; // 00h=floppy #1, 80h=HD#1
FloppyKind fFloppyKind;
DiskGeometry fGeometry;
};
#ifdef WANT_ASPI
/*
* Access to a SCSI volume via the ASPI interface.
*/
class ASPIBlockAccess : public BlockAccess {
public:
ASPIBlockAccess(void) : fpASPI(NULL),
fAdapter(0xff), fTarget(0xff), fLun(0xff), fReadOnly(false),
fLastChunkCache(NULL), fLastChunkNum(-1), fChunkSize(-1)
{}
virtual ~ASPIBlockAccess(void) { delete[] fLastChunkCache; }
virtual DIError Open(const char* deviceName, bool readOnly);
virtual DIError DetectCapacity(long* pNumBlocks);
virtual DIError ReadBlocks(long startBlock, short blockCount,
void* buf);
virtual DIError WriteBlocks(long startBlock, short blockCount,
const void* buf);
virtual DIError Close(void);
private:
int ExtractInt(const char** pStr, int* pResult);
ASPI* fpASPI;
unsigned char fAdapter;
unsigned char fTarget;
unsigned char fLun;
bool fReadOnly;
// block cache
unsigned char* fLastChunkCache;
long fLastChunkNum;
long fChunkSize; // set by DetectCapacity
};
#endif /*WANT_ASPI*/
// write a series of blocks to the volume
DIError DoWriteBlocks(long startBlock, short blockCount, const void* buf)
{
return fpBlockAccess->WriteBlocks(startBlock, blockCount, buf);
}
long fTotalBlocks;
BlockAccess* fpBlockAccess; // really LogicalBA or PhysicalBA
CBCache fBlockCache;
};
}; // namespace DiskImgLib
#endif /*DISKIMG_WIN32BLOCKIO_H*/
#endif /*_WIN32*/

60
diskimg/Win32Extra.h Normal file
View File

@ -0,0 +1,60 @@
/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Visual C++ 6.0 doesn't have this definition, because it wasn't added until
* WinXP.
*
* (Do we want IOCTL_DISK_GET_DRIVE_LAYOUT_EX too?)
*/
#ifndef DISKIMG_WIN32EXTRA_H
#define DISKIMG_WIN32EXTRA_H
#include <winioctl.h> // base definitions
#ifndef IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
/*
BOOL DeviceIoControl(
(HANDLE) hDevice, // handle to volume
IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, // dwIoControlCode
NULL, // lpInBuffer
0, // nInBufferSize
(LPVOID) lpOutBuffer, // output buffer
(DWORD) nOutBufferSize, // size of output buffer
(LPDWORD) lpBytesReturned, // number of bytes returned
(LPOVERLAPPED) lpOverlapped // OVERLAPPED structure
);
*/
#define IOCTL_DISK_GET_DRIVE_GEOMETRY_EX \
CTL_CODE(IOCTL_DISK_BASE, 0x0028, METHOD_BUFFERED, FILE_ANY_ACCESS)
typedef struct _DISK_GEOMETRY_EX {
DISK_GEOMETRY Geometry;
LARGE_INTEGER DiskSize;
BYTE Data[1];
} DISK_GEOMETRY_EX, *PDISK_GEOMETRY_EX;
#if 0
typedef struct _DISK_DETECTION_INFO {
DWORD SizeOfDetectInfo;
DETECTION_TYPE DetectionType;
union {
struct {
DISK_INT13_INFO Int13;
DISK_EX_INT13_INFO ExInt13;
};
};
} DISK_DETECTION_INFO, *PDISK_DETECTION_INFO;
PDISK_DETECTION_INFO DiskGeometryGetDetect(PDISK_GEOMETRY_EX Geometry);
PDISK_PARTITION_INFO DiskGeometryGetPartition(PDISK_GEOMETRY_EX Geometry);
#endif
#endif /*IOCTL_DISK_GET_DRIVE_GEOMETRY_EX*/
#endif /*DISKIMG_WIN32EXTRA_H*/

216
diskimg/diskimg.vcxproj Normal file
View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<SccProjectName />
<SccLocalPath />
<ProjectGuid>{0CFE6FAD-0126-4E99-8625-C807D1D2AAF4}</ProjectGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseOfMfc>false</UseOfMfc>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>12.0.30501.0</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
<TargetName>diskimg5</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
<TargetName>diskimg5</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<PreprocessorDefinitions>WIN32;NDEBUGX;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
<ObjectFileName>$(IntDir)</ObjectFileName>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<WarningLevel>Level3</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<SuppressStartupBanner>false</SuppressStartupBanner>
<ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile>
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers />
<SubSystem>Windows</SubSystem>
</Link>
<Midl>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MkTypLibCompatible>true</MkTypLibCompatible>
<SuppressStartupBanner>true</SuppressStartupBanner>
<TargetEnvironment>Win32</TargetEnvironment>
<TypeLibraryName>.\Release/diskimg.tlb</TypeLibraryName>
<HeaderFileName />
</Midl>
<PostBuildEvent>
<Message>
</Message>
<Command>
</Command>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
<ObjectFileName>$(IntDir)</ObjectFileName>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<WarningLevel>Level3</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<SuppressStartupBanner>false</SuppressStartupBanner>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile>
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers />
<SubSystem>Windows</SubSystem>
</Link>
<Midl>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MkTypLibCompatible>true</MkTypLibCompatible>
<SuppressStartupBanner>true</SuppressStartupBanner>
<TargetEnvironment>Win32</TargetEnvironment>
<TypeLibraryName>.\Debug/diskimg.tlb</TypeLibraryName>
<HeaderFileName />
</Midl>
<PostBuildEvent>
<Message>
</Message>
<Command>
</Command>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="CP_ntddscsi.h" />
<ClInclude Include="CP_wnaspi32.h" />
<ClInclude Include="DiskImg.h" />
<ClInclude Include="DiskImgDetail.h" />
<ClInclude Include="DiskImgPriv.h" />
<ClInclude Include="GenericFD.h" />
<ClInclude Include="SCSIDefs.h" />
<ClInclude Include="SPTI.h" />
<ClInclude Include="StdAfx.h" />
<ClInclude Include="TwoImg.h" />
<ClInclude Include="Win32BlockIO.h" />
<ClInclude Include="Win32Extra.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\nufxlib\nufxlib.vcxproj">
<Project>{c48ae53b-3dcb-43b1-9207-b7c5b6bb78af}</Project>
</ProjectReference>
<ProjectReference Include="..\zlib\zlib.vcxproj">
<Project>{b66109f4-217b-43c0-86aa-eb55657e5ac0}</Project>
</ProjectReference>
<ProjectReference Include="libhfs\libhfs.vcxproj">
<Project>{0fa742e9-8c07-43dd-aff8-ce31faf70821}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ASPI.cpp" />
<ClCompile Include="CFFA.cpp" />
<ClCompile Include="Container.cpp" />
<ClCompile Include="CPM.cpp" />
<ClCompile Include="DDD.cpp" />
<ClCompile Include="DiskFS.cpp" />
<ClCompile Include="DiskImg.cpp" />
<ClCompile Include="DIUtil.cpp" />
<ClCompile Include="DOS33.cpp" />
<ClCompile Include="DOSImage.cpp" />
<ClCompile Include="FAT.cpp" />
<ClCompile Include="FDI.cpp" />
<ClCompile Include="FocusDrive.cpp" />
<ClCompile Include="GenericFD.cpp" />
<ClCompile Include="Global.cpp" />
<ClCompile Include="Gutenberg.cpp" />
<ClCompile Include="HFS.cpp" />
<ClCompile Include="ImageWrapper.cpp" />
<ClCompile Include="MacPart.cpp" />
<ClCompile Include="MicroDrive.cpp" />
<ClCompile Include="Nibble.cpp" />
<ClCompile Include="Nibble35.cpp" />
<ClCompile Include="OuterWrapper.cpp" />
<ClCompile Include="OzDOS.cpp" />
<ClCompile Include="Pascal.cpp" />
<ClCompile Include="ProDOS.cpp" />
<ClCompile Include="RDOS.cpp" />
<ClCompile Include="SPTI.cpp" />
<ClCompile Include="StdAfx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="TwoImg.cpp" />
<ClCompile Include="UNIDOS.cpp" />
<ClCompile Include="VolumeUsage.cpp" />
<ClCompile Include="Win32BlockIO.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{b6ac9831-b535-4474-a308-d3bf6fdf4488}</UniqueIdentifier>
<Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{def4def1-c9b0-48b3-a80b-c137f7ebf9bd}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{ed4624b4-6280-4672-8e7f-e8aac6e178ef}</UniqueIdentifier>
<Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CP_ntddscsi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CP_wnaspi32.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DiskImg.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DiskImgDetail.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DiskImgPriv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GenericFD.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SCSIDefs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SPTI.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="StdAfx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TwoImg.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Win32BlockIO.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Win32Extra.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ASPI.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CFFA.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Container.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CPM.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DDD.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DiskFS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DiskImg.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DIUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DOS33.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DOSImage.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FAT.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FDI.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FocusDrive.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GenericFD.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Global.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Gutenberg.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HFS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ImageWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MacPart.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MicroDrive.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Nibble.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Nibble35.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="OuterWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="OzDOS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Pascal.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ProDOS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RDOS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SPTI.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TwoImg.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UNIDOS.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="VolumeUsage.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Win32BlockIO.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StdAfx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

54
libhfs/CMakeLists.txt Normal file
View File

@ -0,0 +1,54 @@
cmake_minimum_required(VERSION 3.0)
set(CMAKE_BUILD_TYPE DEBUG)
set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(PROJECT_NAME hfs)
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
project(${PROJECT_NAME})
set(ALL_DEFINES "-Wwrite-strings -Wno-pointer-sign -Wpointer-arith -Wshadow -Wstrict-prototypes -D_FILE_OFFSET_BITS=64" )
set(CONFIG_DEF "-DSTDC_HEADERS=1 -DHAVE_MKTIME=1 -DHAVE_FCNTL_H=1 -DHAVE_UNISTD_H=1" )
set(DEBUG_OPT "-O0 -g3 " )
set(RELEASE_OPT "-O3 " )
set(CMAKE_C_FLAGS "-Wall ${ALL_DEFINES} ${CONFIG_DEF}")
set(CMAKE_CXX_FLAGS "-Wall ${ALL_DEFINES} ${CONFIG_DEF}")
set(CMAKE_CXX_FLAGS_DEBUG "${DEBUG_OPT} ")
set(CMAKE_CXX_FLAGS_RELEASE "${RELEASE_OPT}")
set(CMAKE_C_FLAGS_DEBUG "${DEBUG_OPT} ")
set(CMAKE_C_FLAGS_RELEASE "${RELEASE_OPT}")
set(FIND_LIBRARY_USE_LIB64_PATHS TRUE)
set (SOURCE
block.c
btree.c
data.c
file.c
hfs.c
low.c
medium.c
memcmp.c
node.c
os.c
record.c
version.c
volume.c
)
include_directories(BEFORE
${PROJECT_ROOT}
)
add_library( ${PROJECT_NAME} SHARED ${SOURCE})
add_library( ${PROJECT_NAME}_static STATIC ${SOURCE})
target_link_libraries (
${PROJECT_NAME}
)

21
libhfs/COPYRIGHT Normal file
View File

@ -0,0 +1,21 @@
hfsutils - tools for reading and writing Macintosh HFS volumes
Copyright (C) 1996-1998 Robert Leslie
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
If you would like to negotiate alternate licensing terms, you may do
so by contacting the author: Robert Leslie <rob@mars.org>

39
libhfs/Makefile Normal file
View File

@ -0,0 +1,39 @@
#
# DiskImg libhfs makefile for Linux.
#
SHELL = /bin/sh
CC = gcc
AR = ar
OPT = -g -DHAVE_CONFIG_H
#OPT = -g -O2 -DHAVE_CONFIG_H
GCC_FLAGS = -Wall -Wwrite-strings -Wpointer-arith -Wshadow -Wstrict-prototypes
CFLAGS = $(OPT) $(GCC_FLAGS) -D_FILE_OFFSET_BITS=64
SRCS = os.c data.c block.c low.c medium.c file.c btree.c node.c \
record.c volume.c hfs.c version.c
OBJS = os.o data.o block.o low.o medium.o file.o btree.o node.o \
record.o volume.o hfs.o version.o
STATIC_PRODUCT = libhfs.a
PRODUCT = $(STATIC_PRODUCT)
all: $(PRODUCT)
@true
$(STATIC_PRODUCT): $(OBJS)
-rm -f $(STATIC_PRODUCT)
$(AR) rcv $@ $(OBJS)
clean:
-rm -f *.o core
-rm -f $(STATIC_PRODUCT)
-rm -f Makefile.bak
tags::
@ctags -R --totals *
depend:
makedepend -- $(CFLAGS) -- $(SRCS)
# DO NOT DELETE THIS LINE -- make depend depends on it.

5
libhfs/README Normal file
View File

@ -0,0 +1,5 @@
HFS utility library, part of hfsutils v3.2.6 by Robert Leslie.
Adapted for use with CiderPress by Andy McFadden. The os_* functions
have been replaced with a callback mechanism, and code that uses global
variables has been (mostly) removed.

272
libhfs/apple.h Normal file
View File

@ -0,0 +1,272 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
typedef signed char Char;
typedef unsigned char UChar;
typedef signed char SignedByte;
typedef signed short Integer;
typedef unsigned short UInteger;
typedef signed long LongInt;
typedef unsigned long ULongInt;
typedef char Str15[16];
typedef char Str31[32];
typedef long OSType;
typedef struct {
Integer sbSig; /* device signature (should be 0x4552) */
Integer sbBlkSize; /* block size of the device (in bytes) */
LongInt sbBlkCount; /* number of blocks on the device */
Integer sbDevType; /* reserved */
Integer sbDevId; /* reserved */
LongInt sbData; /* reserved */
Integer sbDrvrCount; /* number of driver descriptor entries */
LongInt ddBlock; /* first driver's starting block */
Integer ddSize; /* size of the driver, in 512-byte blocks */
Integer ddType; /* driver operating system type (MacOS = 1) */
Integer ddPad[243]; /* additional drivers, if any */
} Block0;
typedef struct {
Integer pmSig; /* partition signature (0x504d or 0x5453) */
Integer pmSigPad; /* reserved */
LongInt pmMapBlkCnt; /* number of blocks in partition map */
LongInt pmPyPartStart; /* first physical block of partition */
LongInt pmPartBlkCnt; /* number of blocks in partition */
Char pmPartName[33]; /* partition name */
Char pmParType[33]; /* partition type */
LongInt pmLgDataStart; /* first logical block of data area */
LongInt pmDataCnt; /* number of blocks in data area */
LongInt pmPartStatus; /* partition status information */
LongInt pmLgBootStart; /* first logical block of boot code */
LongInt pmBootSize; /* size of boot code, in bytes */
LongInt pmBootAddr; /* boot code load address */
LongInt pmBootAddr2; /* reserved */
LongInt pmBootEntry; /* boot code entry point */
LongInt pmBootEntry2; /* reserved */
LongInt pmBootCksum; /* boot code checksum */
Char pmProcessor[17];/* processor type */
Integer pmPad[188]; /* reserved */
} Partition;
typedef struct {
Integer bbID; /* boot blocks signature */
LongInt bbEntry; /* entry point to boot code */
Integer bbVersion; /* boot blocks version number */
Integer bbPageFlags; /* used internally */
Str15 bbSysName; /* System filename */
Str15 bbShellName; /* Finder filename */
Str15 bbDbg1Name; /* debugger filename */
Str15 bbDbg2Name; /* debugger filename */
Str15 bbScreenName; /* name of startup screen */
Str15 bbHelloName; /* name of startup program */
Str15 bbScrapName; /* name of system scrap file */
Integer bbCntFCBs; /* number of FCBs to allocate */
Integer bbCntEvts; /* number of event queue elements */
LongInt bb128KSHeap; /* system heap size on 128K Mac */
LongInt bb256KSHeap; /* used internally */
LongInt bbSysHeapSize; /* system heap size on all machines */
Integer filler; /* reserved */
LongInt bbSysHeapExtra; /* additional system heap space */
LongInt bbSysHeapFract; /* fraction of RAM for system heap */
} BootBlkHdr;
typedef struct {
UInteger xdrStABN; /* first allocation block */
UInteger xdrNumABlks; /* number of allocation blocks */
} ExtDescriptor;
typedef ExtDescriptor ExtDataRec[3];
typedef struct {
SignedByte xkrKeyLen; /* key length */
SignedByte xkrFkType; /* fork type (0x00/0xff == data/resource */
ULongInt xkrFNum; /* file number */
UInteger xkrFABN; /* starting file allocation block */
} ExtKeyRec;
typedef struct {
SignedByte ckrKeyLen; /* key length */
SignedByte ckrResrv1; /* reserved */
ULongInt ckrParID; /* parent directory ID */
Str31 ckrCName; /* catalog node name */
} CatKeyRec;
typedef struct {
Integer v; /* vertical coordinate */
Integer h; /* horizontal coordinate */
} Point;
typedef struct {
Integer top; /* top edge of rectangle */
Integer left; /* left edge */
Integer bottom; /* bottom edge */
Integer right; /* right edge */
} Rect;
typedef struct {
Rect frRect; /* folder's rectangle */
Integer frFlags; /* flags */
Point frLocation; /* folder's location */
Integer frView; /* folder's view */
} DInfo;
typedef struct {
Point frScroll; /* scroll position */
LongInt frOpenChain; /* directory ID chain of open folders */
Integer frUnused; /* reserved */
Integer frComment; /* comment ID */
LongInt frPutAway; /* directory ID */
} DXInfo;
typedef struct {
OSType fdType; /* file type */
OSType fdCreator; /* file's creator */
Integer fdFlags; /* flags */
Point fdLocation; /* file's location */
Integer fdFldr; /* file's window */
} FInfo;
typedef struct {
Integer fdIconID; /* icon ID */
Integer fdUnused[4]; /* reserved */
Integer fdComment; /* comment ID */
LongInt fdPutAway; /* home directory ID */
} FXInfo;
typedef struct {
Integer drSigWord; /* volume signature (0x4244 for HFS) */
LongInt drCrDate; /* date and time of volume creation */
LongInt drLsMod; /* date and time of last modification */
Integer drAtrb; /* volume attributes */
UInteger drNmFls; /* number of files in root directory */
UInteger drVBMSt; /* first block of volume bit map (always 3) */
UInteger drAllocPtr; /* start of next allocation search */
UInteger drNmAlBlks; /* number of allocation blocks in volume */
ULongInt drAlBlkSiz; /* size (in bytes) of allocation blocks */
ULongInt drClpSiz; /* default clump size */
UInteger drAlBlSt; /* first allocation block in volume */
LongInt drNxtCNID; /* next unused catalog node ID (dir/file ID) */
UInteger drFreeBks; /* number of unused allocation blocks */
char drVN[28]; /* volume name (1-27 chars) */
LongInt drVolBkUp; /* date and time of last backup */
Integer drVSeqNum; /* volume backup sequence number */
ULongInt drWrCnt; /* volume write count */
ULongInt drXTClpSiz; /* clump size for extents overflow file */
ULongInt drCTClpSiz; /* clump size for catalog file */
UInteger drNmRtDirs; /* number of directories in root directory */
ULongInt drFilCnt; /* number of files in volume */
ULongInt drDirCnt; /* number of directories in volume */
LongInt drFndrInfo[8]; /* information used by the Finder */
UInteger drEmbedSigWord; /* type of embedded volume */
ExtDescriptor drEmbedExtent; /* location of embedded volume */
ULongInt drXTFlSize; /* size (in bytes) of extents overflow file */
ExtDataRec drXTExtRec; /* first extent record for extents file */
ULongInt drCTFlSize; /* size (in bytes) of catalog file */
ExtDataRec drCTExtRec; /* first extent record for catalog file */
} MDB;
typedef enum {
cdrDirRec = 1,
cdrFilRec = 2,
cdrThdRec = 3,
cdrFThdRec = 4
} CatDataType;
typedef struct {
SignedByte cdrType; /* record type */
SignedByte cdrResrv2; /* reserved */
union {
struct { /* cdrDirRec */
Integer dirFlags; /* directory flags */
UInteger dirVal; /* directory valence */
ULongInt dirDirID; /* directory ID */
LongInt dirCrDat; /* date and time of creation */
LongInt dirMdDat; /* date and time of last modification */
LongInt dirBkDat; /* date and time of last backup */
DInfo dirUsrInfo; /* Finder information */
DXInfo dirFndrInfo; /* additional Finder information */
LongInt dirResrv[4]; /* reserved */
} dir;
struct { /* cdrFilRec */
SignedByte
filFlags; /* file flags */
SignedByte
filTyp; /* file type */
FInfo filUsrWds; /* Finder information */
ULongInt filFlNum; /* file ID */
UInteger filStBlk; /* first alloc block of data fork */
ULongInt filLgLen; /* logical EOF of data fork */
ULongInt filPyLen; /* physical EOF of data fork */
UInteger filRStBlk; /* first alloc block of resource fork */
ULongInt filRLgLen; /* logical EOF of resource fork */
ULongInt filRPyLen; /* physical EOF of resource fork */
LongInt filCrDat; /* date and time of creation */
LongInt filMdDat; /* date and time of last modification */
LongInt filBkDat; /* date and time of last backup */
FXInfo filFndrInfo; /* additional Finder information */
UInteger filClpSize; /* file clump size */
ExtDataRec
filExtRec; /* first data fork extent record */
ExtDataRec
filRExtRec; /* first resource fork extent record */
LongInt filResrv; /* reserved */
} fil;
struct { /* cdrThdRec */
LongInt thdResrv[2]; /* reserved */
ULongInt thdParID; /* parent ID for this directory */
Str31 thdCName; /* name of this directory */
} dthd;
struct { /* cdrFThdRec */
LongInt fthdResrv[2]; /* reserved */
ULongInt fthdParID; /* parent ID for this file */
Str31 fthdCName; /* name of this file */
} fthd;
} u;
} CatDataRec;
typedef struct {
ULongInt ndFLink; /* forward link */
ULongInt ndBLink; /* backward link */
SignedByte ndType; /* node type */
SignedByte ndNHeight; /* node level */
UInteger ndNRecs; /* number of records in node */
Integer ndResv2; /* reserved */
} NodeDescriptor;
enum {
ndIndxNode = (SignedByte) 0x00,
ndHdrNode = (SignedByte) 0x01,
ndMapNode = (SignedByte) 0x02,
ndLeafNode = (SignedByte) 0xff
};
typedef struct {
UInteger bthDepth; /* current depth of tree */
ULongInt bthRoot; /* number of root node */
ULongInt bthNRecs; /* number of leaf records in tree */
ULongInt bthFNode; /* number of first leaf node */
ULongInt bthLNode; /* number of last leaf node */
UInteger bthNodeSize; /* size of a node */
UInteger bthKeyLen; /* maximum length of a key */
ULongInt bthNNodes; /* total number of nodes in tree */
ULongInt bthFree; /* number of free nodes */
SignedByte bthResv[76]; /* reserved */
} BTHdrRec;

807
libhfs/block.c Normal file
View File

@ -0,0 +1,807 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "volume.h"
# include "block.h"
# include "os.h"
# define INUSE(b) ((b)->flags & HFS_BUCKET_INUSE)
# define DIRTY(b) ((b)->flags & HFS_BUCKET_DIRTY)
/*
* NAME: block->init()
* DESCRIPTION: initialize a volume's block cache
*/
int b_init(hfsvol *vol)
{
bcache *cache;
int i;
ASSERT(vol->cache == 0);
cache = ALLOC(bcache, 1);
if (cache == 0)
ERROR(ENOMEM, 0);
vol->cache = cache;
cache->vol = vol;
cache->tail = &cache->chain[HFS_CACHESZ - 1];
cache->hits = 0;
cache->misses = 0;
for (i = 0; i < HFS_CACHESZ; ++i)
{
bucket *b = &cache->chain[i];
b->flags = 0;
b->count = 0;
b->bnum = 0;
b->data = &cache->pool[i];
b->cnext = b + 1;
b->cprev = b - 1;
b->hnext = 0;
b->hprev = 0;
}
cache->chain[0].cprev = cache->tail;
cache->tail->cnext = &cache->chain[0];
for (i = 0; i < HFS_HASHSZ; ++i)
cache->hash[i] = 0;
return 0;
fail:
return -1;
}
# ifdef DEBUG
/*
* NAME: block->showstats()
* DESCRIPTION: output cache hit/miss ratio
*/
void b_showstats(const bcache *cache)
{
fprintf(stderr, "BLOCK: CACHE vol 0x%lx \"%s\" hit/miss ratio = %.3f\n",
(unsigned long) cache->vol, cache->vol->mdb.drVN,
(float) cache->hits / (float) cache->misses);
}
/*
* NAME: block->dumpcache()
* DESCRIPTION: dump the cache tables for a volume
*/
void b_dumpcache(const bcache *cache)
{
const bucket *b;
int i;
fprintf(stderr, "BLOCK CACHE DUMP:\n");
for (i = 0, b = cache->tail->cnext; i < HFS_CACHESZ; ++i, b = b->cnext)
{
if (INUSE(b))
{
fprintf(stderr, "\t %lu", b->bnum);
if (DIRTY(b))
fprintf(stderr, "*");
fprintf(stderr, ":%u", b->count);
}
}
fprintf(stderr, "\n");
fprintf(stderr, "BLOCK HASH DUMP:\n");
for (i = 0; i < HFS_HASHSZ; ++i)
{
int seen = 0;
for (b = cache->hash[i]; b; b = b->hnext)
{
if (! seen)
fprintf(stderr, " %d:", i);
if (INUSE(b))
{
fprintf(stderr, " %lu", b->bnum);
if (DIRTY(b))
fprintf(stderr, "*");
fprintf(stderr, ":%u", b->count);
}
seen = 1;
}
if (seen)
fprintf(stderr, "\n");
}
}
# endif
/*
* NAME: fillchain()
* DESCRIPTION: fill a chain of bucket buffers with a single read
*/
static
int fillchain(hfsvol *vol, bucket **bptr, unsigned int *count)
{
bucket *blist[HFS_BLOCKBUFSZ], **start = bptr;
unsigned long bnum;
unsigned int len, i;
for (len = 0; len < HFS_BLOCKBUFSZ &&
(unsigned int) (bptr - start) < *count; ++bptr)
{
if (INUSE(*bptr))
continue;
if (len > 0 && (*bptr)->bnum != bnum)
break;
blist[len++] = *bptr;
bnum = (*bptr)->bnum + 1;
}
*count = bptr - start;
if (len == 0)
goto done;
else if (len == 1)
{
if (b_readpb(vol, vol->vstart + blist[0]->bnum,
blist[0]->data, 1) == -1)
goto fail;
}
else
{
block buffer[HFS_BLOCKBUFSZ];
if (b_readpb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1)
goto fail;
for (i = 0; i < len; ++i)
memcpy(blist[i]->data, buffer[i], HFS_BLOCKSZ);
}
for (i = 0; i < len; ++i)
{
blist[i]->flags |= HFS_BUCKET_INUSE;
blist[i]->flags &= ~HFS_BUCKET_DIRTY;
}
done:
return 0;
fail:
return -1;
}
/*
* NAME: flushchain()
* DESCRIPTION: store a chain of bucket buffers with a single write
*/
static
int flushchain(hfsvol *vol, bucket **bptr, unsigned int *count)
{
bucket *blist[HFS_BLOCKBUFSZ], **start = bptr;
unsigned long bnum;
unsigned int len, i;
for (len = 0; len < HFS_BLOCKBUFSZ &&
(unsigned int) (bptr - start) < *count; ++bptr)
{
if (! INUSE(*bptr) || ! DIRTY(*bptr))
continue;
if (len > 0 && (*bptr)->bnum != bnum)
break;
blist[len++] = *bptr;
bnum = (*bptr)->bnum + 1;
}
*count = bptr - start;
if (len == 0)
goto done;
else if (len == 1)
{
if (b_writepb(vol, vol->vstart + blist[0]->bnum,
blist[0]->data, 1) == -1)
goto fail;
}
else
{
block buffer[HFS_BLOCKBUFSZ];
for (i = 0; i < len; ++i)
memcpy(buffer[i], blist[i]->data, HFS_BLOCKSZ);
if (b_writepb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1)
goto fail;
}
for (i = 0; i < len; ++i)
blist[i]->flags &= ~HFS_BUCKET_DIRTY;
done:
return 0;
fail:
return -1;
}
/*
* NAME: compare()
* DESCRIPTION: comparison function for qsort of cache bucket pointers
*/
static
int compare(const bucket **b1, const bucket **b2)
{
long diff;
diff = (*b1)->bnum - (*b2)->bnum;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else
return 0;
}
/*
* NAME: dobuckets()
* DESCRIPTION: fill or flush an array of cache buckets to a volume
*/
static
int dobuckets(hfsvol *vol, bucket **chain, unsigned int len,
int (*func)(hfsvol *, bucket **, unsigned int *))
{
unsigned int count, i;
int result = 0;
qsort(chain, len, sizeof(*chain),
(int (*)(const void *, const void *)) compare);
for (i = 0; i < len; i += count)
{
count = len - i;
if (func(vol, chain + i, &count) == -1)
result = -1;
}
return result;
}
# define fillbuckets(vol, chain, len) dobuckets(vol, chain, len, fillchain)
# define flushbuckets(vol, chain, len) dobuckets(vol, chain, len, flushchain)
/*
* NAME: block->flush()
* DESCRIPTION: commit dirty cache blocks to a volume
*/
int b_flush(hfsvol *vol)
{
bcache *cache = vol->cache;
bucket *chain[HFS_CACHESZ];
int i;
if (cache == 0 || (vol->flags & HFS_VOL_READONLY))
goto done;
for (i = 0; i < HFS_CACHESZ; ++i)
chain[i] = &cache->chain[i];
if (flushbuckets(vol, chain, HFS_CACHESZ) == -1)
goto fail;
done:
# ifdef DEBUG
if (cache)
b_showstats(cache);
# endif
return 0;
fail:
return -1;
}
/*
* NAME: block->finish()
* DESCRIPTION: commit and free a volume's block cache
*/
int b_finish(hfsvol *vol)
{
int result = 0;
if (vol->cache == 0)
goto done;
# ifdef DEBUG
b_dumpcache(vol->cache);
# endif
result = b_flush(vol);
FREE(vol->cache);
vol->cache = 0;
done:
return result;
}
/*
* NAME: findbucket()
* DESCRIPTION: locate a bucket in the cache, and/or its hash slot
*/
static
bucket *findbucket(bcache *cache, unsigned long bnum, bucket ***hslot)
{
bucket *b;
*hslot = &cache->hash[bnum & (HFS_HASHSZ - 1)];
for (b = **hslot; b; b = b->hnext)
{
if (INUSE(b) && b->bnum == bnum)
break;
}
return b;
}
/*
* NAME: reuse()
* DESCRIPTION: free a bucket for reuse, flushing if necessary
*/
static
int reuse(bcache *cache, bucket *b, unsigned long bnum)
{
bucket *chain[HFS_BLOCKBUFSZ], *bptr;
int i;
# ifdef DEBUG
if (INUSE(b))
fprintf(stderr, "BLOCK: CACHE reusing bucket containing "
"vol 0x%lx block %lu:%u\n",
(unsigned long) cache->vol, b->bnum, b->count);
# endif
if (INUSE(b) && DIRTY(b))
{
/* flush most recently unused buckets */
for (bptr = b, i = 0; i < HFS_BLOCKBUFSZ; ++i)
{
chain[i] = bptr;
bptr = bptr->cprev;
}
if (flushbuckets(cache->vol, chain, HFS_BLOCKBUFSZ) == -1)
goto fail;
}
b->flags &= ~HFS_BUCKET_INUSE;
b->count = 1;
b->bnum = bnum;
return 0;
fail:
return -1;
}
/*
* NAME: cplace()
* DESCRIPTION: move a bucket to an appropriate place near head of the chain
*/
static
void cplace(bcache *cache, bucket *b)
{
bucket *p;
for (p = cache->tail->cnext; p->count > 1; p = p->cnext)
--p->count;
b->cnext->cprev = b->cprev;
b->cprev->cnext = b->cnext;
if (cache->tail == b)
cache->tail = b->cprev;
b->cprev = p->cprev;
b->cnext = p;
p->cprev->cnext = b;
p->cprev = b;
}
/*
* NAME: hplace()
* DESCRIPTION: move a bucket to the head of its hash slot
*/
static
void hplace(bucket **hslot, bucket *b)
{
if (*hslot != b)
{
if (b->hprev)
*b->hprev = b->hnext;
if (b->hnext)
b->hnext->hprev = b->hprev;
b->hprev = hslot;
b->hnext = *hslot;
if (*hslot)
(*hslot)->hprev = &b->hnext;
*hslot = b;
}
}
/*
* NAME: getbucket()
* DESCRIPTION: fetch a bucket from the cache, or an empty one to be filled
*/
static
bucket *getbucket(bcache *cache, unsigned long bnum, int fill)
{
bucket **hslot, *b, *p, *bptr,
*chain[HFS_BLOCKBUFSZ], **slots[HFS_BLOCKBUFSZ];
b = findbucket(cache, bnum, &hslot);
if (b)
{
/* cache hit; move towards head of cache chain */
++cache->hits;
if (++b->count > b->cprev->count &&
b != cache->tail->cnext)
{
p = b->cprev;
p->cprev->cnext = b;
b->cnext->cprev = p;
p->cnext = b->cnext;
b->cprev = p->cprev;
p->cprev = b;
b->cnext = p;
if (cache->tail == b)
cache->tail = p;
}
}
else
{
/* cache miss; reuse least-used cache bucket */
++cache->misses;
b = cache->tail;
if (reuse(cache, b, bnum) == -1)
goto fail;
if (fill)
{
unsigned int len = 0;
chain[len] = b;
slots[len++] = hslot;
for (bptr = b->cprev;
len < (HFS_BLOCKBUFSZ >> 1) && ++bnum < cache->vol->vlen;
bptr = bptr->cprev)
{
if (findbucket(cache, bnum, &hslot))
break;
if (reuse(cache, bptr, bnum) == -1)
goto fail;
chain[len] = bptr;
slots[len++] = hslot;
}
if (fillbuckets(cache->vol, chain, len) == -1)
goto fail;
while (--len)
{
cplace(cache, chain[len]);
hplace(slots[len], chain[len]);
}
hslot = slots[0];
}
/* move bucket to appropriate place in chain */
cplace(cache, b);
}
/* insert at front of hash chain */
hplace(hslot, b);
return b;
fail:
return 0;
}
/*
* NAME: block->readpb()
* DESCRIPTION: read blocks from the physical medium (bypassing cache)
*/
int b_readpb(hfsvol *vol, unsigned long bnum, block *bp, unsigned int blen)
{
unsigned long nblocks;
# ifdef DEBUG
fprintf(stderr, "BLOCK: READ vol 0x%lx block %lu",
(unsigned long) vol, bnum);
if (blen > 1)
fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1);
else
fprintf(stderr, "\n");
# endif
nblocks = os_seek(&vol->priv, bnum);
if (nblocks == (unsigned long) -1)
goto fail;
if (nblocks != bnum)
ERROR(EIO, "block seek failed for read");
nblocks = os_read(&vol->priv, bp, blen);
if (nblocks == (unsigned long) -1)
goto fail;
if (nblocks != blen)
ERROR(EIO, "incomplete block read");
return 0;
fail:
return -1;
}
/*
* NAME: block->writepb()
* DESCRIPTION: write blocks to the physical medium (bypassing cache)
*/
int b_writepb(hfsvol *vol, unsigned long bnum, const block *bp,
unsigned int blen)
{
unsigned long nblocks;
# ifdef DEBUG
fprintf(stderr, "BLOCK: WRITE vol 0x%lx block %lu",
(unsigned long) vol, bnum);
if (blen > 1)
fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1);
else
fprintf(stderr, "\n");
# endif
nblocks = os_seek(&vol->priv, bnum);
if (nblocks == (unsigned long) -1)
goto fail;
if (nblocks != bnum)
ERROR(EIO, "block seek failed for write");
nblocks = os_write(&vol->priv, bp, blen);
if (nblocks == (unsigned long) -1)
goto fail;
if (nblocks != blen)
ERROR(EIO, "incomplete block write");
return 0;
fail:
return -1;
}
/*
* NAME: block->readlb()
* DESCRIPTION: read a logical block from a volume (or from the cache)
*/
int b_readlb(hfsvol *vol, unsigned long bnum, block *bp)
{
if (vol->vlen > 0 && bnum >= vol->vlen)
ERROR(EIO, "read nonexistent logical block");
if (vol->cache)
{
bucket *b;
b = getbucket(vol->cache, bnum, 1);
if (b == 0)
goto fail;
memcpy(bp, b->data, HFS_BLOCKSZ);
}
else
{
if (b_readpb(vol, vol->vstart + bnum, bp, 1) == -1)
goto fail;
}
return 0;
fail:
return -1;
}
/*
* NAME: block->writelb()
* DESCRIPTION: write a logical block to a volume (or to the cache)
*/
int b_writelb(hfsvol *vol, unsigned long bnum, const block *bp)
{
if (vol->vlen > 0 && bnum >= vol->vlen)
ERROR(EIO, "write nonexistent logical block");
if (vol->cache)
{
bucket *b;
b = getbucket(vol->cache, bnum, 0);
if (b == 0)
goto fail;
if (! INUSE(b) ||
memcmp(b->data, bp, HFS_BLOCKSZ) != 0)
{
memcpy(b->data, bp, HFS_BLOCKSZ);
b->flags |= HFS_BUCKET_INUSE | HFS_BUCKET_DIRTY;
}
}
else
{
if (b_writepb(vol, vol->vstart + bnum, bp, 1) == -1)
goto fail;
}
return 0;
fail:
return -1;
}
/*
* NAME: block->readab()
* DESCRIPTION: read a block from an allocation block from a volume
*/
int b_readab(hfsvol *vol, unsigned int anum, unsigned int idx, block *bp)
{
/* verify the allocation block exists and is marked as in-use */
if (anum >= vol->mdb.drNmAlBlks)
ERROR(EIO, "read nonexistent allocation block");
else if (vol->vbm && ! BMTST(vol->vbm, anum))
ERROR(EIO, "read unallocated block");
return b_readlb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + idx, bp);
fail:
return -1;
}
/*
* NAME: block->writeab()
* DESCRIPTION: write a block to an allocation block to a volume
*/
int b_writeab(hfsvol *vol,
unsigned int anum, unsigned int idx, const block *bp)
{
/* verify the allocation block exists and is marked as in-use */
if (anum >= vol->mdb.drNmAlBlks)
ERROR(EIO, "write nonexistent allocation block");
else if (vol->vbm && ! BMTST(vol->vbm, anum))
ERROR(EIO, "write unallocated block");
if (v_dirty(vol) == -1)
goto fail;
return b_writelb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + idx, bp);
fail:
return -1;
}
/*
* NAME: block->size()
* DESCRIPTION: return the number of physical blocks on a volume's medium
*/
unsigned long b_size(hfsvol *vol)
{
unsigned long low, high, mid;
block b;
high = os_seek(&vol->priv, -1);
if (high != (unsigned long) -1 && high > 0)
return high;
/* manual size detection: first check there is at least 1 block in medium */
if (b_readpb(vol, 0, &b, 1) == -1)
ERROR(EIO, "size of medium indeterminable or empty");
for (low = 0, high = 2880;
high > 0 && b_readpb(vol, high - 1, &b, 1) != -1;
high <<= 1)
low = high - 1;
if (high == 0)
ERROR(EIO, "size of medium indeterminable or too large");
/* common case: 1440K floppy */
if (low == 2879 && b_readpb(vol, 2880, &b, 1) == -1)
return 2880;
/* binary search for other sizes */
while (low < high - 1)
{
mid = (low + high) >> 1;
if (b_readpb(vol, mid, &b, 1) == -1)
high = mid;
else
low = mid;
}
return low + 1;
fail:
return 0;
}

40
libhfs/block.h Normal file
View File

@ -0,0 +1,40 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
int b_init(hfsvol *);
int b_flush(hfsvol *);
int b_finish(hfsvol *);
int b_readpb(hfsvol *, unsigned long, block *, unsigned int);
int b_writepb(hfsvol *, unsigned long, const block *, unsigned int);
int b_readlb(hfsvol *, unsigned long, block *);
int b_writelb(hfsvol *, unsigned long, const block *);
int b_readab(hfsvol *, unsigned int, unsigned int, block *);
int b_writeab(hfsvol *, unsigned int, unsigned int, const block *);
unsigned long b_size(hfsvol *);
# ifdef DEBUG
void b_showstats(const bcache *);
void b_dumpcache(const bcache *);
# endif

700
libhfs/btree.c Normal file
View File

@ -0,0 +1,700 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "btree.h"
# include "data.h"
# include "file.h"
# include "block.h"
# include "node.h"
/*
* NAME: btree->getnode()
* DESCRIPTION: retrieve a numbered node from a B*-tree file
*/
int bt_getnode(node *np, btree *bt, unsigned long nnum)
{
block *bp = &np->data;
const byte *ptr;
int i;
np->bt = bt;
np->nnum = nnum;
# if 0
fprintf(stderr, "BTREE: GET vol \"%s\" btree \"%s\" node %lu\n",
bt->f.vol->mdb.drVN, bt->f.name, np->nnum);
# endif
/* verify the node exists and is marked as in-use */
if (nnum > 0 && nnum >= bt->hdr.bthNNodes)
ERROR(EIO, "read nonexistent b*-tree node");
else if (bt->map && ! BMTST(bt->map, nnum))
ERROR(EIO, "read unallocated b*-tree node");
if (f_getblock(&bt->f, nnum, bp) == -1)
goto fail;
ptr = *bp;
d_fetchul(&ptr, &np->nd.ndFLink);
d_fetchul(&ptr, &np->nd.ndBLink);
d_fetchsb(&ptr, &np->nd.ndType);
d_fetchsb(&ptr, &np->nd.ndNHeight);
d_fetchuw(&ptr, &np->nd.ndNRecs);
d_fetchsw(&ptr, &np->nd.ndResv2);
if (np->nd.ndNRecs > HFS_MAX_NRECS)
ERROR(EIO, "too many b*-tree node records");
i = np->nd.ndNRecs + 1;
ptr = *bp + HFS_BLOCKSZ - (2 * i);
while (i--)
d_fetchuw(&ptr, &np->roff[i]);
return 0;
fail:
return -1;
}
/*
* NAME: btree->putnode()
* DESCRIPTION: store a numbered node into a B*-tree file
*/
int bt_putnode(node *np)
{
btree *bt = np->bt;
block *bp = &np->data;
byte *ptr;
int i;
# if 0
fprintf(stderr, "BTREE: PUT vol \"%s\" btree \"%s\" node %lu\n",
bt->f.vol->mdb.drVN, bt->f.name, np->nnum);
# endif
/* verify the node exists and is marked as in-use */
if (np->nnum > 0 && np->nnum >= bt->hdr.bthNNodes)
ERROR(EIO, "write nonexistent b*-tree node");
else if (bt->map && ! BMTST(bt->map, np->nnum))
ERROR(EIO, "write unallocated b*-tree node");
ptr = *bp;
d_storeul(&ptr, np->nd.ndFLink);
d_storeul(&ptr, np->nd.ndBLink);
d_storesb(&ptr, np->nd.ndType);
d_storesb(&ptr, np->nd.ndNHeight);
d_storeuw(&ptr, np->nd.ndNRecs);
d_storesw(&ptr, np->nd.ndResv2);
if (np->nd.ndNRecs > HFS_MAX_NRECS)
ERROR(EIO, "too many b*-tree node records");
i = np->nd.ndNRecs + 1;
ptr = *bp + HFS_BLOCKSZ - (2 * i);
while (i--)
d_storeuw(&ptr, np->roff[i]);
return f_putblock(&bt->f, np->nnum, bp);
fail:
return -1;
}
/*
* NAME: btree->readhdr()
* DESCRIPTION: read the header node of a B*-tree
*/
int bt_readhdr(btree *bt)
{
const byte *ptr;
byte *map = 0;
int i;
unsigned long nnum;
if (bt_getnode(&bt->hdrnd, bt, 0) == -1)
goto fail;
if (bt->hdrnd.nd.ndType != ndHdrNode ||
bt->hdrnd.nd.ndNRecs != 3 ||
bt->hdrnd.roff[0] != 0x00e ||
bt->hdrnd.roff[1] != 0x078 ||
bt->hdrnd.roff[2] != 0x0f8 ||
bt->hdrnd.roff[3] != 0x1f8)
ERROR(EIO, "malformed b*-tree header node");
/* read header record */
ptr = HFS_NODEREC(bt->hdrnd, 0);
d_fetchuw(&ptr, &bt->hdr.bthDepth);
d_fetchul(&ptr, &bt->hdr.bthRoot);
d_fetchul(&ptr, &bt->hdr.bthNRecs);
d_fetchul(&ptr, &bt->hdr.bthFNode);
d_fetchul(&ptr, &bt->hdr.bthLNode);
d_fetchuw(&ptr, &bt->hdr.bthNodeSize);
d_fetchuw(&ptr, &bt->hdr.bthKeyLen);
d_fetchul(&ptr, &bt->hdr.bthNNodes);
d_fetchul(&ptr, &bt->hdr.bthFree);
for (i = 0; i < 76; ++i)
d_fetchsb(&ptr, &bt->hdr.bthResv[i]);
if (bt->hdr.bthNodeSize != HFS_BLOCKSZ)
ERROR(EINVAL, "unsupported b*-tree node size");
/* read map record; construct btree bitmap */
/* don't set bt->map until we're done, since getnode() checks it */
map = ALLOC(byte, HFS_MAP1SZ);
if (map == 0)
ERROR(ENOMEM, 0);
memcpy(map, HFS_NODEREC(bt->hdrnd, 2), HFS_MAP1SZ);
bt->mapsz = HFS_MAP1SZ;
/* read continuation map records, if any */
nnum = bt->hdrnd.nd.ndFLink;
while (nnum)
{
node n;
byte *newmap;
if (bt_getnode(&n, bt, nnum) == -1)
goto fail;
if (n.nd.ndType != ndMapNode ||
n.nd.ndNRecs != 1 ||
n.roff[0] != 0x00e ||
n.roff[1] != 0x1fa)
ERROR(EIO, "malformed b*-tree map node");
newmap = REALLOC(map, byte, bt->mapsz + HFS_MAPXSZ);
if (newmap == 0)
ERROR(ENOMEM, 0);
map = newmap;
memcpy(map + bt->mapsz, HFS_NODEREC(n, 0), HFS_MAPXSZ);
bt->mapsz += HFS_MAPXSZ;
nnum = n.nd.ndFLink;
}
bt->map = map;
return 0;
fail:
FREE(map);
return -1;
}
/*
* NAME: btree->writehdr()
* DESCRIPTION: write the header node of a B*-tree
*/
int bt_writehdr(btree *bt)
{
byte *ptr, *map;
unsigned long mapsz, nnum;
int i;
ASSERT(bt->hdrnd.bt == bt &&
bt->hdrnd.nnum == 0 &&
bt->hdrnd.nd.ndType == ndHdrNode &&
bt->hdrnd.nd.ndNRecs == 3);
ptr = HFS_NODEREC(bt->hdrnd, 0);
d_storeuw(&ptr, bt->hdr.bthDepth);
d_storeul(&ptr, bt->hdr.bthRoot);
d_storeul(&ptr, bt->hdr.bthNRecs);
d_storeul(&ptr, bt->hdr.bthFNode);
d_storeul(&ptr, bt->hdr.bthLNode);
d_storeuw(&ptr, bt->hdr.bthNodeSize);
d_storeuw(&ptr, bt->hdr.bthKeyLen);
d_storeul(&ptr, bt->hdr.bthNNodes);
d_storeul(&ptr, bt->hdr.bthFree);
for (i = 0; i < 76; ++i)
d_storesb(&ptr, bt->hdr.bthResv[i]);
memcpy(HFS_NODEREC(bt->hdrnd, 2), bt->map, HFS_MAP1SZ);
if (bt_putnode(&bt->hdrnd) == -1)
goto fail;
map = bt->map + HFS_MAP1SZ;
mapsz = bt->mapsz - HFS_MAP1SZ;
nnum = bt->hdrnd.nd.ndFLink;
while (mapsz)
{
node n;
if (nnum == 0)
ERROR(EIO, "truncated b*-tree map");
if (bt_getnode(&n, bt, nnum) == -1)
goto fail;
if (n.nd.ndType != ndMapNode ||
n.nd.ndNRecs != 1 ||
n.roff[0] != 0x00e ||
n.roff[1] != 0x1fa)
ERROR(EIO, "malformed b*-tree map node");
memcpy(HFS_NODEREC(n, 0), map, HFS_MAPXSZ);
if (bt_putnode(&n) == -1)
goto fail;
map += HFS_MAPXSZ;
mapsz -= HFS_MAPXSZ;
nnum = n.nd.ndFLink;
}
bt->flags &= ~HFS_BT_UPDATE_HDR;
return 0;
fail:
return -1;
}
/* High-Level B*-Tree Routines ============================================= */
/*
* NAME: btree->space()
* DESCRIPTION: assert space for new records, or extend the file
*/
int bt_space(btree *bt, unsigned int nrecs)
{
unsigned int nnodes;
long space;
nnodes = nrecs * (bt->hdr.bthDepth + 1);
if (nnodes <= bt->hdr.bthFree)
goto done;
/* make sure the extents tree has room too */
if (bt != &bt->f.vol->ext)
{
if (bt_space(&bt->f.vol->ext, 1) == -1)
goto fail;
}
space = f_alloc(&bt->f);
if (space == -1)
goto fail;
nnodes = space * (bt->f.vol->mdb.drAlBlkSiz / bt->hdr.bthNodeSize);
bt->hdr.bthNNodes += nnodes;
bt->hdr.bthFree += nnodes;
bt->flags |= HFS_BT_UPDATE_HDR;
bt->f.vol->flags |= HFS_VOL_UPDATE_ALTMDB;
while (bt->hdr.bthNNodes > bt->mapsz * 8)
{
byte *newmap;
node mapnd;
/* extend tree map */
newmap = REALLOC(bt->map, byte, bt->mapsz + HFS_MAPXSZ);
if (newmap == 0)
ERROR(ENOMEM, 0);
memset(newmap + bt->mapsz, 0, HFS_MAPXSZ);
bt->map = newmap;
bt->mapsz += HFS_MAPXSZ;
n_init(&mapnd, bt, ndMapNode, 0);
if (n_new(&mapnd) == -1)
goto fail;
mapnd.nd.ndNRecs = 1;
mapnd.roff[1] = 0x1fa;
/* link the new map node */
if (bt->hdrnd.nd.ndFLink == 0)
{
bt->hdrnd.nd.ndFLink = mapnd.nnum;
mapnd.nd.ndBLink = 0;
}
else
{
node n;
unsigned long nnum;
nnum = bt->hdrnd.nd.ndFLink;
while (1)
{
if (bt_getnode(&n, bt, nnum) == -1)
goto fail;
if (n.nd.ndFLink == 0)
break;
nnum = n.nd.ndFLink;
}
n.nd.ndFLink = mapnd.nnum;
mapnd.nd.ndBLink = n.nnum;
if (bt_putnode(&n) == -1)
goto fail;
}
if (bt_putnode(&mapnd) == -1)
goto fail;
}
done:
return 0;
fail:
return -1;
}
/*
* NAME: insertx()
* DESCRIPTION: recursively locate a node and insert a record
*/
static
int insertx(node *np, byte *record, int *reclen)
{
node child;
byte *rec;
int result = 0;
if (n_search(np, record))
ERROR(EIO, "b*-tree record already exists");
switch (np->nd.ndType)
{
case ndIndxNode:
if (np->rnum == -1)
rec = HFS_NODEREC(*np, 0);
else
rec = HFS_NODEREC(*np, np->rnum);
if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 ||
insertx(&child, record, reclen) == -1)
goto fail;
if (np->rnum == -1)
{
n_index(&child, rec, 0);
if (*reclen == 0)
{
result = bt_putnode(np);
goto done;
}
}
if (*reclen)
result = n_insert(np, record, reclen);
break;
case ndLeafNode:
result = n_insert(np, record, reclen);
break;
default:
ERROR(EIO, "unexpected b*-tree node");
}
done:
return result;
fail:
return -1;
}
/*
* NAME: btree->insert()
* DESCRIPTION: insert a new node record into a tree
*/
int bt_insert(btree *bt, const byte *record, unsigned int reclen)
{
node root;
byte newrec[HFS_MAX_RECLEN];
if (bt->hdr.bthRoot == 0)
{
/* create root node */
n_init(&root, bt, ndLeafNode, 1);
if (n_new(&root) == -1 ||
bt_putnode(&root) == -1)
goto fail;
bt->hdr.bthDepth = 1;
bt->hdr.bthRoot = root.nnum;
bt->hdr.bthFNode = root.nnum;
bt->hdr.bthLNode = root.nnum;
bt->flags |= HFS_BT_UPDATE_HDR;
}
else if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1)
goto fail;
memcpy(newrec, record, reclen);
if (insertx(&root, newrec, &reclen) == -1)
goto fail;
if (reclen)
{
byte oroot[HFS_MAX_RECLEN];
unsigned int orootlen;
/* root node was split; create a new root */
n_index(&root, oroot, &orootlen);
n_init(&root, bt, ndIndxNode, root.nd.ndNHeight + 1);
if (n_new(&root) == -1)
goto fail;
++bt->hdr.bthDepth;
bt->hdr.bthRoot = root.nnum;
bt->flags |= HFS_BT_UPDATE_HDR;
/* insert index records for new root */
n_search(&root, oroot);
n_insertx(&root, oroot, orootlen);
n_search(&root, newrec);
n_insertx(&root, newrec, reclen);
if (bt_putnode(&root) == -1)
goto fail;
}
++bt->hdr.bthNRecs;
bt->flags |= HFS_BT_UPDATE_HDR;
return 0;
fail:
return -1;
}
/*
* NAME: deletex()
* DESCRIPTION: recursively locate a node and delete a record
*/
static
int deletex(node *np, const byte *key, byte *record, int *flag)
{
node child;
byte *rec;
int found, result = 0;
found = n_search(np, key);
switch (np->nd.ndType)
{
case ndIndxNode:
if (np->rnum == -1)
ERROR(EIO, "b*-tree record not found");
rec = HFS_NODEREC(*np, np->rnum);
if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 ||
deletex(&child, key, rec, flag) == -1)
goto fail;
if (*flag)
{
*flag = 0;
if (HFS_RECKEYLEN(rec) == 0)
{
result = n_delete(np, record, flag);
break;
}
if (np->rnum == 0)
{
/* propagate index record change into parent */
n_index(np, record, 0);
*flag = 1;
}
result = bt_putnode(np);
}
break;
case ndLeafNode:
if (found == 0)
ERROR(EIO, "b*-tree record not found");
result = n_delete(np, record, flag);
break;
default:
ERROR(EIO, "unexpected b*-tree node");
}
return result;
fail:
return -1;
}
/*
* NAME: btree->delete()
* DESCRIPTION: remove a node record from a tree
*/
int bt_delete(btree *bt, const byte *key)
{
node root;
byte record[HFS_MAX_RECLEN];
int flag = 0;
if (bt->hdr.bthRoot == 0)
ERROR(EIO, "empty b*-tree");
if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1 ||
deletex(&root, key, record, &flag) == -1)
goto fail;
if (bt->hdr.bthDepth > 1 && root.nd.ndNRecs == 1)
{
const byte *rec;
/* root only has one record; eliminate it and decrease the tree depth */
rec = HFS_NODEREC(root, 0);
--bt->hdr.bthDepth;
bt->hdr.bthRoot = d_getul(HFS_RECDATA(rec));
if (n_free(&root) == -1)
goto fail;
}
else if (bt->hdr.bthDepth == 1 && root.nd.ndNRecs == 0)
{
/* root node was deleted */
bt->hdr.bthDepth = 0;
bt->hdr.bthRoot = 0;
}
--bt->hdr.bthNRecs;
bt->flags |= HFS_BT_UPDATE_HDR;
return 0;
fail:
return -1;
}
/*
* NAME: btree->search()
* DESCRIPTION: locate a data record given a search key
*/
int bt_search(btree *bt, const byte *key, node *np)
{
int found = 0;
unsigned long nnum;
nnum = bt->hdr.bthRoot;
if (nnum == 0)
ERROR(ENOENT, 0);
while (1)
{
const byte *rec;
if (bt_getnode(np, bt, nnum) == -1)
{
found = -1;
goto fail;
}
found = n_search(np, key);
switch (np->nd.ndType)
{
case ndIndxNode:
if (np->rnum == -1)
ERROR(ENOENT, 0);
rec = HFS_NODEREC(*np, np->rnum);
nnum = d_getul(HFS_RECDATA(rec));
break;
case ndLeafNode:
if (! found)
ERROR(ENOENT, 0);
goto done;
default:
found = -1;
ERROR(EIO, "unexpected b*-tree node");
}
}
done:
fail:
return found;
}

33
libhfs/btree.h Normal file
View File

@ -0,0 +1,33 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
int bt_getnode(node *, btree *, unsigned long);
int bt_putnode(node *);
int bt_readhdr(btree *);
int bt_writehdr(btree *);
int bt_space(btree *, unsigned int);
int bt_insert(btree *, const byte *, unsigned int);
int bt_delete(btree *, const byte *);
int bt_search(btree *, const byte *, node *);

60
libhfs/config.h Normal file
View File

@ -0,0 +1,60 @@
/* config.h. Generated automatically by configure. */
/* config.h.in. Generated automatically from configure.in by autoheader. */
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
/*****************************************************************************
* Definitions selected automatically by `configure' *
*****************************************************************************/
/* Define to empty if the keyword does not work. */
/* #undef const */
/* Define to `unsigned' if <sys/types.h> doesn't define. */
/* #undef size_t */
/* Define if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define if your <sys/time.h> declares struct tm. */
/* #undef TM_IN_SYS_TIME */
/* Define if you want to enable diagnostic debugging support. */
/* #undef DEBUG */
/* Define if you have the mktime function. */
#define HAVE_MKTIME 1
/* Define if you have the <fcntl.h> header file. */
#define HAVE_FCNTL_H 1
/* Define if you have the <unistd.h> header file. */
#ifndef _WIN32
# define HAVE_UNISTD_H 1
#endif
/*****************************************************************************
* End of automatically configured definitions *
*****************************************************************************/
# ifdef DEBUG
# include <stdio.h>
# endif

485
libhfs/data.c Normal file
View File

@ -0,0 +1,485 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <string.h>
# include <time.h>
# ifdef TM_IN_SYS_TIME
# include <sys/time.h>
# endif
# include "data.h"
# define TIMEDIFF 2082844800UL
static
time_t tzdiff = -1;
const
unsigned char hfs_charorder[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69,
0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f,
0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1,
0xa3, 0xa5, 0xa8, 0xaa, 0xab, 0xac, 0xad, 0xae,
0x54, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69,
0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f,
0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1,
0xa3, 0xa5, 0xa8, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
0x4c, 0x50, 0x5c, 0x62, 0x7d, 0x81, 0x9a, 0x55,
0x4a, 0x56, 0x4c, 0x4e, 0x50, 0x5c, 0x62, 0x64,
0x65, 0x66, 0x6f, 0x70, 0x71, 0x72, 0x7d, 0x89,
0x8a, 0x8b, 0x81, 0x83, 0x9c, 0x9d, 0x9e, 0x9a,
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0x95,
0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0x52, 0x85,
0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
0xc9, 0xca, 0xcb, 0x57, 0x8c, 0xcc, 0x52, 0x85,
0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0x26,
0x27, 0xd4, 0x20, 0x4a, 0x4e, 0x83, 0x87, 0x87,
0xd5, 0xd6, 0x24, 0x25, 0x2d, 0x2e, 0xd7, 0xd8,
0xa7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/*
* NAME: data->getsb()
* DESCRIPTION: marshal 1 signed byte into local host format
*/
signed char d_getsb(register const unsigned char *ptr)
{
return ptr[0];
}
/*
* NAME: data->getub()
* DESCRIPTION: marshal 1 unsigned byte into local host format
*/
unsigned char d_getub(register const unsigned char *ptr)
{
return ptr[0];
}
/*
* NAME: data->getsw()
* DESCRIPTION: marshal 2 signed bytes into local host format
*/
signed short d_getsw(register const unsigned char *ptr)
{
return
((( signed short) ptr[0] << 8) |
((unsigned short) ptr[1] << 0));
}
/*
* NAME: data->getuw()
* DESCRIPTION: marshal 2 unsigned bytes into local host format
*/
unsigned short d_getuw(register const unsigned char *ptr)
{
return
(((unsigned short) ptr[0] << 8) |
((unsigned short) ptr[1] << 0));
}
/*
* NAME: data->getsl()
* DESCRIPTION: marshal 4 signed bytes into local host format
*/
signed long d_getsl(register const unsigned char *ptr)
{
return
((( signed long) ptr[0] << 24) |
((unsigned long) ptr[1] << 16) |
((unsigned long) ptr[2] << 8) |
((unsigned long) ptr[3] << 0));
}
/*
* NAME: data->getul()
* DESCRIPTION: marshal 4 unsigned bytes into local host format
*/
unsigned long d_getul(register const unsigned char *ptr)
{
return
(((unsigned long) ptr[0] << 24) |
((unsigned long) ptr[1] << 16) |
((unsigned long) ptr[2] << 8) |
((unsigned long) ptr[3] << 0));
}
/*
* NAME: data->putsb()
* DESCRIPTION: marshal 1 signed byte out in big-endian format
*/
void d_putsb(register unsigned char *ptr,
register signed char data)
{
*ptr = data;
}
/*
* NAME: data->putub()
* DESCRIPTION: marshal 1 unsigned byte out in big-endian format
*/
void d_putub(register unsigned char *ptr,
register unsigned char data)
{
*ptr = data;
}
/*
* NAME: data->putsw()
* DESCRIPTION: marshal 2 signed bytes out in big-endian format
*/
void d_putsw(register unsigned char *ptr,
register signed short data)
{
*ptr++ = ((unsigned short) data & 0xff00) >> 8;
*ptr = ((unsigned short) data & 0x00ff) >> 0;
}
/*
* NAME: data->putuw()
* DESCRIPTION: marshal 2 unsigned bytes out in big-endian format
*/
void d_putuw(register unsigned char *ptr,
register unsigned short data)
{
*ptr++ = (data & 0xff00) >> 8;
*ptr = (data & 0x00ff) >> 0;
}
/*
* NAME: data->putsl()
* DESCRIPTION: marshal 4 signed bytes out in big-endian format
*/
void d_putsl(register unsigned char *ptr,
register signed long data)
{
*ptr++ = ((unsigned long) data & 0xff000000UL) >> 24;
*ptr++ = ((unsigned long) data & 0x00ff0000UL) >> 16;
*ptr++ = ((unsigned long) data & 0x0000ff00UL) >> 8;
*ptr = ((unsigned long) data & 0x000000ffUL) >> 0;
}
/*
* NAME: data->putul()
* DESCRIPTION: marshal 4 unsigned bytes out in big-endian format
*/
void d_putul(register unsigned char *ptr,
register unsigned long data)
{
*ptr++ = (data & 0xff000000UL) >> 24;
*ptr++ = (data & 0x00ff0000UL) >> 16;
*ptr++ = (data & 0x0000ff00UL) >> 8;
*ptr = (data & 0x000000ffUL) >> 0;
}
/*
* NAME: data->fetchsb()
* DESCRIPTION: incrementally retrieve a signed byte of data
*/
void d_fetchsb(register const unsigned char **ptr,
register signed char *dest)
{
*dest = *(*ptr)++;
}
/*
* NAME: data->fetchub()
* DESCRIPTION: incrementally retrieve an unsigned byte of data
*/
void d_fetchub(register const unsigned char **ptr,
register unsigned char *dest)
{
*dest = *(*ptr)++;
}
/*
* NAME: data->fetchsw()
* DESCRIPTION: incrementally retrieve a signed word of data
*/
void d_fetchsw(register const unsigned char **ptr,
register signed short *dest)
{
*dest =
((( signed short) (*ptr)[0] << 8) |
((unsigned short) (*ptr)[1] << 0));
*ptr += 2;
}
/*
* NAME: data->fetchuw()
* DESCRIPTION: incrementally retrieve an unsigned word of data
*/
void d_fetchuw(register const unsigned char **ptr,
register unsigned short *dest)
{
*dest =
(((unsigned short) (*ptr)[0] << 8) |
((unsigned short) (*ptr)[1] << 0));
*ptr += 2;
}
/*
* NAME: data->fetchsl()
* DESCRIPTION: incrementally retrieve a signed long word of data
*/
void d_fetchsl(register const unsigned char **ptr,
register signed long *dest)
{
*dest =
((( signed long) (*ptr)[0] << 24) |
((unsigned long) (*ptr)[1] << 16) |
((unsigned long) (*ptr)[2] << 8) |
((unsigned long) (*ptr)[3] << 0));
*ptr += 4;
}
/*
* NAME: data->fetchul()
* DESCRIPTION: incrementally retrieve an unsigned long word of data
*/
void d_fetchul(register const unsigned char **ptr,
register unsigned long *dest)
{
*dest =
(((unsigned long) (*ptr)[0] << 24) |
((unsigned long) (*ptr)[1] << 16) |
((unsigned long) (*ptr)[2] << 8) |
((unsigned long) (*ptr)[3] << 0));
*ptr += 4;
}
/*
* NAME: data->storesb()
* DESCRIPTION: incrementally store a signed byte of data
*/
void d_storesb(register unsigned char **ptr,
register signed char data)
{
*(*ptr)++ = data;
}
/*
* NAME: data->storeub()
* DESCRIPTION: incrementally store an unsigned byte of data
*/
void d_storeub(register unsigned char **ptr,
register unsigned char data)
{
*(*ptr)++ = data;
}
/*
* NAME: data->storesw()
* DESCRIPTION: incrementally store a signed word of data
*/
void d_storesw(register unsigned char **ptr,
register signed short data)
{
*(*ptr)++ = ((unsigned short) data & 0xff00) >> 8;
*(*ptr)++ = ((unsigned short) data & 0x00ff) >> 0;
}
/*
* NAME: data->storeuw()
* DESCRIPTION: incrementally store an unsigned word of data
*/
void d_storeuw(register unsigned char **ptr,
register unsigned short data)
{
*(*ptr)++ = (data & 0xff00) >> 8;
*(*ptr)++ = (data & 0x00ff) >> 0;
}
/*
* NAME: data->storesl()
* DESCRIPTION: incrementally store a signed long word of data
*/
void d_storesl(register unsigned char **ptr,
register signed long data)
{
*(*ptr)++ = ((unsigned long) data & 0xff000000UL) >> 24;
*(*ptr)++ = ((unsigned long) data & 0x00ff0000UL) >> 16;
*(*ptr)++ = ((unsigned long) data & 0x0000ff00UL) >> 8;
*(*ptr)++ = ((unsigned long) data & 0x000000ffUL) >> 0;
}
/*
* NAME: data->storeul()
* DESCRIPTION: incrementally store an unsigned long word of data
*/
void d_storeul(register unsigned char **ptr,
register unsigned long data)
{
*(*ptr)++ = (data & 0xff000000UL) >> 24;
*(*ptr)++ = (data & 0x00ff0000UL) >> 16;
*(*ptr)++ = (data & 0x0000ff00UL) >> 8;
*(*ptr)++ = (data & 0x000000ffUL) >> 0;
}
/*
* NAME: data->fetchstr()
* DESCRIPTION: incrementally retrieve a string
*/
void d_fetchstr(const unsigned char **ptr, char *dest, unsigned size)
{
unsigned len;
len = d_getub(*ptr);
if (len > 0 && len < size)
memcpy(dest, *ptr + 1, len);
else
len = 0;
dest[len] = 0;
*ptr += size;
}
/*
* NAME: data->storestr()
* DESCRIPTION: incrementally store a string
*/
void d_storestr(unsigned char **ptr, const char *src, unsigned size)
{
unsigned len;
len = strlen(src);
if (len > --size)
len = 0;
d_storeub(ptr, (unsigned char) len);
memcpy(*ptr, src, len);
memset(*ptr + len, 0, size - len);
*ptr += size;
}
/*
* NAME: data->relstring()
* DESCRIPTION: compare two strings as per MacOS for HFS
*/
int d_relstring(const char *str1, const char *str2)
{
register int diff;
while (*str1 && *str2)
{
diff = hfs_charorder[(unsigned char) *str1] -
hfs_charorder[(unsigned char) *str2];
if (diff)
return diff;
++str1, ++str2;
}
if (! *str1 && *str2)
return -1;
else if (*str1 && ! *str2)
return 1;
return 0;
}
/*
* NAME: calctzdiff()
* DESCRIPTION: calculate the timezone difference between local time and UTC
*/
static
void calctzdiff(void)
{
# ifdef HAVE_MKTIME
time_t t;
int isdst;
struct tm tm;
const struct tm *tmp;
time(&t);
isdst = localtime(&t)->tm_isdst;
tmp = gmtime(&t);
if (tmp)
{
tm = *tmp;
tm.tm_isdst = isdst;
tzdiff = t - mktime(&tm);
}
else
tzdiff = 0;
# else
tzdiff = 0;
# endif
}
/*
* NAME: data->ltime()
* DESCRIPTION: convert MacOS time to local time
*/
time_t d_ltime(unsigned long mtime)
{
if (tzdiff == -1)
calctzdiff();
return (time_t) (mtime - TIMEDIFF) - tzdiff;
}
/*
* NAME: data->mtime()
* DESCRIPTION: convert local time to MacOS time
*/
unsigned long d_mtime(time_t ltime)
{
if (tzdiff == -1)
calctzdiff();
return (unsigned long) (ltime + tzdiff) + TIMEDIFF;
}

58
libhfs/data.h Normal file
View File

@ -0,0 +1,58 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
extern const unsigned char hfs_charorder[];
signed char d_getsb(register const unsigned char *);
unsigned char d_getub(register const unsigned char *);
signed short d_getsw(register const unsigned char *);
unsigned short d_getuw(register const unsigned char *);
signed long d_getsl(register const unsigned char *);
unsigned long d_getul(register const unsigned char *);
void d_putsb(register unsigned char *, register signed char);
void d_putub(register unsigned char *, register unsigned char);
void d_putsw(register unsigned char *, register signed short);
void d_putuw(register unsigned char *, register unsigned short);
void d_putsl(register unsigned char *, register signed long);
void d_putul(register unsigned char *, register unsigned long);
void d_fetchsb(register const unsigned char **, register signed char *);
void d_fetchub(register const unsigned char **, register unsigned char *);
void d_fetchsw(register const unsigned char **, register signed short *);
void d_fetchuw(register const unsigned char **, register unsigned short *);
void d_fetchsl(register const unsigned char **, register signed long *);
void d_fetchul(register const unsigned char **, register unsigned long *);
void d_storesb(register unsigned char **, register signed char);
void d_storeub(register unsigned char **, register unsigned char);
void d_storesw(register unsigned char **, register signed short);
void d_storeuw(register unsigned char **, register unsigned short);
void d_storesl(register unsigned char **, register signed long);
void d_storeul(register unsigned char **, register unsigned long);
void d_fetchstr(const unsigned char **, char *, unsigned);
void d_storestr(unsigned char **, const char *, unsigned);
int d_relstring(const char *, const char *);
time_t d_ltime(unsigned long);
unsigned long d_mtime(time_t);

520
libhfs/file.c Normal file
View File

@ -0,0 +1,520 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "file.h"
# include "btree.h"
# include "record.h"
# include "volume.h"
/*
* NAME: file->init()
* DESCRIPTION: initialize file structure
*/
void f_init(hfsfile *file, hfsvol *vol, long cnid, const char *name)
{
int i;
file->vol = vol;
file->parid = 0;
strcpy(file->name, name);
file->cat.cdrType = cdrFilRec;
file->cat.cdrResrv2 = 0;
file->cat.u.fil.filFlags = 0;
file->cat.u.fil.filTyp = 0;
file->cat.u.fil.filUsrWds.fdType = 0;
file->cat.u.fil.filUsrWds.fdCreator = 0;
file->cat.u.fil.filUsrWds.fdFlags = 0;
file->cat.u.fil.filUsrWds.fdLocation.v = 0;
file->cat.u.fil.filUsrWds.fdLocation.h = 0;
file->cat.u.fil.filUsrWds.fdFldr = 0;
file->cat.u.fil.filFlNum = cnid;
file->cat.u.fil.filStBlk = 0;
file->cat.u.fil.filLgLen = 0;
file->cat.u.fil.filPyLen = 0;
file->cat.u.fil.filRStBlk = 0;
file->cat.u.fil.filRLgLen = 0;
file->cat.u.fil.filRPyLen = 0;
file->cat.u.fil.filCrDat = 0;
file->cat.u.fil.filMdDat = 0;
file->cat.u.fil.filBkDat = 0;
file->cat.u.fil.filFndrInfo.fdIconID = 0;
for (i = 0; i < 4; ++i)
file->cat.u.fil.filFndrInfo.fdUnused[i] = 0;
file->cat.u.fil.filFndrInfo.fdComment = 0;
file->cat.u.fil.filFndrInfo.fdPutAway = 0;
file->cat.u.fil.filClpSize = 0;
for (i = 0; i < 3; ++i)
{
file->cat.u.fil.filExtRec[i].xdrStABN = 0;
file->cat.u.fil.filExtRec[i].xdrNumABlks = 0;
file->cat.u.fil.filRExtRec[i].xdrStABN = 0;
file->cat.u.fil.filRExtRec[i].xdrNumABlks = 0;
}
file->cat.u.fil.filResrv = 0;
f_selectfork(file, fkData);
file->flags = 0;
file->prev = 0;
file->next = 0;
}
/*
* NAME: file->selectfork()
* DESCRIPTION: choose a fork for file operations
*/
void f_selectfork(hfsfile *file, int fork)
{
file->fork = fork;
memcpy(&file->ext, fork == fkData ?
&file->cat.u.fil.filExtRec : &file->cat.u.fil.filRExtRec,
sizeof(ExtDataRec));
file->fabn = 0;
file->pos = 0;
}
/*
* NAME: file->getptrs()
* DESCRIPTION: make pointers to the current fork's lengths and extents
*/
void f_getptrs(hfsfile *file, ExtDataRec **extrec,
unsigned long **lglen, unsigned long **pylen)
{
if (file->fork == fkData)
{
if (extrec)
*extrec = &file->cat.u.fil.filExtRec;
if (lglen)
*lglen = &file->cat.u.fil.filLgLen;
if (pylen)
*pylen = &file->cat.u.fil.filPyLen;
}
else
{
if (extrec)
*extrec = &file->cat.u.fil.filRExtRec;
if (lglen)
*lglen = &file->cat.u.fil.filRLgLen;
if (pylen)
*pylen = &file->cat.u.fil.filRPyLen;
}
}
/*
* NAME: file->doblock()
* DESCRIPTION: read or write a numbered block from a file
*/
int f_doblock(hfsfile *file, unsigned long num, block *bp,
int (*func)(hfsvol *, unsigned int, unsigned int, block *))
{
unsigned int abnum;
unsigned int blnum;
unsigned int fabn;
int i;
abnum = num / file->vol->lpa;
blnum = num % file->vol->lpa;
/* locate the appropriate extent record */
fabn = file->fabn;
if (abnum < fabn)
{
ExtDataRec *extrec;
f_getptrs(file, &extrec, 0, 0);
fabn = file->fabn = 0;
memcpy(&file->ext, extrec, sizeof(ExtDataRec));
}
else
abnum -= fabn;
while (1)
{
unsigned int n;
for (i = 0; i < 3; ++i)
{
n = file->ext[i].xdrNumABlks;
if (abnum < n)
return func(file->vol, file->ext[i].xdrStABN + abnum, blnum, bp);
fabn += n;
abnum -= n;
}
if (v_extsearch(file, fabn, &file->ext, 0) <= 0)
goto fail;
file->fabn = fabn;
}
fail:
return -1;
}
/*
* NAME: file->addextent()
* DESCRIPTION: add an extent to a file
*/
int f_addextent(hfsfile *file, ExtDescriptor *blocks)
{
hfsvol *vol = file->vol;
ExtDataRec *extrec;
unsigned long *pylen;
unsigned int start, end;
node n;
int i;
f_getptrs(file, &extrec, 0, &pylen);
start = file->fabn;
end = *pylen / vol->mdb.drAlBlkSiz;
n.nnum = 0;
i = -1;
while (start < end)
{
for (i = 0; i < 3; ++i)
{
unsigned int num;
num = file->ext[i].xdrNumABlks;
start += num;
if (start == end)
break;
else if (start > end)
ERROR(EIO, "file extents exceed file physical length");
else if (num == 0)
ERROR(EIO, "empty file extent");
}
if (start == end)
break;
if (v_extsearch(file, start, &file->ext, &n) <= 0)
goto fail;
file->fabn = start;
}
if (i >= 0 &&
file->ext[i].xdrStABN + file->ext[i].xdrNumABlks == blocks->xdrStABN)
file->ext[i].xdrNumABlks += blocks->xdrNumABlks;
else
{
/* create a new extent descriptor */
if (++i < 3)
file->ext[i] = *blocks;
else
{
ExtKeyRec key;
byte record[HFS_MAX_EXTRECLEN];
unsigned int reclen;
/* record is full; create a new one */
file->ext[0] = *blocks;
for (i = 1; i < 3; ++i)
{
file->ext[i].xdrStABN = 0;
file->ext[i].xdrNumABlks = 0;
}
file->fabn = start;
r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, end);
r_packextrec(&key, &file->ext, record, &reclen);
if (bt_insert(&vol->ext, record, reclen) == -1)
goto fail;
i = -1;
}
}
if (i >= 0)
{
/* store the modified extent record */
if (file->fabn)
{
if ((n.nnum == 0 &&
v_extsearch(file, file->fabn, 0, &n) <= 0) ||
v_putextrec(&file->ext, &n) == -1)
goto fail;
}
else
memcpy(extrec, &file->ext, sizeof(ExtDataRec));
}
*pylen += blocks->xdrNumABlks * vol->mdb.drAlBlkSiz;
file->flags |= HFS_FILE_UPDATE_CATREC;
return 0;
fail:
return -1;
}
/*
* NAME: file->alloc()
* DESCRIPTION: reserve allocation blocks for a file
*/
long f_alloc(hfsfile *file)
{
hfsvol *vol = file->vol;
unsigned long clumpsz;
ExtDescriptor blocks;
clumpsz = file->cat.u.fil.filClpSize;
if (clumpsz == 0)
{
if (file == &vol->ext.f)
clumpsz = vol->mdb.drXTClpSiz;
else if (file == &vol->cat.f)
clumpsz = vol->mdb.drCTClpSiz;
else
clumpsz = vol->mdb.drClpSiz;
}
blocks.xdrNumABlks = clumpsz / vol->mdb.drAlBlkSiz;
if (v_allocblocks(vol, &blocks) == -1)
goto fail;
if (f_addextent(file, &blocks) == -1)
{
v_freeblocks(vol, &blocks);
goto fail;
}
return blocks.xdrNumABlks;
fail:
return -1;
}
/*
* NAME: file->trunc()
* DESCRIPTION: release allocation blocks unneeded by a file
*/
int f_trunc(hfsfile *file)
{
hfsvol *vol = file->vol;
ExtDataRec *extrec;
unsigned long *lglen, *pylen, alblksz, newpylen;
unsigned int dlen, start, end;
node n;
int i;
if (vol->flags & HFS_VOL_READONLY)
goto done;
f_getptrs(file, &extrec, &lglen, &pylen);
alblksz = vol->mdb.drAlBlkSiz;
newpylen = (*lglen / alblksz + (*lglen % alblksz != 0)) * alblksz;
if (newpylen > *pylen)
ERROR(EIO, "file size exceeds physical length");
else if (newpylen == *pylen)
goto done;
dlen = (*pylen - newpylen) / alblksz;
start = file->fabn;
end = newpylen / alblksz;
if (start >= end)
{
start = file->fabn = 0;
memcpy(&file->ext, extrec, sizeof(ExtDataRec));
}
n.nnum = 0;
i = -1;
while (start < end)
{
for (i = 0; i < 3; ++i)
{
unsigned int num;
num = file->ext[i].xdrNumABlks;
start += num;
if (start >= end)
break;
else if (num == 0)
ERROR(EIO, "empty file extent");
}
if (start >= end)
break;
if (v_extsearch(file, start, &file->ext, &n) <= 0)
goto fail;
file->fabn = start;
}
if (start > end)
{
ExtDescriptor blocks;
file->ext[i].xdrNumABlks -= start - end;
dlen -= start - end;
blocks.xdrStABN = file->ext[i].xdrStABN + file->ext[i].xdrNumABlks;
blocks.xdrNumABlks = start - end;
if (v_freeblocks(vol, &blocks) == -1)
goto fail;
}
*pylen = newpylen;
file->flags |= HFS_FILE_UPDATE_CATREC;
do
{
while (dlen && ++i < 3)
{
unsigned int num;
num = file->ext[i].xdrNumABlks;
start += num;
if (num == 0)
ERROR(EIO, "empty file extent");
else if (num > dlen)
ERROR(EIO, "file extents exceed physical size");
dlen -= num;
if (v_freeblocks(vol, &file->ext[i]) == -1)
goto fail;
file->ext[i].xdrStABN = 0;
file->ext[i].xdrNumABlks = 0;
}
if (file->fabn)
{
if (n.nnum == 0 &&
v_extsearch(file, file->fabn, 0, &n) <= 0)
goto fail;
if (file->ext[0].xdrNumABlks)
{
if (v_putextrec(&file->ext, &n) == -1)
goto fail;
}
else
{
if (bt_delete(&vol->ext, HFS_NODEREC(n, n.rnum)) == -1)
goto fail;
n.nnum = 0;
}
}
else
memcpy(extrec, &file->ext, sizeof(ExtDataRec));
if (dlen)
{
if (v_extsearch(file, start, &file->ext, &n) <= 0)
goto fail;
file->fabn = start;
i = -1;
}
}
while (dlen);
done:
return 0;
fail:
return -1;
}
/*
* NAME: file->flush()
* DESCRIPTION: flush all pending changes to an open file
*/
int f_flush(hfsfile *file)
{
hfsvol *vol = file->vol;
if (vol->flags & HFS_VOL_READONLY)
goto done;
if (file->flags & HFS_FILE_UPDATE_CATREC)
{
node n;
file->cat.u.fil.filStBlk = file->cat.u.fil.filExtRec[0].xdrStABN;
file->cat.u.fil.filRStBlk = file->cat.u.fil.filRExtRec[0].xdrStABN;
if (v_catsearch(vol, file->parid, file->name, 0, 0, &n) <= 0 ||
v_putcatrec(&file->cat, &n) == -1)
goto fail;
file->flags &= ~HFS_FILE_UPDATE_CATREC;
}
done:
return 0;
fail:
return -1;
}

45
libhfs/file.h Normal file
View File

@ -0,0 +1,45 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
enum {
fkData = 0x00,
fkRsrc = 0xff
};
void f_init(hfsfile *, hfsvol *, long, const char *);
void f_selectfork(hfsfile *, int);
void f_getptrs(hfsfile *, ExtDataRec **, unsigned long **, unsigned long **);
int f_doblock(hfsfile *, unsigned long, block *,
int (*)(hfsvol *, unsigned int, unsigned int, block *));
# define f_getblock(file, num, bp) \
f_doblock((file), (num), (bp), b_readab)
# define f_putblock(file, num, bp) \
f_doblock((file), (num), (bp), \
(int (*)(hfsvol *, unsigned int, unsigned int, block *)) \
b_writeab)
int f_addextent(hfsfile *, ExtDescriptor *);
long f_alloc(hfsfile *);
int f_trunc(hfsfile *);
int f_flush(hfsfile *);

1993
libhfs/hfs.c Normal file

File diff suppressed because it is too large Load Diff

201
libhfs/hfs.h Normal file
View File

@ -0,0 +1,201 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
#ifdef __cplusplus
extern "C" {
#endif
# include <time.h>
# define HFS_BLOCKSZ 512
# define HFS_BLOCKSZ_BITS 9
# define HFS_MAX_FLEN 31
# define HFS_MAX_VLEN 27
typedef struct _hfsvol_ hfsvol;
typedef struct _hfsfile_ hfsfile;
typedef struct _hfsdir_ hfsdir;
typedef struct {
char name[HFS_MAX_VLEN + 1]; /* name of volume (MacOS Standard Roman) */
int flags; /* volume flags */
unsigned long totbytes; /* total bytes on volume */
unsigned long freebytes; /* free bytes on volume */
unsigned long alblocksz; /* volume allocation block size */
unsigned long clumpsz; /* default file clump size */
unsigned long numfiles; /* number of files in volume */
unsigned long numdirs; /* number of directories in volume */
time_t crdate; /* volume creation date */
time_t mddate; /* last volume modification date */
time_t bkdate; /* last volume backup date */
unsigned long blessed; /* CNID of MacOS System Folder */
} hfsvolent;
typedef struct {
char name[HFS_MAX_FLEN + 1]; /* catalog name (MacOS Standard Roman) */
int flags; /* bit flags */
unsigned long cnid; /* catalog node id (CNID) */
unsigned long parid; /* CNID of parent directory */
time_t crdate; /* date of creation */
time_t mddate; /* date of last modification */
time_t bkdate; /* date of last backup */
short fdflags; /* Macintosh Finder flags */
struct {
signed short v; /* Finder icon vertical coordinate */
signed short h; /* horizontal coordinate */
} fdlocation;
union {
struct {
unsigned long dsize; /* size of data fork */
unsigned long rsize; /* size of resource fork */
char type[5]; /* file type code (plus null) */
char creator[5]; /* file creator code (plus null) */
} file;
struct {
unsigned short valence; /* number of items in directory */
struct {
signed short top; /* top edge of folder's rectangle */
signed short left; /* left edge */
signed short bottom; /* bottom edge */
signed short right; /* right edge */
} rect;
} dir;
} u;
} hfsdirent;
# define HFS_ISDIR 0x0001
# define HFS_ISLOCKED 0x0002
# define HFS_CNID_ROOTPAR 1
# define HFS_CNID_ROOTDIR 2
# define HFS_CNID_EXT 3
# define HFS_CNID_CAT 4
# define HFS_CNID_BADALLOC 5
# define HFS_FNDR_ISONDESK (1 << 0)
# define HFS_FNDR_COLOR 0x0e
# define HFS_FNDR_COLORRESERVED (1 << 4)
# define HFS_FNDR_REQUIRESSWITCHLAUNCH (1 << 5)
# define HFS_FNDR_ISSHARED (1 << 6)
# define HFS_FNDR_HASNOINITS (1 << 7)
# define HFS_FNDR_HASBEENINITED (1 << 8)
# define HFS_FNDR_RESERVED (1 << 9)
# define HFS_FNDR_HASCUSTOMICON (1 << 10)
# define HFS_FNDR_ISSTATIONERY (1 << 11)
# define HFS_FNDR_NAMELOCKED (1 << 12)
# define HFS_FNDR_HASBUNDLE (1 << 13)
# define HFS_FNDR_ISINVISIBLE (1 << 14)
# define HFS_FNDR_ISALIAS (1 << 15)
extern const char *hfs_error;
extern const unsigned char hfs_charorder[];
# define HFS_MODE_RDONLY 0
# define HFS_MODE_RDWR 1
# define HFS_MODE_ANY 2
# define HFS_MODE_MASK 0x0003
# define HFS_OPT_NOCACHE 0x0100
# define HFS_OPT_2048 0x0200
# define HFS_OPT_ZERO 0x0400
# define HFS_SEEK_SET 0
# define HFS_SEEK_CUR 1
# define HFS_SEEK_END 2
#ifdef CP_NO_STATIC
hfsvol *hfs_mount(const char *, int, int);
#endif
int hfs_flush(hfsvol *);
#ifdef CP_NO_STATIC
void hfs_flushall(void);
int hfs_umount(hfsvol *);
void hfs_umountall(void);
hfsvol *hfs_getvol(const char *);
void hfs_setvol(hfsvol *);
#endif
int hfs_vstat(hfsvol *, hfsvolent *);
int hfs_vsetattr(hfsvol *, hfsvolent *);
int hfs_chdir(hfsvol *, const char *);
unsigned long hfs_getcwd(hfsvol *);
int hfs_setcwd(hfsvol *, unsigned long);
int hfs_dirinfo(hfsvol *, unsigned long *, char *);
hfsdir *hfs_opendir(hfsvol *, const char *);
int hfs_readdir(hfsdir *, hfsdirent *);
int hfs_closedir(hfsdir *);
hfsfile *hfs_create(hfsvol *, const char *, const char *, const char *);
hfsfile *hfs_open(hfsvol *, const char *);
int hfs_setfork(hfsfile *, int);
int hfs_getfork(hfsfile *);
unsigned long hfs_read(hfsfile *, void *, unsigned long);
unsigned long hfs_write(hfsfile *, const void *, unsigned long);
int hfs_truncate(hfsfile *, unsigned long);
unsigned long hfs_seek(hfsfile *, long, int);
int hfs_close(hfsfile *);
int hfs_stat(hfsvol *, const char *, hfsdirent *);
int hfs_fstat(hfsfile *, hfsdirent *);
int hfs_setattr(hfsvol *, const char *, const hfsdirent *);
int hfs_fsetattr(hfsfile *, const hfsdirent *);
int hfs_mkdir(hfsvol *, const char *);
int hfs_rmdir(hfsvol *, const char *);
int hfs_delete(hfsvol *, const char *);
int hfs_rename(hfsvol *, const char *, const char *);
int hfs_zero(const char *, unsigned int, unsigned long *);
int hfs_mkpart(const char *, unsigned long);
int hfs_nparts(const char *);
int hfs_format(const char *, int, int,
const char *, unsigned int, const unsigned long []);
/* CiderPress callback interface */
enum { HFS_CB_VOLSIZE, HFS_CB_READ, HFS_CB_WRITE, HFS_CB_SEEK };
typedef unsigned long (*oscallback)(void* cookie, int op, unsigned long arg1,
void* arg2);
hfsvol* hfs_callback_open(oscallback func, void* cookie, int mode);
int hfs_callback_close(hfsvol* vol);
int hfs_callback_format(oscallback func, void* cookie, int mode,
const char* vname);
#ifdef __cplusplus
};
#endif

227
libhfs/libhfs.h Normal file
View File

@ -0,0 +1,227 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# include "hfs.h"
# include "apple.h"
//extern int errno;
# include <errno.h>
# define ERROR(code, str) \
do { hfs_error = (str), errno = (code); goto fail; } while (0)
# ifdef DEBUG
# define ASSERT(cond) do { if (! (cond)) abort(); } while (0)
# else
# define ASSERT(cond) /* nothing */
# endif
# define SIZE(type, n) ((size_t) (sizeof(type) * (n)))
# define ALLOC(type, n) ((type *) malloc(SIZE(type, n)))
# define ALLOCX(type, n) ((n) ? ALLOC(type, n) : (type *) 0)
# define FREE(ptr) ((ptr) ? (void) free((void *) ptr) : (void) 0)
# define REALLOC(ptr, type, n) \
((type *) ((ptr) ? realloc(ptr, SIZE(type, n)) : malloc(SIZE(type, n))))
# define REALLOCX(ptr, type, n) \
((n) ? REALLOC(ptr, type, n) : (FREE(ptr), (type *) 0))
# define BMTST(bm, num) \
(((const byte *) (bm))[(num) >> 3] & (0x80 >> ((num) & 0x07)))
# define BMSET(bm, num) \
(((byte *) (bm))[(num) >> 3] |= (0x80 >> ((num) & 0x07)))
# define BMCLR(bm, num) \
(((byte *) (bm))[(num) >> 3] &= ~(0x80 >> ((num) & 0x07)))
# define STRINGIZE(x) #x
# define STR(x) STRINGIZE(x)
typedef unsigned char byte;
typedef byte block[HFS_BLOCKSZ];
typedef struct _bucket_ {
int flags; /* bit flags */
unsigned int count; /* number of times this block is requested */
unsigned long bnum; /* logical block number */
block *data; /* pointer to block contents */
struct _bucket_ *cnext; /* next bucket in cache chain */
struct _bucket_ *cprev; /* previous bucket in cache chain */
struct _bucket_ *hnext; /* next bucket in hash chain */
struct _bucket_ **hprev; /* previous bucket's pointer to this bucket */
} bucket;
# define HFS_BUCKET_INUSE 0x01
# define HFS_BUCKET_DIRTY 0x02
# define HFS_CACHESZ 128
# define HFS_HASHSZ 32
# define HFS_BLOCKBUFSZ 16
typedef struct {
struct _hfsvol_ *vol; /* volume to which cache belongs */
bucket *tail; /* end of bucket chain */
unsigned int hits; /* number of cache hits */
unsigned int misses; /* number of cache misses */
bucket chain[HFS_CACHESZ]; /* cache bucket chain */
bucket *hash[HFS_HASHSZ]; /* hash table for bucket chain */
block pool[HFS_CACHESZ]; /* physical blocks in cache */
} bcache;
# define HFS_MAP1SZ 256
# define HFS_MAPXSZ 492
# define HFS_NODEREC(nd, rnum) ((nd).data + (nd).roff[rnum])
# define HFS_RECLEN(nd, rnum) ((nd).roff[(rnum) + 1] - (nd).roff[rnum])
# define HFS_RECKEYLEN(ptr) (*(const byte *) (ptr))
# define HFS_RECKEYSKIP(ptr) ((size_t) ((1 + HFS_RECKEYLEN(ptr) + 1) & ~1))
# define HFS_RECDATA(ptr) ((ptr) + HFS_RECKEYSKIP(ptr))
# define HFS_SETKEYLEN(ptr, x) (*(byte *) (ptr) = (x))
# define HFS_CATDATALEN sizeof(CatDataRec)
# define HFS_EXTDATALEN sizeof(ExtDataRec)
# define HFS_MAX_DATALEN (HFS_CATDATALEN > HFS_EXTDATALEN ? \
HFS_CATDATALEN : HFS_EXTDATALEN)
# define HFS_CATKEYLEN sizeof(CatKeyRec)
# define HFS_EXTKEYLEN sizeof(ExtKeyRec)
# define HFS_MAX_KEYLEN (HFS_CATKEYLEN > HFS_EXTKEYLEN ? \
HFS_CATKEYLEN : HFS_EXTKEYLEN)
# define HFS_MAX_CATRECLEN (HFS_CATKEYLEN + HFS_CATDATALEN)
# define HFS_MAX_EXTRECLEN (HFS_EXTKEYLEN + HFS_EXTDATALEN)
# define HFS_MAX_RECLEN (HFS_MAX_KEYLEN + HFS_MAX_DATALEN)
# define HFS_SIGWORD 0x4244
# define HFS_SIGWORD_MFS ((Integer) 0xd2d7)
# define HFS_ATRB_BUSY (1 << 6)
# define HFS_ATRB_HLOCKED (1 << 7)
# define HFS_ATRB_UMOUNTED (1 << 8)
# define HFS_ATRB_BBSPARED (1 << 9)
# define HFS_ATRB_BVINCONSIS (1 << 11)
# define HFS_ATRB_COPYPROT (1 << 14)
# define HFS_ATRB_SLOCKED (1 << 15)
struct _hfsfile_ {
struct _hfsvol_ *vol; /* pointer to volume descriptor */
unsigned long parid; /* parent directory ID of this file */
char name[HFS_MAX_FLEN + 1]; /* catalog name of this file */
CatDataRec cat; /* catalog information */
ExtDataRec ext; /* current extent record */
unsigned int fabn; /* starting file allocation block number */
int fork; /* current selected fork for I/O */
unsigned long pos; /* current file seek pointer */
int flags; /* bit flags */
struct _hfsfile_ *prev;
struct _hfsfile_ *next;
};
# define HFS_FILE_UPDATE_CATREC 0x01
# define HFS_MAX_NRECS 35 /* maximum based on minimum record size */
typedef struct _node_ {
struct _btree_ *bt; /* btree to which this node belongs */
unsigned long nnum; /* node index */
NodeDescriptor nd; /* node descriptor */
int rnum; /* current record index */
UInteger roff[HFS_MAX_NRECS + 1];
/* record offsets */
block data; /* raw contents of node */
} node;
struct _hfsdir_ {
struct _hfsvol_ *vol; /* associated volume */
unsigned long dirid; /* directory ID of interest (or 0) */
node n; /* current B*-tree node */
struct _hfsvol_ *vptr; /* current volume pointer */
struct _hfsdir_ *prev;
struct _hfsdir_ *next;
};
typedef void (*keyunpackfunc)(const byte *, void *);
typedef int (*keycomparefunc)(const void *, const void *);
typedef struct _btree_ {
hfsfile f; /* subset file information */
node hdrnd; /* header node */
BTHdrRec hdr; /* header record */
byte *map; /* usage bitmap */
unsigned long mapsz; /* number of bytes in bitmap */
int flags; /* bit flags */
keyunpackfunc keyunpack; /* key unpacking function */
keycomparefunc keycompare; /* key comparison function */
} btree;
# define HFS_BT_UPDATE_HDR 0x01
struct _hfsvol_ {
void *priv; /* OS-dependent private descriptor data */
int flags; /* bit flags */
int pnum; /* ordinal HFS partition number */
unsigned long vstart; /* logical block offset to start of volume */
unsigned long vlen; /* number of logical blocks in volume */
unsigned int lpa; /* number of logical blocks per allocation block */
bcache *cache; /* cache of recently used blocks */
MDB mdb; /* master directory block */
block *vbm; /* volume bitmap */
unsigned short vbmsz; /* number of blocks in bitmap */
btree ext; /* B*-tree control block for extents overflow file */
btree cat; /* B*-tree control block for catalog file */
unsigned long cwd; /* directory id of current working directory */
int refs; /* number of external references to this volume */
hfsfile *files; /* list of open files */
hfsdir *dirs; /* list of open directories */
struct _hfsvol_ *prev;
struct _hfsvol_ *next;
};
# define HFS_VOL_OPEN 0x0001
# define HFS_VOL_MOUNTED 0x0002
# define HFS_VOL_READONLY 0x0004
# define HFS_VOL_USINGCACHE 0x0008
# define HFS_VOL_UPDATE_MDB 0x0010
# define HFS_VOL_UPDATE_ALTMDB 0x0020
# define HFS_VOL_UPDATE_VBM 0x0040
# define HFS_VOL_OPT_MASK 0xff00
extern hfsvol *hfs_mounts;

136
libhfs/libhfs.vcxproj Normal file
View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{0FA742E9-8C07-43DD-AFF8-CE31FAF70821}</ProjectGuid>
<SccProjectName />
<SccLocalPath />
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<UseOfMfc>false</UseOfMfc>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>Dynamic</UseOfMfc>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>12.0.30501.0</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader />
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
<ObjectFileName>$(IntDir)</ObjectFileName>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<BrowseInformation>true</BrowseInformation>
<WarningLevel>Level2</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile>
<Lib>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Lib>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
<ObjectFileName>$(IntDir)</ObjectFileName>
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
<WarningLevel>Level2</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
</ClCompile>
<Lib>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Lib>
<ResourceCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="apple.h" />
<ClInclude Include="block.h" />
<ClInclude Include="btree.h" />
<ClInclude Include="config.h" />
<ClInclude Include="data.h" />
<ClInclude Include="file.h" />
<ClInclude Include="hfs.h" />
<ClInclude Include="libhfs.h" />
<ClInclude Include="low.h" />
<ClInclude Include="medium.h" />
<ClInclude Include="node.h" />
<ClInclude Include="os.h" />
<ClInclude Include="record.h" />
<ClInclude Include="version.h" />
<ClInclude Include="volume.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="block.c" />
<ClCompile Include="btree.c" />
<ClCompile Include="data.c" />
<ClCompile Include="file.c" />
<ClCompile Include="hfs.c" />
<ClCompile Include="low.c" />
<ClCompile Include="medium.c" />
<ClCompile Include="node.c" />
<ClCompile Include="os.c" />
<ClCompile Include="record.c" />
<ClCompile Include="version.c" />
<ClCompile Include="volume.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{2d85999b-987f-4814-9a1a-50647f99d4a7}</UniqueIdentifier>
<Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{3de635b8-d8d0-4540-8bc6-f060838e8e8e}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="apple.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="block.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="btree.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="config.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="data.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hfs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="libhfs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="low.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="medium.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="node.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="os.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="record.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="volume.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="block.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="btree.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="data.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hfs.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="low.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="medium.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="node.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="os.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="record.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="version.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="volume.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

470
libhfs/low.c Normal file
View File

@ -0,0 +1,470 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "low.h"
# include "data.h"
# include "block.h"
# include "file.h"
/*
* NAME: low->getddr()
* DESCRIPTION: read a driver descriptor record
*/
int l_getddr(hfsvol *vol, Block0 *ddr)
{
block b;
const byte *ptr = b;
int i;
if (b_readpb(vol, 0, &b, 1) == -1)
goto fail;
d_fetchsw(&ptr, &ddr->sbSig);
d_fetchsw(&ptr, &ddr->sbBlkSize);
d_fetchsl(&ptr, &ddr->sbBlkCount);
d_fetchsw(&ptr, &ddr->sbDevType);
d_fetchsw(&ptr, &ddr->sbDevId);
d_fetchsl(&ptr, &ddr->sbData);
d_fetchsw(&ptr, &ddr->sbDrvrCount);
d_fetchsl(&ptr, &ddr->ddBlock);
d_fetchsw(&ptr, &ddr->ddSize);
d_fetchsw(&ptr, &ddr->ddType);
for (i = 0; i < 243; ++i)
d_fetchsw(&ptr, &ddr->ddPad[i]);
ASSERT(ptr - b == HFS_BLOCKSZ);
return 0;
fail:
return -1;
}
/*
* NAME: low->putddr()
* DESCRIPTION: write a driver descriptor record
*/
int l_putddr(hfsvol *vol, const Block0 *ddr)
{
block b;
byte *ptr = b;
int i;
d_storesw(&ptr, ddr->sbSig);
d_storesw(&ptr, ddr->sbBlkSize);
d_storesl(&ptr, ddr->sbBlkCount);
d_storesw(&ptr, ddr->sbDevType);
d_storesw(&ptr, ddr->sbDevId);
d_storesl(&ptr, ddr->sbData);
d_storesw(&ptr, ddr->sbDrvrCount);
d_storesl(&ptr, ddr->ddBlock);
d_storesw(&ptr, ddr->ddSize);
d_storesw(&ptr, ddr->ddType);
for (i = 0; i < 243; ++i)
d_storesw(&ptr, ddr->ddPad[i]);
ASSERT(ptr - b == HFS_BLOCKSZ);
if (b_writepb(vol, 0, &b, 1) == -1)
goto fail;
return 0;
fail:
return -1;
}
/*
* NAME: low->getpmentry()
* DESCRIPTION: read a partition map entry
*/
int l_getpmentry(hfsvol *vol, Partition *map, unsigned long bnum)
{
block b;
const byte *ptr = b;
int i;
if (b_readpb(vol, bnum, &b, 1) == -1)
goto fail;
d_fetchsw(&ptr, &map->pmSig);
d_fetchsw(&ptr, &map->pmSigPad);
d_fetchsl(&ptr, &map->pmMapBlkCnt);
d_fetchsl(&ptr, &map->pmPyPartStart);
d_fetchsl(&ptr, &map->pmPartBlkCnt);
strncpy((char *) map->pmPartName, (const char *) ptr, 32);
map->pmPartName[32] = 0;
ptr += 32;
strncpy((char *) map->pmParType, (const char *) ptr, 32);
map->pmParType[32] = 0;
ptr += 32;
d_fetchsl(&ptr, &map->pmLgDataStart);
d_fetchsl(&ptr, &map->pmDataCnt);
d_fetchsl(&ptr, &map->pmPartStatus);
d_fetchsl(&ptr, &map->pmLgBootStart);
d_fetchsl(&ptr, &map->pmBootSize);
d_fetchsl(&ptr, &map->pmBootAddr);
d_fetchsl(&ptr, &map->pmBootAddr2);
d_fetchsl(&ptr, &map->pmBootEntry);
d_fetchsl(&ptr, &map->pmBootEntry2);
d_fetchsl(&ptr, &map->pmBootCksum);
strncpy((char *) map->pmProcessor, (const char *) ptr, 16);
map->pmProcessor[16] = 0;
ptr += 16;
for (i = 0; i < 188; ++i)
d_fetchsw(&ptr, &map->pmPad[i]);
ASSERT(ptr - b == HFS_BLOCKSZ);
return 0;
fail:
return -1;
}
/*
* NAME: low->putpmentry()
* DESCRIPTION: write a partition map entry
*/
int l_putpmentry(hfsvol *vol, const Partition *map, unsigned long bnum)
{
block b;
byte *ptr = b;
int i;
d_storesw(&ptr, map->pmSig);
d_storesw(&ptr, map->pmSigPad);
d_storesl(&ptr, map->pmMapBlkCnt);
d_storesl(&ptr, map->pmPyPartStart);
d_storesl(&ptr, map->pmPartBlkCnt);
memset(ptr, 0, 32);
strncpy((char *) ptr, (const char *) map->pmPartName, 32);
ptr += 32;
memset(ptr, 0, 32);
strncpy((char *) ptr, (const char *) map->pmParType, 32);
ptr += 32;
d_storesl(&ptr, map->pmLgDataStart);
d_storesl(&ptr, map->pmDataCnt);
d_storesl(&ptr, map->pmPartStatus);
d_storesl(&ptr, map->pmLgBootStart);
d_storesl(&ptr, map->pmBootSize);
d_storesl(&ptr, map->pmBootAddr);
d_storesl(&ptr, map->pmBootAddr2);
d_storesl(&ptr, map->pmBootEntry);
d_storesl(&ptr, map->pmBootEntry2);
d_storesl(&ptr, map->pmBootCksum);
memset(ptr, 0, 16);
strncpy((char *) ptr, (const char *) map->pmProcessor, 16);
ptr += 16;
for (i = 0; i < 188; ++i)
d_storesw(&ptr, map->pmPad[i]);
ASSERT(ptr - b == HFS_BLOCKSZ);
if (b_writepb(vol, bnum, &b, 1) == -1)
goto fail;
return 0;
fail:
return -1;
}
/*
* NAME: low->getbb()
* DESCRIPTION: read a volume's boot blocks
*/
int l_getbb(hfsvol *vol, BootBlkHdr *bb, byte *bootcode)
{
block b;
const byte *ptr = b;
if (b_readlb(vol, 0, &b) == -1)
goto fail;
d_fetchsw(&ptr, &bb->bbID);
d_fetchsl(&ptr, &bb->bbEntry);
d_fetchsw(&ptr, &bb->bbVersion);
d_fetchsw(&ptr, &bb->bbPageFlags);
d_fetchstr(&ptr, bb->bbSysName, sizeof(bb->bbSysName));
d_fetchstr(&ptr, bb->bbShellName, sizeof(bb->bbShellName));
d_fetchstr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name));
d_fetchstr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name));
d_fetchstr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName));
d_fetchstr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName));
d_fetchstr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName));
d_fetchsw(&ptr, &bb->bbCntFCBs);
d_fetchsw(&ptr, &bb->bbCntEvts);
d_fetchsl(&ptr, &bb->bb128KSHeap);
d_fetchsl(&ptr, &bb->bb256KSHeap);
d_fetchsl(&ptr, &bb->bbSysHeapSize);
d_fetchsw(&ptr, &bb->filler);
d_fetchsl(&ptr, &bb->bbSysHeapExtra);
d_fetchsl(&ptr, &bb->bbSysHeapFract);
ASSERT(ptr - b == 148);
if (bootcode)
{
memcpy(bootcode, ptr, HFS_BOOTCODE1LEN);
if (b_readlb(vol, 1, &b) == -1)
goto fail;
memcpy(bootcode + HFS_BOOTCODE1LEN, b, HFS_BOOTCODE2LEN);
}
return 0;
fail:
return -1;
}
/*
* NAME: low->putbb()
* DESCRIPTION: write a volume's boot blocks
*/
int l_putbb(hfsvol *vol, const BootBlkHdr *bb, const byte *bootcode)
{
block b;
byte *ptr = b;
d_storesw(&ptr, bb->bbID);
d_storesl(&ptr, bb->bbEntry);
d_storesw(&ptr, bb->bbVersion);
d_storesw(&ptr, bb->bbPageFlags);
d_storestr(&ptr, bb->bbSysName, sizeof(bb->bbSysName));
d_storestr(&ptr, bb->bbShellName, sizeof(bb->bbShellName));
d_storestr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name));
d_storestr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name));
d_storestr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName));
d_storestr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName));
d_storestr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName));
d_storesw(&ptr, bb->bbCntFCBs);
d_storesw(&ptr, bb->bbCntEvts);
d_storesl(&ptr, bb->bb128KSHeap);
d_storesl(&ptr, bb->bb256KSHeap);
d_storesl(&ptr, bb->bbSysHeapSize);
d_storesw(&ptr, bb->filler);
d_storesl(&ptr, bb->bbSysHeapExtra);
d_storesl(&ptr, bb->bbSysHeapFract);
ASSERT(ptr - b == 148);
if (bootcode)
memcpy(ptr, bootcode, HFS_BOOTCODE1LEN);
else
memset(ptr, 0, HFS_BOOTCODE1LEN);
if (b_writelb(vol, 0, &b) == -1)
goto fail;
if (bootcode)
memcpy(&b, bootcode + HFS_BOOTCODE1LEN, HFS_BOOTCODE2LEN);
else
memset(&b, 0, HFS_BOOTCODE2LEN);
if (b_writelb(vol, 1, &b) == -1)
goto fail;
return 0;
fail:
return -1;
}
/*
* NAME: low->getmdb()
* DESCRIPTION: read a master directory block
*/
int l_getmdb(hfsvol *vol, MDB *mdb, int backup)
{
block b;
const byte *ptr = b;
int i;
if (b_readlb(vol, backup ? vol->vlen - 2 : 2, &b) == -1)
goto fail;
d_fetchsw(&ptr, &mdb->drSigWord);
d_fetchsl(&ptr, &mdb->drCrDate);
d_fetchsl(&ptr, &mdb->drLsMod);
d_fetchsw(&ptr, &mdb->drAtrb);
d_fetchuw(&ptr, &mdb->drNmFls);
d_fetchuw(&ptr, &mdb->drVBMSt);
d_fetchuw(&ptr, &mdb->drAllocPtr);
d_fetchuw(&ptr, &mdb->drNmAlBlks);
d_fetchul(&ptr, &mdb->drAlBlkSiz);
d_fetchul(&ptr, &mdb->drClpSiz);
d_fetchuw(&ptr, &mdb->drAlBlSt);
d_fetchsl(&ptr, &mdb->drNxtCNID);
d_fetchuw(&ptr, &mdb->drFreeBks);
d_fetchstr(&ptr, mdb->drVN, sizeof(mdb->drVN));
ASSERT(ptr - b == 64);
d_fetchsl(&ptr, &mdb->drVolBkUp);
d_fetchsw(&ptr, &mdb->drVSeqNum);
d_fetchul(&ptr, &mdb->drWrCnt);
d_fetchul(&ptr, &mdb->drXTClpSiz);
d_fetchul(&ptr, &mdb->drCTClpSiz);
d_fetchuw(&ptr, &mdb->drNmRtDirs);
d_fetchul(&ptr, &mdb->drFilCnt);
d_fetchul(&ptr, &mdb->drDirCnt);
for (i = 0; i < 8; ++i)
d_fetchsl(&ptr, &mdb->drFndrInfo[i]);
ASSERT(ptr - b == 124);
d_fetchuw(&ptr, &mdb->drEmbedSigWord);
d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrStABN);
d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrNumABlks);
d_fetchul(&ptr, &mdb->drXTFlSize);
for (i = 0; i < 3; ++i)
{
d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrStABN);
d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrNumABlks);
}
ASSERT(ptr - b == 146);
d_fetchul(&ptr, &mdb->drCTFlSize);
for (i = 0; i < 3; ++i)
{
d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrStABN);
d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrNumABlks);
}
ASSERT(ptr - b == 162);
return 0;
fail:
return -1;
}
/*
* NAME: low->putmdb()
* DESCRIPTION: write master directory block(s)
*/
int l_putmdb(hfsvol *vol, const MDB *mdb, int backup)
{
block b;
byte *ptr = b;
int i;
d_storesw(&ptr, mdb->drSigWord);
d_storesl(&ptr, mdb->drCrDate);
d_storesl(&ptr, mdb->drLsMod);
d_storesw(&ptr, mdb->drAtrb);
d_storeuw(&ptr, mdb->drNmFls);
d_storeuw(&ptr, mdb->drVBMSt);
d_storeuw(&ptr, mdb->drAllocPtr);
d_storeuw(&ptr, mdb->drNmAlBlks);
d_storeul(&ptr, mdb->drAlBlkSiz);
d_storeul(&ptr, mdb->drClpSiz);
d_storeuw(&ptr, mdb->drAlBlSt);
d_storesl(&ptr, mdb->drNxtCNID);
d_storeuw(&ptr, mdb->drFreeBks);
d_storestr(&ptr, mdb->drVN, sizeof(mdb->drVN));
ASSERT(ptr - b == 64);
d_storesl(&ptr, mdb->drVolBkUp);
d_storesw(&ptr, mdb->drVSeqNum);
d_storeul(&ptr, mdb->drWrCnt);
d_storeul(&ptr, mdb->drXTClpSiz);
d_storeul(&ptr, mdb->drCTClpSiz);
d_storeuw(&ptr, mdb->drNmRtDirs);
d_storeul(&ptr, mdb->drFilCnt);
d_storeul(&ptr, mdb->drDirCnt);
for (i = 0; i < 8; ++i)
d_storesl(&ptr, mdb->drFndrInfo[i]);
ASSERT(ptr - b == 124);
d_storeuw(&ptr, mdb->drEmbedSigWord);
d_storeuw(&ptr, mdb->drEmbedExtent.xdrStABN);
d_storeuw(&ptr, mdb->drEmbedExtent.xdrNumABlks);
d_storeul(&ptr, mdb->drXTFlSize);
for (i = 0; i < 3; ++i)
{
d_storeuw(&ptr, mdb->drXTExtRec[i].xdrStABN);
d_storeuw(&ptr, mdb->drXTExtRec[i].xdrNumABlks);
}
ASSERT(ptr - b == 146);
d_storeul(&ptr, mdb->drCTFlSize);
for (i = 0; i < 3; ++i)
{
d_storeuw(&ptr, mdb->drCTExtRec[i].xdrStABN);
d_storeuw(&ptr, mdb->drCTExtRec[i].xdrNumABlks);
}
ASSERT(ptr - b == 162);
memset(ptr, 0, HFS_BLOCKSZ - (ptr - b));
if (b_writelb(vol, 2, &b) == -1 ||
(backup && b_writelb(vol, vol->vlen - 2, &b) == -1))
goto fail;
return 0;
fail:
return -1;
}

44
libhfs/low.h Normal file
View File

@ -0,0 +1,44 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# define HFS_DDR_SIGWORD 0x4552
# define HFS_PM_SIGWORD 0x504d
# define HFS_PM_SIGWORD_OLD 0x5453
# define HFS_BB_SIGWORD 0x4c4b
# define HFS_BOOTCODE1LEN (HFS_BLOCKSZ - 148)
# define HFS_BOOTCODE2LEN HFS_BLOCKSZ
# define HFS_BOOTCODELEN (HFS_BOOTCODE1LEN + HFS_BOOTCODE2LEN)
int l_getddr(hfsvol *, Block0 *);
int l_putddr(hfsvol *, const Block0 *);
int l_getpmentry(hfsvol *, Partition *, unsigned long);
int l_putpmentry(hfsvol *, const Partition *, unsigned long);
int l_getbb(hfsvol *, BootBlkHdr *, byte *);
int l_putbb(hfsvol *, const BootBlkHdr *, const byte *);
int l_getmdb(hfsvol *, MDB *, int);
int l_putmdb(hfsvol *, const MDB *, int);

318
libhfs/medium.c Normal file
View File

@ -0,0 +1,318 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "block.h"
# include "low.h"
# include "medium.h"
/* Driver Descriptor Record Routines ======================================= */
/*
* NAME: medium->zeroddr()
* DESCRIPTION: write a new/empty driver descriptor record
*/
int m_zeroddr(hfsvol *vol)
{
Block0 ddr;
int i;
ASSERT(vol->pnum == 0 && vol->vlen != 0);
ddr.sbSig = HFS_DDR_SIGWORD;
ddr.sbBlkSize = HFS_BLOCKSZ;
ddr.sbBlkCount = vol->vlen;
ddr.sbDevType = 0;
ddr.sbDevId = 0;
ddr.sbData = 0;
ddr.sbDrvrCount = 0;
ddr.ddBlock = 0;
ddr.ddSize = 0;
ddr.ddType = 0;
for (i = 0; i < 243; ++i)
ddr.ddPad[i] = 0;
return l_putddr(vol, &ddr);
}
/* Partition Map Routines ================================================== */
/*
* NAME: medium->zeropm()
* DESCRIPTION: write new/empty partition map
*/
int m_zeropm(hfsvol *vol, unsigned int maxparts)
{
Partition map;
unsigned int i;
ASSERT(vol->pnum == 0 && vol->vlen != 0);
if (maxparts < 2)
ERROR(EINVAL, "must allow at least 2 partitions");
/* first entry: partition map itself */
map.pmSig = HFS_PM_SIGWORD;
map.pmSigPad = 0;
map.pmMapBlkCnt = 2;
map.pmPyPartStart = 1;
map.pmPartBlkCnt = maxparts;
strcpy((char *) map.pmPartName, "Apple");
strcpy((char *) map.pmParType, "Apple_partition_map");
map.pmLgDataStart = 0;
map.pmDataCnt = map.pmPartBlkCnt;
map.pmPartStatus = 0;
map.pmLgBootStart = 0;
map.pmBootSize = 0;
map.pmBootAddr = 0;
map.pmBootAddr2 = 0;
map.pmBootEntry = 0;
map.pmBootEntry2 = 0;
map.pmBootCksum = 0;
strcpy((char *) map.pmProcessor, "");
for (i = 0; i < 188; ++i)
map.pmPad[i] = 0;
if (l_putpmentry(vol, &map, 1) == -1)
goto fail;
/* second entry: rest of medium */
map.pmPyPartStart = 1 + maxparts;
map.pmPartBlkCnt = vol->vlen - 1 - maxparts;
strcpy((char *) map.pmPartName, "Extra");
strcpy((char *) map.pmParType, "Apple_Free");
map.pmDataCnt = map.pmPartBlkCnt;
if (l_putpmentry(vol, &map, 2) == -1)
goto fail;
/* zero rest of partition map's partition */
if (maxparts > 2)
{
block b;
memset(&b, 0, sizeof(b));
for (i = 3; i <= maxparts; ++i)
{
if (b_writepb(vol, i, &b, 1) == -1)
goto fail;
}
}
return 0;
fail:
return -1;
}
/*
* NAME: medium->findpmentry()
* DESCRIPTION: locate a partition map entry
*/
int m_findpmentry(hfsvol *vol, const char *type,
Partition *map, unsigned long *start)
{
unsigned long bnum;
int found = 0;
if (start && *start > 0)
{
bnum = *start;
if (bnum++ >= (unsigned long) map->pmMapBlkCnt)
ERROR(EINVAL, "partition not found");
}
else
bnum = 1;
while (1)
{
if (l_getpmentry(vol, map, bnum) == -1)
{
found = -1;
goto fail;
}
if (map->pmSig != HFS_PM_SIGWORD)
{
found = -1;
if (map->pmSig == HFS_PM_SIGWORD_OLD)
ERROR(EINVAL, "old partition map format not supported");
else
ERROR(EINVAL, "invalid partition map");
}
if (strcmp((char *) map->pmParType, type) == 0)
{
found = 1;
goto done;
}
if (bnum++ >= (unsigned long) map->pmMapBlkCnt)
ERROR(EINVAL, "partition not found");
}
done:
if (start)
*start = bnum;
fail:
return found;
}
/*
* NAME: medium->mkpart()
* DESCRIPTION: create a new partition from available free space
*/
int m_mkpart(hfsvol *vol,
const char *name, const char *type, unsigned long len)
{
Partition map;
unsigned int nparts, maxparts;
unsigned long bnum, start, remain;
int found;
if (strlen(name) > 32 ||
strlen(type) > 32)
ERROR(EINVAL, "partition name/type can each be at most 32 chars");
if (len == 0)
ERROR(EINVAL, "partition length must be > 0");
found = m_findpmentry(vol, "Apple_partition_map", &map, 0);
if (found == -1)
goto fail;
if (! found)
ERROR(EIO, "cannot find partition map's partition");
nparts = map.pmMapBlkCnt;
maxparts = map.pmPartBlkCnt;
bnum = 0;
do
{
found = m_findpmentry(vol, "Apple_Free", &map, &bnum);
if (found == -1)
goto fail;
if (! found)
ERROR(ENOSPC, "no available partitions");
}
while (len > (unsigned long) map.pmPartBlkCnt);
start = (unsigned long) map.pmPyPartStart + len;
remain = (unsigned long) map.pmPartBlkCnt - len;
if (remain && nparts >= maxparts)
ERROR(EINVAL, "must allocate all blocks in free space");
map.pmPartBlkCnt = len;
strcpy((char *) map.pmPartName, name);
strcpy((char *) map.pmParType, type);
map.pmLgDataStart = 0;
map.pmDataCnt = len;
map.pmPartStatus = 0;
if (l_putpmentry(vol, &map, bnum) == -1)
goto fail;
if (remain)
{
map.pmPyPartStart = start;
map.pmPartBlkCnt = remain;
strcpy((char *) map.pmPartName, "Extra");
strcpy((char *) map.pmParType, "Apple_Free");
map.pmDataCnt = remain;
if (l_putpmentry(vol, &map, ++nparts) == -1)
goto fail;
for (bnum = 1; bnum <= nparts; ++bnum)
{
if (l_getpmentry(vol, &map, bnum) == -1)
goto fail;
map.pmMapBlkCnt = nparts;
if (l_putpmentry(vol, &map, bnum) == -1)
goto fail;
}
}
return 0;
fail:
return -1;
}
/* Boot Blocks Routines ==================================================== */
/*
* NAME: medium->zerobb()
* DESCRIPTION: write new/empty volume boot blocks
*/
int m_zerobb(hfsvol *vol)
{
block b;
memset(&b, 0, sizeof(b));
if (b_writelb(vol, 0, &b) == -1 ||
b_writelb(vol, 1, &b) == -1)
goto fail;
return 0;
fail:
return -1;
}

42
libhfs/medium.h Normal file
View File

@ -0,0 +1,42 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
/*
* Partition Types:
*
* "Apple_partition_map" partition map
* "Apple_Driver" device driver
* "Apple_Driver43" SCSI Manager 4.3 device driver
* "Apple_MFS" Macintosh 64K ROM filesystem
* "Apple_HFS" Macintosh hierarchical filesystem
* "Apple_Unix_SVR2" Unix filesystem
* "Apple_PRODOS" ProDOS filesystem
* "Apple_Free" unused
* "Apple_Scratch" empty
*/
int m_zeroddr(hfsvol *);
int m_zeropm(hfsvol *, unsigned int);
int m_findpmentry(hfsvol *, const char *, Partition *, unsigned long *);
int m_mkpart(hfsvol *, const char *, const char *, unsigned long);
int m_zerobb(hfsvol *);

50
libhfs/memcmp.c Normal file
View File

@ -0,0 +1,50 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <sys/types.h>
/*
* NAME: memcmp()
* DESCRIPTION: compare memory areas
*/
int memcmp(const void *s1, const void *s2, size_t n)
{
register const unsigned char *c1, *c2;
c1 = s1;
c2 = s2;
while (n--)
{
register int diff;
diff = *c1++ - *c2++;
if (diff)
return diff;
}
return 0;
}

473
libhfs/node.c Normal file
View File

@ -0,0 +1,473 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include "libhfs.h"
# include "node.h"
# include "data.h"
# include "btree.h"
/* total bytes used by records (NOT including record offsets) */
# define NODEUSED(n) \
((size_t) ((n).roff[(n).nd.ndNRecs] - (n).roff[0]))
/* total bytes available for new records (INCLUDING record offsets) */
# define NODEFREE(n) \
((size_t) (HFS_BLOCKSZ - (n).roff[(n).nd.ndNRecs] - \
2 * ((n).nd.ndNRecs + 1)))
/*
* NAME: node->init()
* DESCRIPTION: construct an empty node
*/
void n_init(node *np, btree *bt, int type, int height)
{
np->bt = bt;
np->nnum = (unsigned long) -1;
np->nd.ndFLink = 0;
np->nd.ndBLink = 0;
np->nd.ndType = type;
np->nd.ndNHeight = height;
np->nd.ndNRecs = 0;
np->nd.ndResv2 = 0;
np->rnum = -1;
np->roff[0] = 0x00e;
memset(&np->data, 0, sizeof(np->data));
}
/*
* NAME: node->new()
* DESCRIPTION: allocate a new b*-tree node
*/
int n_new(node *np)
{
btree *bt = np->bt;
unsigned long num;
if (bt->hdr.bthFree == 0)
ERROR(EIO, "b*-tree full");
num = 0;
while (num < bt->hdr.bthNNodes && BMTST(bt->map, num))
++num;
if (num == bt->hdr.bthNNodes)
ERROR(EIO, "free b*-tree node not found");
np->nnum = num;
BMSET(bt->map, num);
--bt->hdr.bthFree;
bt->flags |= HFS_BT_UPDATE_HDR;
return 0;
fail:
return -1;
}
/*
* NAME: node->free()
* DESCRIPTION: deallocate and remove a b*-tree node
*/
int n_free(node *np)
{
btree *bt = np->bt;
node sib;
if (bt->hdr.bthFNode == np->nnum)
bt->hdr.bthFNode = np->nd.ndFLink;
if (bt->hdr.bthLNode == np->nnum)
bt->hdr.bthLNode = np->nd.ndBLink;
if (np->nd.ndFLink > 0)
{
if (bt_getnode(&sib, bt, np->nd.ndFLink) == -1)
goto fail;
sib.nd.ndBLink = np->nd.ndBLink;
if (bt_putnode(&sib) == -1)
goto fail;
}
if (np->nd.ndBLink > 0)
{
if (bt_getnode(&sib, bt, np->nd.ndBLink) == -1)
goto fail;
sib.nd.ndFLink = np->nd.ndFLink;
if (bt_putnode(&sib) == -1)
goto fail;
}
BMCLR(bt->map, np->nnum);
++bt->hdr.bthFree;
bt->flags |= HFS_BT_UPDATE_HDR;
return 0;
fail:
return -1;
}
/*
* NAME: compact()
* DESCRIPTION: clean up a node, removing deleted records
*/
static
void compact(node *np)
{
byte *ptr;
int offset, nrecs, i;
offset = 0x00e;
ptr = np->data + offset;
nrecs = 0;
for (i = 0; i < np->nd.ndNRecs; ++i)
{
const byte *rec;
int reclen;
rec = HFS_NODEREC(*np, i);
reclen = HFS_RECLEN(*np, i);
if (HFS_RECKEYLEN(rec) > 0)
{
np->roff[nrecs++] = offset;
offset += reclen;
if (ptr == rec)
ptr += reclen;
else
{
while (reclen--)
*ptr++ = *rec++;
}
}
}
np->roff[nrecs] = offset;
np->nd.ndNRecs = nrecs;
}
/*
* NAME: node->search()
* DESCRIPTION: locate a record in a node, or the record it should follow
*/
int n_search(node *np, const byte *pkey)
{
const btree *bt = np->bt;
byte key1[HFS_MAX_KEYLEN], key2[HFS_MAX_KEYLEN];
int i, comp = -1;
bt->keyunpack(pkey, key2);
for (i = np->nd.ndNRecs; i--; )
{
const byte *rec;
rec = HFS_NODEREC(*np, i);
if (HFS_RECKEYLEN(rec) == 0)
continue; /* deleted record */
bt->keyunpack(rec, key1);
comp = bt->keycompare(key1, key2);
if (comp <= 0)
break;
}
np->rnum = i;
return comp == 0;
}
/*
* NAME: node->index()
* DESCRIPTION: create an index record from a key and node pointer
*/
void n_index(const node *np, byte *record, unsigned int *reclen)
{
const byte *key = HFS_NODEREC(*np, 0);
if (np->bt == &np->bt->f.vol->cat)
{
/* force the key length to be 0x25 */
HFS_SETKEYLEN(record, 0x25);
memset(record + 1, 0, 0x25);
memcpy(record + 1, key + 1, HFS_RECKEYLEN(key));
}
else
memcpy(record, key, HFS_RECKEYSKIP(key));
d_putul(HFS_RECDATA(record), np->nnum);
if (reclen)
*reclen = HFS_RECKEYSKIP(record) + 4;
}
/*
* NAME: split()
* DESCRIPTION: divide a node into two and insert a record
*/
static
int split(node *left, byte *record, unsigned int *reclen)
{
btree *bt = left->bt;
node n, *right = &n, *side = 0;
int mark, i;
/* create a second node by cloning the first */
*right = *left;
if (n_new(right) == -1)
goto fail;
left->nd.ndFLink = right->nnum;
right->nd.ndBLink = left->nnum;
/* divide all records evenly between the two nodes */
mark = (NODEUSED(*left) + 2 * left->nd.ndNRecs + *reclen + 2) >> 1;
if (left->rnum == -1)
{
side = left;
mark -= *reclen + 2;
}
for (i = 0; i < left->nd.ndNRecs; ++i)
{
node *np;
byte *rec;
np = (mark > 0) ? right : left;
rec = HFS_NODEREC(*np, i);
mark -= HFS_RECLEN(*np, i) + 2;
HFS_SETKEYLEN(rec, 0);
if (left->rnum == i)
{
side = (mark > 0) ? left : right;
mark -= *reclen + 2;
}
}
compact(left);
compact(right);
/* insert the new record and store the modified nodes */
ASSERT(side);
n_search(side, record);
n_insertx(side, record, *reclen);
if (bt_putnode(left) == -1 ||
bt_putnode(right) == -1)
goto fail;
/* create an index record in the parent for the new node */
n_index(right, record, reclen);
/* update link pointers */
if (bt->hdr.bthLNode == left->nnum)
{
bt->hdr.bthLNode = right->nnum;
bt->flags |= HFS_BT_UPDATE_HDR;
}
if (right->nd.ndFLink > 0)
{
node sib;
if (bt_getnode(&sib, right->bt, right->nd.ndFLink) == -1)
goto fail;
sib.nd.ndBLink = right->nnum;
if (bt_putnode(&sib) == -1)
goto fail;
}
return 0;
fail:
return -1;
}
/*
* NAME: node->insertx()
* DESCRIPTION: insert a record into a node (which must already have room)
*/
void n_insertx(node *np, const byte *record, unsigned int reclen)
{
int rnum, i;
byte *ptr;
rnum = np->rnum + 1;
/* push other records down to make room */
for (ptr = HFS_NODEREC(*np, np->nd.ndNRecs) + reclen;
ptr > HFS_NODEREC(*np, rnum) + reclen; --ptr)
*(ptr - 1) = *(ptr - 1 - reclen);
++np->nd.ndNRecs;
for (i = np->nd.ndNRecs; i > rnum; --i)
np->roff[i] = np->roff[i - 1] + reclen;
/* write the new record */
memcpy(HFS_NODEREC(*np, rnum), record, reclen);
}
/*
* NAME: node->insert()
* DESCRIPTION: insert a new record into a node; return a record for parent
*/
int n_insert(node *np, byte *record, unsigned int *reclen)
{
/* check for free space */
if (np->nd.ndNRecs >= HFS_MAX_NRECS ||
*reclen + 2 > NODEFREE(*np))
return split(np, record, reclen);
n_insertx(np, record, *reclen);
*reclen = 0;
return bt_putnode(np);
}
/*
* NAME: join()
* DESCRIPTION: combine two nodes into a single node
*/
static
int join(node *left, node *right, byte *record, int *flag)
{
int i, offset;
/* copy records and offsets */
memcpy(HFS_NODEREC(*left, left->nd.ndNRecs),
HFS_NODEREC(*right, 0), NODEUSED(*right));
offset = left->roff[left->nd.ndNRecs] - right->roff[0];
for (i = 1; i <= right->nd.ndNRecs; ++i)
left->roff[++left->nd.ndNRecs] = offset + right->roff[i];
if (bt_putnode(left) == -1)
goto fail;
/* eliminate node and update link pointers */
if (n_free(right) == -1)
goto fail;
HFS_SETKEYLEN(record, 0);
*flag = 1;
return 0;
fail:
return -1;
}
/*
* NAME: node->delete()
* DESCRIPTION: remove a record from a node
*/
int n_delete(node *np, byte *record, int *flag)
{
byte *rec;
rec = HFS_NODEREC(*np, np->rnum);
HFS_SETKEYLEN(rec, 0);
compact(np);
if (np->nd.ndNRecs == 0)
{
if (n_free(np) == -1)
goto fail;
HFS_SETKEYLEN(record, 0);
*flag = 1;
return 0;
}
/* see if we can join with our left sibling */
if (np->nd.ndBLink > 0)
{
node left;
if (bt_getnode(&left, np->bt, np->nd.ndBLink) == -1)
goto fail;
if (np->nd.ndNRecs + left.nd.ndNRecs <= HFS_MAX_NRECS &&
NODEUSED(*np) + 2 * np->nd.ndNRecs <= NODEFREE(left))
return join(&left, np, record, flag);
}
if (np->rnum == 0)
{
/* special case: first record changed; update parent record key */
n_index(np, record, 0);
*flag = 1;
}
return bt_putnode(np);
fail:
return -1;
}

34
libhfs/node.h Normal file
View File

@ -0,0 +1,34 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
void n_init(node *, btree *, int, int);
int n_new(node *);
int n_free(node *);
int n_search(node *, const byte *);
void n_index(const node *, byte *, unsigned int *);
void n_insertx(node *, const byte *, unsigned int);
int n_insert(node *, byte *, unsigned int *);
int n_delete(node *, byte *, int *);

182
libhfs/os.c Normal file
View File

@ -0,0 +1,182 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# ifdef HAVE_FCNTL_H
# include <fcntl.h>
# else
int open(const char *, int, ...);
int fcntl(int, int, ...);
# endif
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# else
#ifndef _WIN32
int close(int);
off_t lseek(int, off_t, int);
ssize_t read(int, void *, size_t);
ssize_t write(int, const char *, size_t);
int stat(const char *, struct stat *);
int fstat(int, struct stat *);
#endif
# endif
# include <errno.h>
# include <sys/stat.h>
# include <stdlib.h>
# include <stdio.h> /* debug */
# include "libhfs.h"
# include "os.h"
typedef struct cp_private {
oscallback func; /* function to call */
void* cookie; /* magic cookie to pass in */
long cur_block; /* current seek offset */
} cp_private;
/*
* NAME: os->callback_open()
* DESCRIPTION: open and lock a new descriptor from the given path and mode
*/
int os_callback_open(void **priv, oscallback func, void* cookie)
{
cp_private* mypriv;
mypriv = malloc(sizeof(*mypriv));
mypriv->func = func;
mypriv->cookie = cookie;
mypriv->cur_block = 0;
*priv = mypriv;
//fprintf(stderr, "ALLOC %p->%p\n", priv, *priv);
return 0;
}
/*
* NAME: os->close()
* DESCRIPTION: close an open descriptor
*/
int os_close(void **priv)
{
//fprintf(stderr, "FREEING %p->%p\n", priv, *priv);
free(*priv);
*priv = 0;
return 0;
}
#ifdef CP_NOT_USED
/*
* NAME: os->same()
* DESCRIPTION: return 1 iff path is same as the open descriptor
*/
int os_same(void **priv, const char *path)
{
return 0;
int fd = (int) *priv;
struct stat fdev, dev;
if (fstat(fd, &fdev) == -1 ||
stat(path, &dev) == -1)
ERROR(errno, "can't get path information");
return fdev.st_dev == dev.st_dev &&
fdev.st_ino == dev.st_ino;
fail:
return -1;
}
#endif
/*
* NAME: os->seek()
* DESCRIPTION: set a descriptor's seek pointer (offset in blocks)
*/
unsigned long os_seek(void **priv, unsigned long offset)
{
cp_private* mypriv = (cp_private*) *priv;
unsigned long result;
if (offset == (unsigned long) -1) {
result = (*mypriv->func)(mypriv->cookie, HFS_CB_VOLSIZE, 0, 0);
} else {
result = (*mypriv->func)(mypriv->cookie, HFS_CB_SEEK, offset, 0);
if (result != -1)
mypriv->cur_block = offset;
}
return result;
}
/*
* NAME: os->read()
* DESCRIPTION: read blocks from an open descriptor
*/
unsigned long os_read(void **priv, void *buf, unsigned long len)
{
cp_private* mypriv = (cp_private*) *priv;
unsigned long result;
unsigned long success = 0;
while (len--) {
result = (*mypriv->func)(mypriv->cookie, HFS_CB_READ,
mypriv->cur_block, buf);
if (result == -1)
break;
mypriv->cur_block++;
buf = ((unsigned char*) buf) + HFS_BLOCKSZ;
success++;
}
return success;
}
/*
* NAME: os->write()
* DESCRIPTION: write blocks to an open descriptor
*/
unsigned long os_write(void **priv, const void *buf, unsigned long len)
{
cp_private* mypriv = (cp_private*) *priv;
unsigned long result;
unsigned long success = 0;
while (len--) {
result = (*mypriv->func)(mypriv->cookie, HFS_CB_WRITE,
mypriv->cur_block, (void*)buf);
if (result == -1)
break;
mypriv->cur_block++;
buf = ((unsigned char*) buf) + HFS_BLOCKSZ;
success++;
}
return success;
}

34
libhfs/os.h Normal file
View File

@ -0,0 +1,34 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
#ifdef CP_NOT_USED
int os_open(void **, const char *, int);
#endif
int os_callback_open(void **priv, oscallback func, void* cookie);
int os_close(void **);
#ifdef CP_NOT_USED
int os_same(void **, const char *);
#endif
unsigned long os_seek(void **, unsigned long);
unsigned long os_read(void **, void *, unsigned long);
unsigned long os_write(void **, const void *, unsigned long);

557
libhfs/record.c Normal file
View File

@ -0,0 +1,557 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include <string.h>
# include "libhfs.h"
# include "record.h"
# include "data.h"
/*
* NAME: record->packcatkey()
* DESCRIPTION: pack a catalog record key
*/
void r_packcatkey(const CatKeyRec *key, byte *pkey, unsigned int *len)
{
const byte *start = pkey;
d_storesb(&pkey, key->ckrKeyLen);
d_storesb(&pkey, key->ckrResrv1);
d_storeul(&pkey, key->ckrParID);
d_storestr(&pkey, key->ckrCName, sizeof(key->ckrCName));
if (len)
*len = HFS_RECKEYSKIP(start);
}
/*
* NAME: record->unpackcatkey()
* DESCRIPTION: unpack a catalog record key
*/
void r_unpackcatkey(const byte *pkey, CatKeyRec *key)
{
d_fetchsb(&pkey, &key->ckrKeyLen);
d_fetchsb(&pkey, &key->ckrResrv1);
d_fetchul(&pkey, &key->ckrParID);
d_fetchstr(&pkey, key->ckrCName, sizeof(key->ckrCName));
}
/*
* NAME: record->packextkey()
* DESCRIPTION: pack an extents record key
*/
void r_packextkey(const ExtKeyRec *key, byte *pkey, unsigned int *len)
{
const byte *start = pkey;
d_storesb(&pkey, key->xkrKeyLen);
d_storesb(&pkey, key->xkrFkType);
d_storeul(&pkey, key->xkrFNum);
d_storeuw(&pkey, key->xkrFABN);
if (len)
*len = HFS_RECKEYSKIP(start);
}
/*
* NAME: record->unpackextkey()
* DESCRIPTION: unpack an extents record key
*/
void r_unpackextkey(const byte *pkey, ExtKeyRec *key)
{
d_fetchsb(&pkey, &key->xkrKeyLen);
d_fetchsb(&pkey, &key->xkrFkType);
d_fetchul(&pkey, &key->xkrFNum);
d_fetchuw(&pkey, &key->xkrFABN);
}
/*
* NAME: record->comparecatkeys()
* DESCRIPTION: compare two (packed) catalog record keys
*/
int r_comparecatkeys(const CatKeyRec *key1, const CatKeyRec *key2)
{
int diff;
diff = key1->ckrParID - key2->ckrParID;
if (diff)
return diff;
return d_relstring(key1->ckrCName, key2->ckrCName);
}
/*
* NAME: record->compareextkeys()
* DESCRIPTION: compare two (packed) extents record keys
*/
int r_compareextkeys(const ExtKeyRec *key1, const ExtKeyRec *key2)
{
int diff;
diff = key1->xkrFNum - key2->xkrFNum;
if (diff)
return diff;
diff = (unsigned char) key1->xkrFkType -
(unsigned char) key2->xkrFkType;
if (diff)
return diff;
return key1->xkrFABN - key2->xkrFABN;
}
/*
* NAME: record->packcatdata()
* DESCRIPTION: pack catalog record data
*/
void r_packcatdata(const CatDataRec *data, byte *pdata, unsigned int *len)
{
const byte *start = pdata;
int i;
d_storesb(&pdata, data->cdrType);
d_storesb(&pdata, data->cdrResrv2);
switch (data->cdrType)
{
case cdrDirRec:
d_storesw(&pdata, data->u.dir.dirFlags);
d_storeuw(&pdata, data->u.dir.dirVal);
d_storeul(&pdata, data->u.dir.dirDirID);
d_storesl(&pdata, data->u.dir.dirCrDat);
d_storesl(&pdata, data->u.dir.dirMdDat);
d_storesl(&pdata, data->u.dir.dirBkDat);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.top);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.left);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.bottom);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.right);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frFlags);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.v);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.h);
d_storesw(&pdata, data->u.dir.dirUsrInfo.frView);
d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.v);
d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.h);
d_storesl(&pdata, data->u.dir.dirFndrInfo.frOpenChain);
d_storesw(&pdata, data->u.dir.dirFndrInfo.frUnused);
d_storesw(&pdata, data->u.dir.dirFndrInfo.frComment);
d_storesl(&pdata, data->u.dir.dirFndrInfo.frPutAway);
for (i = 0; i < 4; ++i)
d_storesl(&pdata, data->u.dir.dirResrv[i]);
break;
case cdrFilRec:
d_storesb(&pdata, data->u.fil.filFlags);
d_storesb(&pdata, data->u.fil.filTyp);
d_storesl(&pdata, data->u.fil.filUsrWds.fdType);
d_storesl(&pdata, data->u.fil.filUsrWds.fdCreator);
d_storesw(&pdata, data->u.fil.filUsrWds.fdFlags);
d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.v);
d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.h);
d_storesw(&pdata, data->u.fil.filUsrWds.fdFldr);
d_storeul(&pdata, data->u.fil.filFlNum);
d_storeuw(&pdata, data->u.fil.filStBlk);
d_storeul(&pdata, data->u.fil.filLgLen);
d_storeul(&pdata, data->u.fil.filPyLen);
d_storeuw(&pdata, data->u.fil.filRStBlk);
d_storeul(&pdata, data->u.fil.filRLgLen);
d_storeul(&pdata, data->u.fil.filRPyLen);
d_storesl(&pdata, data->u.fil.filCrDat);
d_storesl(&pdata, data->u.fil.filMdDat);
d_storesl(&pdata, data->u.fil.filBkDat);
d_storesw(&pdata, data->u.fil.filFndrInfo.fdIconID);
for (i = 0; i < 4; ++i)
d_storesw(&pdata, data->u.fil.filFndrInfo.fdUnused[i]);
d_storesw(&pdata, data->u.fil.filFndrInfo.fdComment);
d_storesl(&pdata, data->u.fil.filFndrInfo.fdPutAway);
d_storeuw(&pdata, data->u.fil.filClpSize);
for (i = 0; i < 3; ++i)
{
d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrStABN);
d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrNumABlks);
}
for (i = 0; i < 3; ++i)
{
d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrStABN);
d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrNumABlks);
}
d_storesl(&pdata, data->u.fil.filResrv);
break;
case cdrThdRec:
for (i = 0; i < 2; ++i)
d_storesl(&pdata, data->u.dthd.thdResrv[i]);
d_storeul(&pdata, data->u.dthd.thdParID);
d_storestr(&pdata, data->u.dthd.thdCName,
sizeof(data->u.dthd.thdCName));
break;
case cdrFThdRec:
for (i = 0; i < 2; ++i)
d_storesl(&pdata, data->u.fthd.fthdResrv[i]);
d_storeul(&pdata, data->u.fthd.fthdParID);
d_storestr(&pdata, data->u.fthd.fthdCName,
sizeof(data->u.fthd.fthdCName));
break;
default:
ASSERT(0);
}
if (len)
*len += pdata - start;
}
/*
* NAME: record->unpackcatdata()
* DESCRIPTION: unpack catalog record data
*/
void r_unpackcatdata(const byte *pdata, CatDataRec *data)
{
int i;
d_fetchsb(&pdata, &data->cdrType);
d_fetchsb(&pdata, &data->cdrResrv2);
switch (data->cdrType)
{
case cdrDirRec:
d_fetchsw(&pdata, &data->u.dir.dirFlags);
d_fetchuw(&pdata, &data->u.dir.dirVal);
d_fetchul(&pdata, &data->u.dir.dirDirID);
d_fetchsl(&pdata, &data->u.dir.dirCrDat);
d_fetchsl(&pdata, &data->u.dir.dirMdDat);
d_fetchsl(&pdata, &data->u.dir.dirBkDat);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.top);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.left);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.bottom);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.right);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frFlags);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.v);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.h);
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frView);
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.v);
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.h);
d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frOpenChain);
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frUnused);
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frComment);
d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frPutAway);
for (i = 0; i < 4; ++i)
d_fetchsl(&pdata, &data->u.dir.dirResrv[i]);
break;
case cdrFilRec:
d_fetchsb(&pdata, &data->u.fil.filFlags);
d_fetchsb(&pdata, &data->u.fil.filTyp);
d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdType);
d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdCreator);
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFlags);
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.v);
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.h);
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFldr);
d_fetchul(&pdata, &data->u.fil.filFlNum);
d_fetchuw(&pdata, &data->u.fil.filStBlk);
d_fetchul(&pdata, &data->u.fil.filLgLen);
d_fetchul(&pdata, &data->u.fil.filPyLen);
d_fetchuw(&pdata, &data->u.fil.filRStBlk);
d_fetchul(&pdata, &data->u.fil.filRLgLen);
d_fetchul(&pdata, &data->u.fil.filRPyLen);
d_fetchsl(&pdata, &data->u.fil.filCrDat);
d_fetchsl(&pdata, &data->u.fil.filMdDat);
d_fetchsl(&pdata, &data->u.fil.filBkDat);
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdIconID);
for (i = 0; i < 4; ++i)
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdUnused[i]);
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdComment);
d_fetchsl(&pdata, &data->u.fil.filFndrInfo.fdPutAway);
d_fetchuw(&pdata, &data->u.fil.filClpSize);
for (i = 0; i < 3; ++i)
{
d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrStABN);
d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrNumABlks);
}
for (i = 0; i < 3; ++i)
{
d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrStABN);
d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrNumABlks);
}
d_fetchsl(&pdata, &data->u.fil.filResrv);
break;
case cdrThdRec:
for (i = 0; i < 2; ++i)
d_fetchsl(&pdata, &data->u.dthd.thdResrv[i]);
d_fetchul(&pdata, &data->u.dthd.thdParID);
d_fetchstr(&pdata, data->u.dthd.thdCName,
sizeof(data->u.dthd.thdCName));
break;
case cdrFThdRec:
for (i = 0; i < 2; ++i)
d_fetchsl(&pdata, &data->u.fthd.fthdResrv[i]);
d_fetchul(&pdata, &data->u.fthd.fthdParID);
d_fetchstr(&pdata, data->u.fthd.fthdCName,
sizeof(data->u.fthd.fthdCName));
break;
default:
ASSERT(0);
}
}
/*
* NAME: record->packextdata()
* DESCRIPTION: pack extent record data
*/
void r_packextdata(const ExtDataRec *data, byte *pdata, unsigned int *len)
{
const byte *start = pdata;
int i;
for (i = 0; i < 3; ++i)
{
d_storeuw(&pdata, (*data)[i].xdrStABN);
d_storeuw(&pdata, (*data)[i].xdrNumABlks);
}
if (len)
*len += pdata - start;
}
/*
* NAME: record->unpackextdata()
* DESCRIPTION: unpack extent record data
*/
void r_unpackextdata(const byte *pdata, ExtDataRec *data)
{
int i;
for (i = 0; i < 3; ++i)
{
d_fetchuw(&pdata, &(*data)[i].xdrStABN);
d_fetchuw(&pdata, &(*data)[i].xdrNumABlks);
}
}
/*
* NAME: record->makecatkey()
* DESCRIPTION: construct a catalog record key
*/
void r_makecatkey(CatKeyRec *key, unsigned long parid, const char *name)
{
int len;
len = strlen(name) + 1;
key->ckrKeyLen = 0x05 + len + (len & 1);
key->ckrResrv1 = 0;
key->ckrParID = parid;
strcpy(key->ckrCName, name);
}
/*
* NAME: record->makeextkey()
* DESCRIPTION: construct an extents record key
*/
void r_makeextkey(ExtKeyRec *key,
int fork, unsigned long fnum, unsigned int fabn)
{
key->xkrKeyLen = 0x07;
key->xkrFkType = fork;
key->xkrFNum = fnum;
key->xkrFABN = fabn;
}
/*
* NAME: record->packcatrec()
* DESCRIPTION: create a packed catalog record
*/
void r_packcatrec(const CatKeyRec *key, const CatDataRec *data,
byte *precord, unsigned int *len)
{
r_packcatkey(key, precord, len);
r_packcatdata(data, HFS_RECDATA(precord), len);
}
/*
* NAME: record->packextrec()
* DESCRIPTION: create a packed extents record
*/
void r_packextrec(const ExtKeyRec *key, const ExtDataRec *data,
byte *precord, unsigned int *len)
{
r_packextkey(key, precord, len);
r_packextdata(data, HFS_RECDATA(precord), len);
}
/*
* NAME: record->packdirent()
* DESCRIPTION: make changes to a catalog record
*/
void r_packdirent(CatDataRec *data, const hfsdirent *ent)
{
switch (data->cdrType)
{
case cdrDirRec:
data->u.dir.dirCrDat = d_mtime(ent->crdate);
data->u.dir.dirMdDat = d_mtime(ent->mddate);
data->u.dir.dirBkDat = d_mtime(ent->bkdate);
data->u.dir.dirUsrInfo.frFlags = ent->fdflags;
data->u.dir.dirUsrInfo.frLocation.v = ent->fdlocation.v;
data->u.dir.dirUsrInfo.frLocation.h = ent->fdlocation.h;
data->u.dir.dirUsrInfo.frRect.top = ent->u.dir.rect.top;
data->u.dir.dirUsrInfo.frRect.left = ent->u.dir.rect.left;
data->u.dir.dirUsrInfo.frRect.bottom = ent->u.dir.rect.bottom;
data->u.dir.dirUsrInfo.frRect.right = ent->u.dir.rect.right;
break;
case cdrFilRec:
if (ent->flags & HFS_ISLOCKED)
data->u.fil.filFlags |= (1 << 0);
else
data->u.fil.filFlags &= ~(1 << 0);
data->u.fil.filCrDat = d_mtime(ent->crdate);
data->u.fil.filMdDat = d_mtime(ent->mddate);
data->u.fil.filBkDat = d_mtime(ent->bkdate);
data->u.fil.filUsrWds.fdFlags = ent->fdflags;
data->u.fil.filUsrWds.fdLocation.v = ent->fdlocation.v;
data->u.fil.filUsrWds.fdLocation.h = ent->fdlocation.h;
data->u.fil.filUsrWds.fdType =
d_getsl((const unsigned char *) ent->u.file.type);
data->u.fil.filUsrWds.fdCreator =
d_getsl((const unsigned char *) ent->u.file.creator);
break;
}
}
/*
* NAME: record->unpackdirent()
* DESCRIPTION: unpack catalog information into hfsdirent structure
*/
void r_unpackdirent(unsigned long parid, const char *name,
const CatDataRec *data, hfsdirent *ent)
{
strcpy(ent->name, name);
ent->parid = parid;
switch (data->cdrType)
{
case cdrDirRec:
ent->flags = HFS_ISDIR;
ent->cnid = data->u.dir.dirDirID;
ent->crdate = d_ltime(data->u.dir.dirCrDat);
ent->mddate = d_ltime(data->u.dir.dirMdDat);
ent->bkdate = d_ltime(data->u.dir.dirBkDat);
ent->fdflags = data->u.dir.dirUsrInfo.frFlags;
ent->fdlocation.v = data->u.dir.dirUsrInfo.frLocation.v;
ent->fdlocation.h = data->u.dir.dirUsrInfo.frLocation.h;
ent->u.dir.valence = data->u.dir.dirVal;
ent->u.dir.rect.top = data->u.dir.dirUsrInfo.frRect.top;
ent->u.dir.rect.left = data->u.dir.dirUsrInfo.frRect.left;
ent->u.dir.rect.bottom = data->u.dir.dirUsrInfo.frRect.bottom;
ent->u.dir.rect.right = data->u.dir.dirUsrInfo.frRect.right;
break;
case cdrFilRec:
ent->flags = (data->u.fil.filFlags & (1 << 0)) ? HFS_ISLOCKED : 0;
ent->cnid = data->u.fil.filFlNum;
ent->crdate = d_ltime(data->u.fil.filCrDat);
ent->mddate = d_ltime(data->u.fil.filMdDat);
ent->bkdate = d_ltime(data->u.fil.filBkDat);
ent->fdflags = data->u.fil.filUsrWds.fdFlags;
ent->fdlocation.v = data->u.fil.filUsrWds.fdLocation.v;
ent->fdlocation.h = data->u.fil.filUsrWds.fdLocation.h;
ent->u.file.dsize = data->u.fil.filLgLen;
ent->u.file.rsize = data->u.fil.filRLgLen;
d_putsl((unsigned char *) ent->u.file.type,
data->u.fil.filUsrWds.fdType);
d_putsl((unsigned char *) ent->u.file.creator,
data->u.fil.filUsrWds.fdCreator);
ent->u.file.type[4] = ent->u.file.creator[4] = 0;
break;
}
}

47
libhfs/record.h Normal file
View File

@ -0,0 +1,47 @@
/*
* libhfs - library for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
void r_packcatkey(const CatKeyRec *, byte *, unsigned int *);
void r_unpackcatkey(const byte *, CatKeyRec *);
void r_packextkey(const ExtKeyRec *, byte *, unsigned int *);
void r_unpackextkey(const byte *, ExtKeyRec *);
int r_comparecatkeys(const CatKeyRec *, const CatKeyRec *);
int r_compareextkeys(const ExtKeyRec *, const ExtKeyRec *);
void r_packcatdata(const CatDataRec *, byte *, unsigned int *);
void r_unpackcatdata(const byte *, CatDataRec *);
void r_packextdata(const ExtDataRec *, byte *, unsigned int *);
void r_unpackextdata(const byte *, ExtDataRec *);
void r_makecatkey(CatKeyRec *, unsigned long, const char *);
void r_makeextkey(ExtKeyRec *, int, unsigned long, unsigned int);
void r_packcatrec(const CatKeyRec *, const CatDataRec *,
byte *, unsigned int *);
void r_packextrec(const ExtKeyRec *, const ExtDataRec *,
byte *, unsigned int *);
void r_packdirent(CatDataRec *, const hfsdirent *);
void r_unpackdirent(unsigned long, const char *,
const CatDataRec *, hfsdirent *);

Some files were not shown because too many files have changed in this diff Show More